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

Programming NES in C

Programming NES in C
by on (#154645)
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.
Re: Programming NES in C
by on (#154652)
What makes setting up ca65/cc65 so involved?
Re: Programming NES in C
by on (#154658)
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.

Quote:
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.
Re: Programming NES in C
by on (#154660)
Quote:
speed


That's 1094 cycles (ASM version) vs 1107 cycles (C version)...to complete all the logic between frames.
Re: Programming NES in C
by on (#154661)
Quote:
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:
*((unsigned char*)0x2006) = updateHigh;
   *((unsigned char*)0x2006) = updateLow;
   *((unsigned char*)0x2007) = updateData;


and this stuff..

Code:
#pragma rodata-name (push, "RODATA")
Re: Programming NES in C
by on (#154664)
...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.
Re: Programming NES in C
by on (#154680)
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.
Re: Programming NES in C
by on (#154690)
dougeff wrote:
Quote:
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:
*((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:
volatile unsigned char *h_ptr = (volatile unsigned char *)0x2006;
*h_ptr = updateHigh;
// Etc etc
Re: Programming NES in C
by on (#154691)
cc65 ignores volatile. I suppose it doesn't have the ability to optimize away stuff that would matter anyway.
Re: Programming NES in C
by on (#154694)
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:
((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:
; assembly file:
.proc _ppu_write:
    sta $2007
    rts
.endproc

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

// C code:
ppu_write(a);
Re: Programming NES in C
by on (#156445)
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/
Re: Programming NES in C
by on (#159320)
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:
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?
Re: Programming NES in C
by on (#159322)
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
Re: Programming NES in C
by on (#159325)
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

Quote:
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.
Re: Programming NES in C
by on (#159327)
So, for the sake of argument...
If all my functions look like this...
Code:
void foo(void)

And I use no multiply/divide.
(And a few other things)
I can leave runtime.lib off without error?
Re: Programming NES in C
by on (#159328)
".lib" (library) is just a collection of ".o" (object) files. If your project compiled without it, it means that none of the files in your project referenced any of the symbols in the object files in the library.

You're going to run into problems sooner or later if you try to go entirely without the runtime environment that the C compiler expects.
Re: Programming NES in C
by on (#159329)
dougeff wrote:
So, for the sake of argument...
If all my functions look like void foo(void)
And declare all your variables static, global, or use cc65 -Cl ... in theory, yes, there's a way to avoid using the runtime at all.

In practice, you'll keep on finding random other edge cases that I failed to remember. And it's not like the runtime code is bad at what it does.

(Actually, your function declaration can return a 8- or 16- bit value without the runtime, too)
Re: Programming NES in C
by on (#159330)
lidnariq wrote:
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

Quote:
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.


Cool, thanks for the clear explanation. I knew it was stuff like that, just hadn't yet bothered to find out exactly what. :D I didn't think that any file operations were even implemented at all in nes.lib to begin with. I actually did try to open up shiru's runtime.lib to compare it to nes.lib, using ar, but I must have had a much later version of cc65 (I compile it from the master branch, using mingw64) as it didn't want to list the file.
Re: Programming NES in C
by on (#159334)
dougeff wrote:
I can leave runtime.lib off without error?

1. It's very likely you'll do something that needs it sooner or later as your program grows. If you want to attempt to program without the CRT you'll run up against all sorts of arbitrary and strange restrictions, there's no way somebody could give you a concise list. The C compiler simply EXPECTS a lot of it to be available, and it will freely produce code that makes calls to it.

2. Even if you're not using it, there's no real benefit to leaving it off. The linker will include only what it needs from the runtime library. Everything else is automatically omitted from your final binary. So, no space is saved by omitting it.
Re: Programming NES in C
by on (#159347)
I figured as much, I just like to ask questions so I know as much about the tools I'm using as possible.

Follow up question...

I notice there's no snes.lib. Should I avoid using cc65 for SNES dev? And stick to ca65 alone?
Re: Programming NES in C
by on (#159349)
The cc65 C compiler can only target 6502 (not 65816), so you'd be limited to the 6502 subset of 65816 if you use it for SNES stuff.
Re: Programming NES in C
by on (#159350)
cc65 can't generate 65816 code (yet). Devkitsnes uses a thing called Tiny C Compiler.

You should still be able to use cc65 to generate 65c02 programs that'll run on the SNES, but.