Programming NES in C

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

Moderator: Moderators

User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Programming NES in C

Post by dougeff »

After finally getting cc65 to work, I decided to test it out, so rewrote my "Paint or Draw" game almost entirely in C. While I assumed that C would run slower and take up more ROM space, ...after comparing the two, I discovered that the one programmed in C was actually slightly shorter than the one in ASM...(the original had some redundancy, that I turned into a reused functions)... and they run about the same speed. They both took me about a week. So, all in all I'd say that programming in C is roughly equivalent to programming in ASM.

Occasionally, there's a few things that can't be done in C, but you can write those bits in ASM and export the function to be called by the C code, which is nice.

Next, I'm going to rewrite the music in Famitracker and import them in.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
Sogona
Posts: 186
Joined: Thu Jul 23, 2015 7:54 pm
Location: USA
Contact:

Re: Programming NES in C

Post by Sogona »

What makes setting up ca65/cc65 so involved?
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Re: Programming NES in C

Post by tokumaru »

dougeff wrote:and they run about the same speed.
Is that the same perceived speed (as seen by the player), or have you actually measured CPU usage in some objective way? Perceived speed is easy to match in simple programs, because there's often a lot of free CPU time per frame either way.
They both took me about a week. So, all in all I'd say that programming in C is roughly equivalent to programming in ASM.
I think it's great that you see C as a viable alternative for coding NES programs, but saying "it's the same" might be a little precipitated after only one small scale project.
User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Programming NES in C

Post by dougeff »

speed
That's 1094 cycles (ASM version) vs 1107 cycles (C version)...to complete all the logic between frames.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Programming NES in C

Post by dougeff »

What makes setting up ca65/cc65 so involved?
Alot of my problems is just the learning curve, and figuring out syntax differences from what I'm used to...

Code: Select all

*((unsigned char*)0x2006) = updateHigh;
	*((unsigned char*)0x2006) = updateLow;
	*((unsigned char*)0x2007) = updateData;
and this stuff..

Code: Select all

#pragma rodata-name (push, "RODATA")
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Programming NES in C

Post by dougeff »

...and, I couldn't get it to work at first because I was trying to compile Shirus example files with the wrong version of cc65. And I was getting error messages from the linker. Etc.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Re: Programming NES in C

Post by Bregalad »

I am surprised that you could get something working at the same speed as ASM, however I suspect that this is because you don't use features I used when I tested CC64. I used bi dimensional arrays and this turned out to be implemented by a particularly catastrophic way in CC65.
User avatar
mikejmoffitt
Posts: 1352
Joined: Sun May 27, 2012 8:43 pm

Re: Programming NES in C

Post by mikejmoffitt »

dougeff wrote:
What makes setting up ca65/cc65 so involved?
Alot of my problems is just the learning curve, and figuring out syntax differences from what I'm used to...

Code: Select all

*((unsigned char*)0x2006) = updateHigh;
	*((unsigned char*)0x2006) = updateLow;
	*((unsigned char*)0x2007) = updateData;
This isn't anything really particular to CC65, this is just totally normal C.

I don't know much about CC65, but you should really be defining those as volatile unsigned char. Otherwise the compiler may optimize out certain reads/writes.

You should be able to do something like this:

Code: Select all

volatile unsigned char *h_ptr = (volatile unsigned char *)0x2006;
*h_ptr = updateHigh;
// Etc etc
User avatar
Movax12
Posts: 529
Joined: Sun Jan 02, 2011 11:50 am

Re: Programming NES in C

Post by Movax12 »

cc65 ignores volatile. I suppose it doesn't have the ability to optimize away stuff that would matter anyway.
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Programming NES in C

Post by rainwarrior »

