Memory segments in ca65

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Post Reply
Morotskaka
Posts: 2
Joined: Sat Dec 01, 2012 2:46 am

Memory segments in ca65

Post by Morotskaka »

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: Select all

	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: Select all

.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!
Jsolo
Posts: 27
Joined: Mon Jun 27, 2011 4:14 am
Location: Lurker Cave

Re: Memory segments in ca65

Post by Jsolo »

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: Select all

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.
User avatar
Movax12
Posts: 529
Joined: Sun Jan 02, 2011 11:50 am

Re: Memory segments in ca65

Post by Movax12 »

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: Select all

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; )
Morotskaka
Posts: 2
Joined: Sat Dec 01, 2012 2:46 am

Re: Memory segments in ca65

Post by Morotskaka »

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.
Post Reply