Page 1 of 4

Programming NES games in C article

Posted: Wed Jan 04, 2012 9:03 am
by Shiru

Re: Programming NES games in C article

Posted: Wed Jan 04, 2012 10:23 am
by cpow
Shiru wrote:Read the article
Shiru, very nice article! One thing I'd added to NESICIDE is the ability to see mixed-mode source during debugging. That helps -- I think, anyway -- people that want to develop in C see exactly what the C compiler generates for any particular C source line of code. Example:

Large screenshot

Also, this statement:
Shiru wrote:The problem is that there are no comfortable debuggers for C code compiled into 6502 assembly around (yet) - ones that allow to put breakpoints on random C lines and see what the variables contain at the moment. Usually you only have an assembly level debugger in some emulators, and it is not very helpful with compiled code.
is not really true. NESICIDE can do that. I've been stepping through AlterEgo at the C level for quite a while now. Thank you for such a polished and useful example of programming for NES in C.

Posted: Wed Jan 04, 2012 10:54 am
by mrm78
@Shiru :D nice chasing game

Posted: Wed Jan 04, 2012 4:27 pm
by jwdonal
Cool game dude! Awesome article as well! Would love to read/play more. :)

Posted: Wed Jan 04, 2012 7:57 pm
by Shiru
cpow, I've changed that line in the text. It is great that NESICIDE has C level debugger. I can't really use it, though, because my PC is too slow to run the built emulator fullspeed (it is like 50% now), so I have to wait to the next PC upgrade.

Posted: Thu Jan 05, 2012 2:29 am
by mic_
Nice info and cleanly written.
Do you know if CC65 handles cross-bank calls well (linking symbols that have been compiled into separate banks)? This was quite a hassle with SDCC (Z80) and I ended up having to write some custom tools for it, and it still didn't work perfectly.

Posted: Thu Jan 05, 2012 2:54 am
by Shiru
I don't know, haven't get to this point yet - so far all my NES projects were NROM. I only read that you can put functions and data in different banks (so they are compiled into the same addresses), but have to switch banks before accessing them by yourself.

Posted: Thu Jan 05, 2012 4:00 am
by Bregalad
Congratulations, that was interesting.

Something I don't understand so well :
Due to very limited NES resources, such as CPU speed, RAM and ROM size, writting a proper, clean C code isn't very effective. To make it faster and shorter you have to do things that otherwise aren't considered acceptable. They are disable some of C advantages, making the code more low level and less structured.

There are suggestions that will make your code more effective, but certainly less readable:
....
Then you write C code that is less portable and less readable, and more low level. I don't have anything against it but doesn't this kill the purpose of using C instead of assembly ?

Posted: Thu Jan 05, 2012 4:29 am
by Shiru
It is a bit low level this way, but certainly not down to the assembly level - it is still rather high level.

Random example, you have a level map that is larger than 256 bytes, lets say 32x32, and need to get a value from it using two 8-bit vars, mx and my. So, for C code:

Code: Select all

n=map[(my<<5)+mx]
Assembly code counterpart is something like this:

Code: Select all

 lda my
 sta ptr_l
 lda #0
 sta ptr_h
dup 5
 asl ptr_l ;<<5
 rol ptr_h
edup
 lda ptr_l
 clc
 adc mx
 sta ptr_l
 lda ptr_h
 adc #0
 sta ptr_h
 lda ptr_l
 clc
 adc #<map
 sta ptr_l
 lda ptr_h
 adc #>map
 sta ptr_h
 ldy #0
 lda [ptr_l],y
One line of the code is faster to write and easier to mantain than 20+. You probably could optimize the assembly version as it was written off the top of my head, but certainly not down to a single line.

Posted: Thu Jan 05, 2012 6:46 am
by clueless
I can see using a hybrid model of C and Assembly. Consider an RPG, where the battle logic is better expressed in C, but code the rest (NMI handler, tile display logic, sound engine, etc...) in asm.

In the past Tepples has stated that the Koei simulation games were written in C, and that is probably why they seem sluggish. I bet that we could do better.

I seriously considered making some sort of an RPG as my next nesdev project, and coding part of it in C, but I lack the time to take on such a project right now. :(

Posted: Thu Jan 05, 2012 7:18 am
by Shiru
That's how it is done in my NES games - C code only works with hardware through special functions written in assembly. So NMI handler, tile updater, sound code is written in assembly, and game logic in C.

You'll run into the other problem with RPG battle logic in C - the resulting code will be huge, so the problem with bankswitching have to be solved somehow.

Posted: Thu Jan 05, 2012 7:33 am
by Bregalad
Shiriu, your example just convicted me the usefulness of C language !

Posted: Thu Jan 05, 2012 7:44 am
by thefox
mic_ wrote:Do you know if CC65 handles cross-bank calls well (linking symbols that have been compiled into separate banks)? This was quite a hassle with SDCC (Z80) and I ended up having to write some custom tools for it, and it still didn't work perfectly.
Not sure what exactly you're asking here. As long as the runtime functions (pusha/popa etc) are placed in a fixed bank it should work OK. The switching itself has to be done manually of course.

Posted: Thu Jan 05, 2012 8:32 am
by tepples
mic_ wrote:Nice info and cleanly written.
Do you know if CC65 handles cross-bank calls well (linking symbols that have been compiled into separate banks)?
It doesn't happen automagically, but depending on the calling convention used, you may be able to write a bunch of stubs that make cross-bank calls using a trampoline in the fixed bank.

Posted: Thu Jan 05, 2012 11:08 am
by mic_
Do you know if CC65 handles cross-bank calls well
Not sure what exactly you're asking here.
Let's say you split your code/data into separate banks which are compiled and linked separately (to avoid having duplicates of the same stuff for different combinations of banks) and then combined into a .nes file.

So e.g.

bank0.c -> bank0.obj -> bank0.bin
bank1.c -> bank1.obj -> bank1.bin
...
header + bank0.bin + bank1.bin + ... -> game.nes

If bank0 wants to call a function in bank1, can you tell CC65 to resolve the address of that function without actually putting a copy of the function in bank0 as well? Using hardcoded addresses is a PITA, and using a proxy function at a fixed address to delegate calls isn't really that nice either IMO.

With SDCC I ended up writing a tool that parsed the .SYM files generated by the linker and it would output a header file with named function pointers for any given bank that other banks could use. It didn't work when there were cross-dependencies (bank X and bank Y both wanted to call eachother), so in some instances I still had to use hardcoded addresses.