This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Memory segments in ca65

Memory segments in ca65
by on (#103773)
Hi,

I've just started out with NES programming, and it's time for my first question.

I'm using the ca65 assembler (together with the ld65 linker) on OS X. At https://bitbucket.org/ddribin/nerdy-nights/src, there is a ca65 version of some of the Nerdy Night tutorials. I will refer to tutorial 6 in the following, but my question applies to all ca65 code. It seems to me that the following happens: Sprite data gets loaded into CPU address $0200, and then dma'd into PPU memory. Here is the question: *Why* does it end up at $0200? I can find no reference to the address in the code (I don't expect to, since that's not how ca65 rolls), or in the linker configuration (this is where I expected to find it).

I run 'make' to build example 6. The following command line is executed:
cl65 -vm -l -g -t nes -m background2.map -Ln background2.lbl -o background2.nes background2.asm

When I run it, I see the Mario sprite.

The following code from the example starts the dma copy:

Code:
   lda   #$00      ; set the low byte (00) of the RAM address
   sta   $2003
   lda   #$02      ; set the high byte (02) of the RAM address
   sta   $4014   ; start the transfer


In other words, it sets the source address to $0200 and then starts the transfer.

However, in the source, the sprite pixel data is declared thus:

Code:
.segment "CHARS"
   .incbin   "mario.chr"   ; includes 8KB graphics from SMB1


So, to find where data in the "CHARS" segment end up, I consult the linker configuration:

$ ld65 --dump-config nes

MEMORY {
ZP: start = $02, size = $1A, type = rw, define = yes;
HEADER: start = $0, size = $10, file = %O ,fill = yes;
ROM0: start = $8000, size = $7ff4, file = %O ,fill = yes, define = yes;
ROMV: start = $fff6, size = $c, file = %O, fill = yes;
ROM2: start = $0000, size = $2000, file = %O, fill = yes;
SRAM: start = $0500, size = $0300, define = yes;
RAM: start = $6000, size = $2000, define = yes;
}
SEGMENTS {
HEADER: load = HEADER, type = ro;
STARTUP: load = ROM0, type = ro, define = yes;
LOWCODE: load = ROM0, type = ro, optional = yes;
INIT: load = ROM0, type = ro, define = yes, optional = yes;
CODE: load = ROM0, type = ro, define = yes;
RODATA: load = ROM0, type = ro, define = yes;
DATA: load = ROM0, run = RAM, type = rw, define = yes;
VECTORS: load = ROMV, type = rw;
CHARS: load = ROM2, type = rw;
BSS: load = RAM, type = bss, define = yes;
HEAP: load = RAM, type = bss, optional = yes;
ZEROPAGE: load = ZP, type = zp;
}

FEATURES {
CONDES: segment = INIT,
type = constructor,
label = __CONSTRUCTOR_TABLE__,
count = __CONSTRUCTOR_COUNT__;
CONDES: segment = RODATA,
type = destructor,
label = __DESTRUCTOR_TABLE__,
count = __DESTRUCTOR_COUNT__;
CONDES: type = interruptor,
segment = RODATA,
label = __INTERRUPTOR_TABLE__,
count = __INTERRUPTOR_COUNT__;
}

SYMBOLS {
__STACKSIZE__ = $0300;
}


It seems that "CHARS" data is loaded into address "ROM2", which in turn is set to 0. So, why does it get loaded into $0200?

(I assume that does in fact end up at $0200, since the dma transfer seems to work. However, since I'm a complete newbie at this, I can't be completely sure).

Thanks in advance!
Re: Memory segments in ca65
by on (#103782)
Hi :)

You're confusing OAM data (e.g. sprite position) with the actual pattern data (the 8x8 graphics you see on screen).
Each sprite on screen is composed out of 8x8 so called "tiles". The OAM data only defines which tile a sprite uses, not how the tile looks.
The graphics data for the tiles (also called "pattern data") lies in a special part of ROM only accessible to the rendering unit of the NES.

The DMA unit can only transfer OAM data from a specified page in RAM or ROM. For many programs this is the $0200-$02FF area in RAM.
From the tutorial,
Code:
load_sprites:
        ldx     #$00            ; start at 0
@loop:
        lda     sprites, x      ; load data from address (sprites + x)
        sta     $0200, x        ; store into RAM address ($0200 + x)
        inx                     ; x = x + 1
        cpx     #$10            ; copmare x to hex $10, decimal 16
        bne     @loop

This populates the first 16 bytes of $0200 with meaningful sprite data, which the DMA uploads to OAM.

The CHR data is in fact situated at $0000-$1FFF, but does not live in CPU address space. CHR data lives in PPU address space $0000-$1FFF, which
can only be accessed through the PPU data register.
Re: Memory segments in ca65
by on (#103788)
As well, your linker config is overly complex; maybe try this for plain nrom, with -t none rather than -t -nes since the preconfigured nes stuff is maybe not too useful for actual game coding.

Code:
MEMORY
{
  ZP: start = $0, size = $100, type = rw;
  HEADER: start = 0, size = $0010, type = ro, file = %O, fill=yes, fillval=$00;
  STACK:    start = $0100, size = $0100, type = rw;
  OAMRAM:    start = $0200, size = $0100, type = rw;
  RAM:    start = $0300, size = $0500, type = rw;
  ROM:    start = $8000, size = $8000, type = ro, file = %O, fill=yes, fillval=$00;
  CHR:    start = $0000, size = $2000, type = ro, file = %O, fill=yes, fillval=$00;
}

SEGMENTS {
  INESHDR:  load = HEADER, type = ro;
  ZEROPAGE: load = ZP, type = zp;
  STACK:      load = STACK, type = bss, define = yes, optional = yes;
  OAMRAM:      load = OAMRAM, type = bss, define = yes, optional = yes;
  BSS:      load = RAM, type = bss, define = yes;
  CODE:     load = ROM, type = ro;
  RODATA:   load = ROM, type = ro;
  VECTORS:  load = ROM, type = ro, start = $FFFA;
  CHRROM:  load = CHR, type = ro;
}

FILES {
  %O: format = bin;
}



This is what I use now for nrom, (note CHRROM rather than CHARS). You could also remove OAMRAM and STACK segments since they are rarely explicitly defined in code and optionally redefine RAM to start at $0200 or some combination of what you like. (OAMRAM may be a misleading name since that is technically in the PPU, maybe SHADOWOAM would be better.)

As mentioned, CHR starts at zero because it is not CPU memory space - it could start at any value at all since there is no code that is defined in that segment. Ca65/ld65 needs the start address to know where code labels go. The only important thing is the size. (you could use CHR: start = $1234, size = $2000, type = ro, file = %O, fill=yes, fillval=$00; )
Re: Memory segments in ca65
by on (#103798)
Hi,

Thank you very much (both of you)! That clears it up. Now I can move on to the rest of the code. :)

Thanks again for the quick answers.