Home > Blockchain >  Unexpected outcome of writing single byte to VRAM in GBA tile mode, value also written in next or pr
Unexpected outcome of writing single byte to VRAM in GBA tile mode, value also written in next or pr

Time:06-19

I'm trying to display a single tile with a single colored pixel using GBA tile mode, starting from scratch.

It mostly work, but instead of setting a single pixel, it set the same color to the two bytes at the 16bits aligned location where I intend to write:

Resulting image when running through the mgba emulator: Screen

The emulator allows to inspect the tiles, memory and register.
Tilesmap, showing the two pixels set on the tiles.
VRAM, starting at the tiles address, showing that there are indeed two 8bpp pixel set, referencing the color in palette at index 42.
Display Control Register nothing unexpected, running in first tile mode with background 0 enabled.
Background 0 Control register, in 8bpp mode.

Main:

#include "screen.h"

void do_it() {
  // [tile_index][row][column] = palette index
  VRAM.tilesets[3].d_tiles[37][0][0] = 42;
}


int main() {
  // [tilemap][row][column] = tile_index
  VRAM.tilemaps[0][0][0] = 37;
  do_it();
  PALETTE.backgrounds[42] = rgb(0b1111, 0b11111, 0);
  BG0CNT = BGCNT_SIZE_32X32 | BGCNT_COLOR_8BPP | (0 << BGCNT_TILEMAP_INDEX) | (3 << BGCNT_TILESET_INDEX);
  DISPCNT = DISPCNT_MODE_TILE_0 | DISPCNT_BACKGROUND_0;
  while (1) {};
}

Useful parts of screen.h:

typedef unsigned char tile_4bpp[8][4];
typedef unsigned char tile_8bpp[8][8];

typedef union {
  tile_4bpp s_tiles[512];
  tile_8bpp d_tiles[256];
} char_block;

extern union {
  char_block tilesets[4];
  screen_block tilemaps[32];
  color bitmap[HEIGHT][WIDTH];
} VRAM;

So in the function do_it, I am setting a single byte value, only once at an hardcoded location.
Yet in memory, it set the value to the given address AND the one that complete 2 bytes alignment:
VRAM.tilesets[3].d_tiles[37][0][0] = 42; and VRAM.tilesets[3].d_tiles[37][0][1] = 42; have the same outcome.

I thought I could have done something badly in my structs/unions/types definitions, but assembly looks fine:

Disassembly of section .text:

080000e8 <do_it>:
 80000e8:   4b02        ldr r3, [pc, #8]    ; (80000f4 <do_it 0xc>)
 80000ea:   4a03        ldr r2, [pc, #12]   ; (80000f8 <do_it 0x10>)
 80000ec:   212a        movs    r1, #42 ; 0x2a
 80000ee:   5499        strb    r1, [r3, r2]
 80000f0:   46c0        nop         ; (mov r8, r8)
 80000f2:   4770        bx  lr
 80000f4:   06000000    streq   r0, [r0], -r0
 80000f8:   0000c940    andeq   ip, r0, r0, asr #18

080000fc <main>:
 80000fc:   b510        push    {r4, lr}
 80000fe:   4b09        ldr r3, [pc, #36]   ; (8000124 <main 0x28>)
 8000100:   2225        movs    r2, #37 ; 0x25
 8000102:   801a        strh    r2, [r3, #0]
 8000104:   f7ff fff0   bl  80000e8 <do_it>
 8000108:   4b07        ldr r3, [pc, #28]   ; (8000128 <main 0x2c>)
 800010a:   2254        movs    r2, #84 ; 0x54
 800010c:   4907        ldr r1, [pc, #28]   ; (800012c <main 0x30>)
 800010e:   5299        strh    r1, [r3, r2]
 8000110:   4b07        ldr r3, [pc, #28]   ; (8000130 <main 0x34>)
 8000112:   228c        movs    r2, #140    ; 0x8c
 8000114:   801a        strh    r2, [r3, #0]
 8000116:   4b07        ldr r3, [pc, #28]   ; (8000134 <main 0x38>)
 8000118:   2280        movs    r2, #128    ; 0x80
 800011a:   0052        lsls    r2, r2, #1
 800011c:   801a        strh    r2, [r3, #0]
 800011e:   46c0        nop         ; (mov r8, r8)
 8000120:   e7fd        b.n 800011e <main 0x22>
 8000122:   46c0        nop         ; (mov r8, r8)
 8000124:   06000000    streq   r0, [r0], -r0
 8000128:   05000000    streq   r0, [r0, #-0]
 800012c:   000003ef    andeq   r0, r0, pc, ror #7
 8000130:   04000008    streq   r0, [r0], #-8
 8000134:   04000000    streq   r0, [r0], #-0

I'm not fluent in arm assembly, but the strb in do_it seems to match the affectation and it should operate on a single byte according to the arm doc.

Some more info:

  • It happens on multiple emulator: I usually use mgba, it happens on vbam too
  • Other roms work as intended on mgba
  • Building with arm-none-eabi-gcc toolchain v 12.1.0
  • Without any optimisations flags
  • Using -mthumb -mthumb-interwork -mcpu=arm7tdmi -fomit-frame-pointer -ffast-math -fno-strict-aliasing
  • It also happens with 4bpp mode, setting four pixels instead of expected two
  • Code is running from gamepak1 memory directly
  • It happens when using differents tilesets/tilemaps locations
  • It does not happens when running in bitmap mode:

This works and display a SINGLE green pixel on the top left of the screen:

int main() {
  VRAM.bitmap[0][0] = rgb(0b1111, 0b11111, 0);
  DISPCNT = DISPCNT_MODE_3 | DISPCNT_BACKGROUND_2;
  while (1) {};
}

Full codebase can be found there: github

Any idea of what could cause this ?

CodePudding user response:

Solved thanks to @old_timer comments.

VRAM can only be accessed with 16 or 32 bit transactions

Changing my typedefs from

typedef unsigned char tile_4bpp[8][4];
typedef unsigned char tile_8bpp[8][8];

to

typedef unsigned short tile_4bpp[8][2];
typedef unsigned short tile_8bpp[8][4];

Fixed the issue, in assembly the strb is replaced by a strh that store the half-word as expected:

void do_it() {                                                                                                                                                 
  int column = 0;                                                                                                                                              
  int row = 0;                                                                                                                                                 
  VRAM.tilesets[0].d_tiles[37][row][column >> 1] |= 42 << ((column & 1) ? 8 : 0);                                                                              
  column = 1;                                                                                                                                                  
  VRAM.tilesets[0].d_tiles[37][row][column >> 1] |= 43 << ((column & 1) ? 8 : 0);                                                                              
}                                                                                                                                                              
                                                                                                                                                               
                                                                                                                                                               
int main() {                                                                                                                                                   
  // [tilemap][row][column] = tile_index                                                                                                                       
  VRAM.tilemaps[20][0][0] = 37;                                                                                                                                
  do_it();                                                                                                                                                     
  PALETTE.backgrounds[42] = rgb(0b11111, 0b11111, 0);                                                                                                          
  PALETTE.backgrounds[43] = rgb(0, 0, 0b11111);                                                                                                                
  BG0CNT = BGCNT_SIZE_32X32 | BGCNT_COLOR_8BPP | (20 << BGCNT_TILEMAP_INDEX) | (0 << BGCNT_TILESET_INDEX);                                                     
  DISPCNT = DISPCNT_MODE_TILE_0 | DISPCNT_BACKGROUND_0;                                                                                                        
  while (1) {};                                                                                                                                                
}  

Will set the two pixels with their respective colors.

It did not happen in bitmap mode because the bitmap mode I used for testing was 16bpp mode, so I was setting a full half-word for each pixels.

I have not yet been able to test on hardware, so I can't tell if the observed behavior when using byte level operation on VRAM on emulator would have happened on a real GBA too.

CodePudding user response:

memmap:

MEMORY
{
   ewram : ORIGIN = 0x02000000, LENGTH = 256K
}
SECTIONS
{
   .text : { *(.text*) } > ewram
}

startup.s

.cpu arm7tdmi
.code 32

.globl _start
_start:
    ldr sp,=0x03008000
    bl notmain
hang:
    b hang

.globl PUT16
PUT16:
    strh r1,[r0]
    bx lr

.globl GET16
GET16:
    ldrh r0,[r0]
    bx lr

.globl PUT32
PUT32:
    str r1,[r0]
    bx lr

.globl GET32
GET32:
    ldr r0,[r0]
    bx lr

notmain.c

extern void PUT32 ( unsigned int, unsigned int );
extern unsigned int GET32 ( unsigned int );
extern void PUT16 ( unsigned int, unsigned int );
extern unsigned int GET16 ( unsigned int );

#define DISPCNT 0x04000000
#define BG0CNT  0x04000008

#define PMEM 0x05000000
#define TMEM 0x06000000
#define VMEM 0x06008000

void notmain ( void )
{
    unsigned int ra;
    unsigned int rb;

    //display control,
    //mode 0
    //enable BG0
    PUT16(DISPCNT,0x0100);
    //BG0 control
    //256 color palette
    //tiles defined at 0x60000000
    //screen at 0x60008000
    PUT16( BG0CNT,0x1080);
    //setup the first 8 colors
    PUT16(PMEM 0x0,0x0000); //BLACK
    PUT16(PMEM 0x2,0x001F); //RED
    PUT16(PMEM 0x4,0x03E0); //GREEN
    PUT16(PMEM 0x6,0x03FF); //GREEN RED
    PUT16(PMEM 0x8,0x7C00); //BLUE
    PUT16(PMEM 0xA,0x7C1F); //BLUE RED
    PUT16(PMEM 0xC,0x7FE0); //BLUE GREEN
    PUT16(PMEM 0xE,0x7FFF); //BLUE GREEN RED (WHITE)
    //lets make a few tiles 64 bytes per tile.
    ra=TMEM;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;

    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000000); ra =4;
    PUT32(ra,0x00000000); ra =4; PUT32(ra,0x00000002); ra =4;

    //make screen black/clear screen
    ra=VMEM;
    for(rb=0;rb<(32*20);rb  )
    {
        PUT16(ra,0x0000);
        ra =2;
    }
    //put some tiles on the screen
    PUT16(VMEM,1);
}

build:

arm-none-eabi-as --warn --fatal-warnings  startup.s -o startup.o
arm-none-eabi-gcc -c -mcpu=arm7tdmi -Wall -O2 -ffreestanding  notmain.c -o notmain.o
arm-none-eabi-ld -nostdlib -nostartfiles -T memmap startup.o notmain.o -o notmain.elf
arm-none-eabi-objdump -D notmain.elf > notmain.list
arm-none-eabi-objcopy notmain.elf -O binary notmain.mb

run:

vba notmain.mb

I run linux so I do not have vba-m but I do have vba. And this works fine. You can wrap this with a copyjump or build it as a gba rom and the cartridges you can buy today do not care about the title header in the gba file because the cartridge firmware is the actual gba rom. mgba seems to want the proper header, and looks like you know how to do that.

Your key problem is that VRAM can only be properly accessed with 16 or 32 bit transfers (str or strh) not 8 bit (strb). It will mess up the other pixel in that halfword.

EWRAM  256Kbytes, can be written as bytes, halfwords, and words
IWRAM  32Kbytes, can be written as bytes, halfwords, and words
PRAM   512 halfwords, can be written as halfwords or words (not bytes)
VRAM   98Kbytes, can be written as halfwords or words (not bytes)
  • Related