Volatile is the semantically correct thing to do. It basically just means its value can change outside this code (e.g. multi-threading, hardware interfaces, etc) so that an optimizer should always load/store it directly to memory every time it is used (don't reorder operations, or keep it around in a register to optimize calculations with it, etc.). I don't think cc65's optimizer is strong enough to even do the kinds of things that would make volatile matter, which is why it is currently meaningless for cc65.

There used to be an issue with code the looks like this:

Code: Select all

((unsigned char*)0)[0x2007] = a
At one time this would generate an indirect indexed write, which for the PPU causes a double increment of the PPU address. I don't know if this has since been fixed, but I don't recommend trying it, anyway.

I think it is a poor approach to try to update hardware registers in C. Much better to delegate this to assembly:

Code: Select all

; assembly file:
.proc _ppu_write:
    sta $2007
    rts
.endproc

// C header:
void __fastcall__ ppu_write(unsigned char data);

// C code:
ppu_write(a);
User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Programming NES in C

Post by dougeff »

I reposted my Paint or Draw game to RHDN, and included the source code, in C for cc65.

I've only been using cc65 for 1 month, so it's not the best code, but there are so few example codes using cc65, I thought I should. Consider this one step above 'Hello World' for any C programmers out there who want to program an NES game. It builds from Shiru's Chase game, but I rewrote 90% of the code, so it's not entirely derivative. Also, it runs music with Famitone2 from Famitracker file.

http://www.romhacking.net/homebrew/69/
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Programming NES in C

Post by dougeff »

Hey, question for all the people who know anything about cc65.

I was writing some test code, and...previously my compile.bat file always said something like this...

Code: Select all

ld65 -C nes.cfg -o blah.nes reset.o blah.o runtime.lib
...near the end.

But, I then removed 'runtime.lib' off that line...and it compiled perfectly into an .nes file. So, my question is, why do we need a runtime.lib? What is it supposed to be doing?
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
GradualGames
Posts: 1106
Joined: Sun Nov 09, 2008 9:18 pm
Location: Pennsylvania, USA
Contact:

Re: Programming NES in C

Post by GradualGames »

If I understand correctly, runtime.lib is Shiru's modified version of nes.lib (included with CC65). I'm not exactly sure what he's done with it, but in my own adventures with C, what I've done is to remove any object files I don't need from nes.lib but I still compile against it for any standard C stuff I might use, such as string routines for example. My understanding is that it has a partial implementation of the standard C library and some default code for interacting with the ppu. I don't think they have printf implemented, but you can use putchar and write your own string printing routine without adding any custom code. They have a buffered ppu system set up (by default in nes.lib) for just writing tiles to the screen. It's not very useful for game programming so what I did was to remove crt0.o from nes.lib, scarf crt0.s from cc65's source code and modify it with my own startup code and my own approach to nmi handlers. Breaks some of the C implementation, but you should still be able to use the rest of it. Basically I think it is harmless to keep compiling against it because, if I understand compilation correctly, if you don't link to a function it won't get built into your rom. So I just leave it there.

*edit* I'm pretty sure there's other stuff in there that's required for basic operations the compiler relies on for temporary register manipulation, too. I'm unable to compile without it, probably because I'm using crt0.s. I'd be wary of not including it all if you're coding in C. *edit* sure enough, just one simple C routine I wrote, which doesn't even use any C library stuff, won't compile without nes.lib. So, I anticipate if you write much C at all in your program, you'll run into issues pretty quickly without the runtime. *edit* note this is all assuming runtime.lib was indeed from shiru's C examples, and assuming that it is indeed a modified version of nes.lib. I'm pretty sure it is because, it seemed natural also for me to remove crt0.o from nes.lib and customize it, but leave the runtime in place. He just renamed it to runtime.lib. *edit* probably because the library he has provided is itself called neslib, haha! It all makes sense now :)

Not sure if that helps or has just increased the confusion! :P
lidnariq
Posts: 10677
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: Programming NES in C

Post by lidnariq »

GradualGames wrote:If I understand correctly, runtime.lib is Shiru's modified version of nes.lib (included with CC65). I'm not exactly sure what he's done with it
Shiru's runtime.lib is just a subset of the runtime as-of when he made it. It's missing an awful lot, so rather than explicitly listing all 310 missing object files:
* No TGI
* No string operations
* No conio.h things
* No FILE * operations
* random other things
I'm pretty sure there's other stuff in there that's required for basic operations the compiler relies on for temporary register manipulation, too. I'm unable to compile without it, probably because I'm using crt0.s. I'd be wary of not including it all if you're coding in C. *edit* sure enough, just one simple C routine I wrote, which doesn't even use any C library stuff, won't compile without nes.lib. So, I anticipate if you write much C at all in your program, you'll run into issues pretty quickly without the runtime.
You can't do function calls with arguments without at least some of the cc65 runtime (push* and pop* functions)¹. You can't do multiplication or division without some of the cc65 runtime. Many bits of pointer math require the runtime.

¹ You can call assembly functions that have a single 8- or 16- bit argument without the runtime if cc65 is run WITHOUT --all-cdecl or the function prototype is declared with __fastcall__. That argument is in A or X:A.
User avatar
dougeff
Posts: 2875
Joined: Fri May 08, 2015 7:17 pm
Location: DIGDUG
Contact:

Re: Programming NES in C

Post by dougeff »

So, for the sake of argument...
If all my functions look like this...

Code: Select all

void foo(void)
And I use no multiply/divide.
(And a few other things)
I can leave runtime.lib off without error?
nesdoug.com -- blog/tutorial on programming for the NES
Post Reply