I'm trying to segment my C code into two distinct memory areas, one which will be flashed to a ROM chip and another which will end up somewhere else. Here is what I have in sbc.cfg (I was originally following the custom target tutorial):

    ZP:        start =    $0, size =  $100, type   = rw, define = yes;
    RAM:       start =  $200, size = $34FF, define = yes;
    FIRM:      start = $4000, size = $3000, file   = "%O-FIRM.bin", fill = yes;
    ROM:       start = $8000, size = $8000, file   = "%O-ROM.bin", fill = yes;
    ZEROPAGE: load = ZP,  type = zp,  define   = yes;
    DATA:     load = ROM, type = rw,  define   = yes, run = RAM;
    BSS:      load = RAM, type = bss, define   = yes;
    HEAP:     load = RAM, type = bss, optional = yes;
    STARTUP:  load = ROM, type = ro;
    ONCE:     load = ROM, type = ro,  optional = yes;
    CODE:     load = ROM, type = ro;
    CODE2:    load = FIRM, type = ro;
    RODATA:   load = ROM, type = ro;
    VECTORS:  load = ROM, type = ro,  start    = $FFFA;
# ...

Of special interest are the FIRM and ROM memory areas and the CODE and CODE2 segments which are loaded into them respectively. Given that I said the memory areas begin at $4000 and $8000, I was expecting to see the instructions written to file starting there, but ld65 actually appears to have put it at the top of each file:

neil@Neil-Legion:~/cc65-customizing-demo$ hexdump build/6502-ROM.bin 
0000000 0e8d 8e02 020f 158d 8e02 0216 b988 ffff
0000010 1f8d 8802 ffb9 8dff 021e 218c 2002 ffff
0000020 ffa0 e8d0 a260 9aff a9d8 85ff a902 8536
0000030 2003 80c5 9820 2080 8041 7f20 2080 808c
0000040 a000 f000 a907 a241 4c80 0200 4060 bada
0000050 e848 bde8 0100 1029 03d0 fa68 4c40 8063
0000060 cb58 db60 0085 0186 00a0 00b2 008d c810
0000070 00b1 f8d0 0da9 008d a910 8d0a 1000 2060
0000080 4000 0220 5840 0420 7840 f680 00a0 07f0
0000090 41a9 80a2 004c 6002 00a9 0a85 80a9 0b85
00000a0 00a9 0c85 02a9 0d85 daa2 ffa9 1285 00a0
00000b0 f0e8 b10d 910a c80c f6d0 0be6 0de6 f0d0
00000c0 12e6 efd0 a960 8525 a90a 8502 a90b a800
00000d0 00a2 0af0 0a91 d0c8 e6fb ca0b f6d0 00c0
00000e0 05f0 0a91 d0c8 60f7 0000 0000 0000 0000
00000f0 0000 0000 0000 0000 0000 0000 0000 0000
0007ff0 0000 0000 0000 0000 0000 804d 8025 804e
neil@Neil-Legion:~/cc65-customizing-demo$ hexdump build/6502-FIRM.bin 
0000000 60ea 60ea 60ea 0000 0000 0000 0000 0000
0000010 0000 0000 0000 0000 0000 0000 0000 0000

It's entirely possible I'm misunderstanding the .cfg options here, but this seems to be what worked here too, so I'm confused. I'm also quite new to microprocessor programming, so I'm not exactly sure what to try next. I tried the start segment attribute as mentioned in the docs here, but this didn't change anything.

1 Answer 1


[I'm not sure what the issue is, as the wording isn't very clear, so I might entirely misinterprete this.]

Given that I said the memory areas begin at $4000 and $8000, I was expecting to see the instructions written to file starting there,

Err ... Starting where?

  • At the beginning of the file?

    • Sure!
  • At some offset address in the file?

    • No, why?

Each file collects all segments for a given memory area, nicely packed to be loaded into that area. These are files to be loaded at a certain address or burned into some storage device (EPROM) located at that address. Why should it contain and empty prelude?

I might be wrong, but it seems as if you expect every file to be an image of the whole 65k of memory, do you? Think of them as ROM chips. Like the BASIC ROM in a Commodore 64. It holds 8 KiB of code and is located at $A000. So the first byte ($94) is

  • located at (relative) address $0000 within the ROM and
  • located at (absolute) address $A000 within the CPU address space

as the address decoder hardware selects the ROM (if paged in) whenever an address between $A000 and $BFFF is used. Think of it as loaded to that address when the machine is powered on :))

Everything seems to work as intended - or at least as configured.

P.S.: Configurations are love letters:

Dear Linker,

I have collected some all the beautiful code our child needs to start walking. Would you be so kind as to look thru and

    CODE:     load = ROM, type = ro;
    CODE2:    load = FIRM, type = ro;
    VECTORS:  load = ROM, type = ro,  start    = $FFFA;
  • if you find a segment called CODE, please put it into the 'ROM' bin
  • if you find a segment called CODE2, please put it into the 'FIRM' bin

Oh, and before I forget,

  • there might be a segment called 'VECTORS'. If you find it, throw it as well into the 'ROM' bin, but all the way up to where address $FFFA will end up.

When done sorting them segments out, you may go ahead and assign them to memory the following way:

    FIRM:      start = $4000, size = $3000, file   = "%O-FIRM.bin", fill = yes;
  • there should be an output block called 'FIRM'.
  • collect all segments marked accordingly into it
  • relocated all addresses assuming it would be loaded to absolute address $4000
  • fill up all unused space with default value
  • make it up to $3000 bytes long
  • when done, please punch it into a file with the base name and "-FIRM.BIN" added.
    ROM:       start = $8000, size = $8000, file   = "%O-ROM.bin", fill = yes;
  • do the same with everything marked 'ROM'
  • just this time it needs to be relocated to $8000 and
  • it'll be $8000 in length and
  • punched it into a file with base name and "-ROM.BIN"

Let him inherit the machine.

Thoughts and Prayers

Your Programmer

Well, kinda like that.

  • Well, I feel kindof stupid now. I swear I knew this before and just had a major lapse of memory. You were completely correct in determining what my confusion was and thanks for answering my boneheaded question nicely. :) Sorry for wording my question ambiguously... I thought it made logical sense at the time...
    – ifconfig
    Nov 2, 2021 at 7:59
  • 1
    @ifconfig :)) Sure it always makes sense at the moment. That's why (re)phrasing a question often yields its answer. Good luck with your nice project.
    – Raffzahn
    Nov 2, 2021 at 8:06
  • FWIW, C64-origin assemblers like 64tass actually do model a 64K bank of memory, and will jump around in memory if you use "*=". So if you do *=0 / NOP / *=$4000 / NOP you end up with a file that's $4001 bytes long.
    – fadden
    Nov 2, 2021 at 14:40
  • @fadden: I find it somewhat curious that there wasn't, so far as I can tell, any assembler for the C64 that would output a small loader program followed by an image which the loader could place in the appropriate parts of memory. Such a program could, among other things, allow a single-pass assembler design which would output code where each patch-point contained the LSB of the preceding patch-point, and then at the end of the file include a list of all the necessary patch-contents.
    – supercat
    Nov 2, 2021 at 15:04
  • @fadden 64tass (and others) are simply, lets say, a bit less advanced. Of course the same can be reached with defining only one memory region for CC65. But what good shoud that do?
    – Raffzahn
    Nov 2, 2021 at 16:01

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .