Programming for the NES in C or C++

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Programming for the NES in C or C++
by on (#95762)
I know how to program, but I have no idea how to program in Assembler. And when I have a look at tutorials, it doesn't look like fun to me, unlike working in a higher programming language. That's why I'd like to know: Is there a C or C++ compiler for the NES that you can write actual programs with?

by on (#95765)
Zooming Secretary is written in C, and has source code available.
But C on the NES is not that fast, probably 4-8 times slower than equivalent ASM code (wild guess).

by on (#95767)
NESICIDE can be used to develop/debug C or assembly programs for NES.

The speed factor of C is, IMO, not noticeable in Zooming Secretary or Alter Ego.

by on (#95777)
I really should be mentioned that any game written for the NES in C is going to get wretched performance.

You really should just learn 6502 if you're interested in programming the NES. You'd be surprised how simple it is. Much, much simpler than C.


EDIT: maybe I should actually read the replies before I reply. Looks like dwedit already mentioned that C performance would suck. =P

by on (#95778)
If you think assembly isn't fun in it's own way, you've never programmed assembly. It's 10x better than x86 or Z80, so be happy. 6502 is simple, and fun.

by on (#95781)
Disch wrote:
I really should be mentioned that any game written for the NES in C is going to get wretched performance.

C code itself generally has awful performance compared to assembly (especially on the NES), but that doesn't mean the game itself has to have wretched performance.
cpow wrote:
The speed factor of C is, IMO, not noticeable in Zooming Secretary or Alter Ego.

Yes.

Performance isn't necessarily the bottleneck that will stop you from making your game. It can be, but it doesn't have to be. I think just getting the code written and working is a much bigger bottleneck. I find C code a lot faster to write, and easier to debug, and a lot of the time that's worth much more to me than the performance hit. A lot of things in any given game do not have to be high performance. If it takes you one or four frames to set up the next level, the user is not going to notice, but if it takes you one day or a week to write it...


Shiru wrote a great article about getting started in C: http://nesdev.com/bbs/viewtopic.php?t=8493

You will eventually need to learn some assembly, but you might be able to get a fair bit done without it just by using Shiru's work as a springboard.

by on (#95787)
I'd say programming for the NES in C is feasible for a wide variety of single screen games, but I wouldn't expect something like SMB3. Scrolling, object management and collision detection are already too demanding in assembly.

I'm with the gang that thinks 6502 assembly is fun. If you don't think so it's probably because of the shock that is the initial transition from a high-level language. Once you master it though, assembly can be really fun.

by on (#95788)
Disch wrote:
Much, much simpler than C.


Well, I'm very fluent with C and 6502 asm, and I strongly disagree with your statement. C is easier than ANY assembly language, from almost any point of view. The thing with assembly is that you have to know not only instructions, but general architecture of the target machine, which involve a bit more of knowledge of computer system in general (processor flags etc.) which is a lot of things to learn as a beginner. Moreover, while with C there's room for programming errors, with assembly there's even more room for silly errors you can't get with higher level programming (unmatched push/pop, accidental clubber of temp reg/mem, etc). Maybe highly arguable, but the last pages of this thread proves my point.

I mean, let's consider this very simple example:

C:
Code:
int somefunction(int a, int b)
{
   int c = a + b;
   return c;
}


Assembly (6502):
Code:
somefunction:
   ; let's suppose parameters are in temp1, temp1+1, temp2 and temp2+2
   ; and reuse temp1 & temp1+1 for return values.
   lda temp1
   clc
   adc temp2
   sta temp1
   lda temp1+1
   adc temp2+1
   sta temp1+1
   rts


That's 2 lines of C code (that could be put in one line; matter of style) against 8 in 6502 assembly. You forgot the clc? Tough luck, you'll have slightly inacurate results, which will have some strange result depending when you call it. Forgot the rts? Well, your program will be screwed. There's plenty of way simple code can go wrong in assembly, and your assembler will not catch 'em. In C that's another matter: forget the return statement, you'll get a warning; give the wrong number of parameters, it will error out. You can still screw thing up with pointers, but at least it's not with the very basic.

Just look also: in assembly, you'll have to know where your data is so you get them correctly, and you must know its size too. In C, I don't give a heck of where my data is and its size, or the registers used, I just want to add them, period. If I want to multiply two numbers, I don't even care of the algorithm, I just want to be more focused on my algorithm. Code look more natural, I have less things to consider when doing code.

Now, don't get me wrong, I'm not saying C is better than assembly, and I didn't consider performance, which in some implementations (on NES in particular) is really not the best, or other limitations in the NES implementation (mapper handling), but at least it is always easier than any assembler language and the most accessible for a beginner.

by on (#95789)
How many lines of code does it take to get something actually on the screen and working in C vs assembly? I can get a RLE compressed screen and start up the hardware in about 100 lines total, including the subroutine. I'd like to see the most optimized C output of doing the same.

by on (#95790)
Quote:
...

If I'm more comfortable in C and you're more comfortable in assembly, what's the difference? If we both can produce quality games that people enjoy playing, what's the difference? If C and assembly both can be used as springboards for entry into the arena of programming for NES and for learning the other, what's the difference?

The OP asked about the availability of a C compiler for NES. The simple answer to that is "yes, there is a C compiler for the NES." Responding with "C sucks" and "you'll never make a game like SMB3 with C" doesn't answer the question, it just fuels the fire to turn this into a flamethread.

by on (#95796)
There is a C compiler for the NES. It is certainly a viable way to make games. Speed-wise, I'm very confident that you can make a scrolling game like SMB or Final Fight (i.e. short range attacks and 4-5 characters on the screen), but probably can't make a good Contra-like game (requires processing many objects). The main problem is not speed, but code size, but there are ways to deal with it, it is not a complete show stopper. If you get a speed bottleneck, you always can make an assembly equivalent of this exact part of the code, which is way easier that writing everything in assembly.

Writing in C is few times faster and easier than in assembly code, even 6502 one, so it worth to consider if you don't have much time to work on a project.

For games that pushes NES hardware to the max you have to choose assembly. However, to my knowledge, from all homebrew games only one actually attempts to do that - Hiatus Ward, which is not even finished currently.

by on (#95797)
3gengames wrote:
How many lines of code does it take to get something actually on the screen and working in C vs assembly? I can get a RLE compressed screen and start up the hardware in about 1100 lines total, including the subroutine. I'd like to see the most optimized C output of doing the same.


Input:

Code:
#include "neslib.h"

#include "test.h"   //packed nametable data

const unsigned char palette[16]={ 0x0f,0x21,0x10,0x30,0x0f,0x14,0x21,0x31,0x0f,0x29,0x16,0x26,0x0f,0x09,0x19,0x29 };//palette data


void main(void)
{
   pal_bg(palette);//set background palette from an array

   unrle_vram(test,0x2000);//unpack nametable into the VRAM

   ppu_on_all();//enable rendering

   while(1);//do nothing, infinite loop
}


Output is 365 lines, but it uses a library that is ~1000 lines of code without a music player (another 1000 lines). All that assembly code is commented, even the compiler output (it includes corresponding parts of the source code).

by on (#95818)
cpow wrote:
"you'll never make a game like SMB3 with C" doesn't answer the question

Of course it does. In addition to knowing that there is a C compiler, he must know about the limitations of using that language. I didn't say that out of spite, I was just making it clear that even though you can use C, assembler is more capable.

by on (#95820)
I somehow think that 'you'll never make a game like SMB3' statement is close to the truth for homebrew projects, with assembler or not.

by on (#95821)
Shiru wrote:
I somehow think that 'you'll never make a game like SMB3' statement is close to the truth for homebrew projects, with assembler or not.


But in the very distant future, Maybe it is possible if we keep learning everything about 65xx ASM by then...

McKids (McDonaldLand in PAL reigons) is almost compared to SMW, Although it took a official dev team 2 years, It may still lead us to inspire to recreate another engine several or more years after this year (If we still exist...)

So maybe there can be a chance, May be a long-shot, but it is possible!

by on (#95822)
Depending on how capable the assembler is, advanced macros can sometimes be a great substitute for real C code without hurting performance as much.
However, since C is such a ubiquitous language, it automatically comes with many other benefits e.g. reusable code, large toolset with syntax highlighting, static analysis, etc

by on (#95826)
Hamtaro126 wrote:
But in the very distant future, Maybe it is possible if we keep learning everything about 65xx ASM by then...

The problem here is not knowledge, it's time and motivation. Lots of people here know the NES better than most of the professional developers from back in the day (we've been studying it for over 10 years, while they only focused on it for short periods), and we know everything there is to know about game concepts like scrolling, collision detection, and everything else necessary for a big-scale game.

However, coding a big game like SMB3 demands a lot of time, something we don't have because of our real-life jobs and bills to pay. When we do have the time, we lack the motivation, because it's such a huge undertaking anyway and we are not offered any sort of compensation for it.

by on (#95827)
It seems creating SMB3 comparable games is not a pure ASM vs. C thing then. It's a motivation thing.

Also, I suspect that we all know why higher level languages exist. Why EDLIN gave way to EDIT. If more savvy programmers worked on better C libraries then the difference between C and ASM performance would decrease. As long as we think C *OR* ASM - high or low - they'll always be a self-justifying difference in output.

The best way to convince a beginner or higher level language programmer to convert to ASM is to recommend something that allows for ASM but retains its higher level language underpinnings for faster development. This is what Fred Quimby did with Batari BASIC for the Atari 2600. It's a gateway drug to assembly - easy results and easy integration of assembly when needed.

NESICIDE should offer the best chance for high level language programmers to start NES development. Especially ones who expect a development environment to just work out of the box.

Here's a promising C library but I'm not sure how it works with the cc65 included in NESICIDE:
http://kkfos.aspekt.fi/projects/nes/kne ... -for-cc65/

by on (#95830)
Well maybe it's a bit too early for me to talk about this but I currently have a project where I'm going to write an entire game in assembly, and port it entirely in C, and this could be a benchmark for compilers. Before anyone is calling me crazy I'm talking about a simple puzzle game here, not something like SMB3. Also I'm at about 50% of the development stage of the assembly version.

The C version will have to use almost everything the C language offers too (complex data types, routines with loads of arguments etc...). This could be used as a benchmark for C compilers for how well they perform.

The reason to do this for a whole game instead of just some piece of code is that it will give constructive results. If you just use a random piece of code as a benchmark, it will not be sinificative enough to tell if the downgrade of performance done by the comiler is handicapping for a game or not.

First I'd try with CC65, but then I'd like to either port SDCC to the 6502, or do an unofficial port of CC65 which will have better performance. I don't know if I'll be able to do it, in fact I'll probably have some trouble as I have no experience in writing compilers, and this is a complex subject.

Ideally I think a program written in C should at the very worst be 1/3 less efficient than it's assembly counterpart, which should be acceptable in most cases. When it's not acceptable, anyone can still continue to use assembly.

However I have quite some experience both in programming in C, and definitely a whole load of experience programming in 6502 assembly, so maybe I'm one of the few people that could handle such a project so I'll try.

So yes this seems ambitious, but for some reason I think this should be useful to the NESDev communality to be able to program in C without being afraid that the resulting compiled code will eventually suck.

by on (#95832)
Bregalad wrote:
I currently have a project where I'm going to write an entire game in assembly, and port it entirely in C

I think this experiment would work better if you made the C version first, and then ported it to assembly. Since C is more restricted than assembly, the program will have to be structured in a certain way for it to work, and when porting it to assembly you can obey the same structure. If you start will all the freedom of assembly, the C version will probably have to be completely redesigned. i think a comparison would be more meaningful if both programs had the same underlying architecture, and only the actual tasks were performed in either C or assembly.

by on (#95833)
I don't know maybe you're right.
I've already started the game in assembly - the idea to port it to C and make a benchmark out of it only came later.

I think I could port it in C without changing the entire structure. It's not like if I were using lots of tricks - even when I code in assembly I usually stuck to standard structure where functions calls themselves with some arguments, and it's extremely rare I have a function returning more than one value - if this happens I'll just use global variables or anyother alternative to port it directly in C without changing the structure too much.

The reason I don't start with C is so I don't feel like I'm doing the compiler's job when I translate into assembly, which could influence my in my style of assembly writing.

by on (#95834)
Games of SMB3 scale are very difficult for homebrew devs not because of programming (which is maybe a bit tricky, but doable), but because of the scale, amount and quality of the content - things that homebrew devs not very good at, because they mostly programmers. SMB3 wouldn't be that great without these components.

by on (#95835)
@Bregalad: Maybe when you start the C version you could use NESICIDE. I'm sure your feedback from a seasoned developers point of view would be valuable. I've already got the n00b developers point of view covered :p

by on (#95838)
slobu wrote:
Here's a promising C library but I'm not sure how it works with the cc65 included in NESICIDE:
http://kkfos.aspekt.fi/projects/nes/kne ... -for-cc65/

My library is kind of low level, it mostly just exposes the NES hardware registers to the C code in an easy to use way together with some support routines like controller reading. It's OK for quickly prototyping some stuff (I have written various tests using it myself, much faster than it would have taken to write them in assembly), but for game development right now it's better to use a higher level library, like the one provided by Shiru in his C examples.

That said I have sometimes been thinking about expanding the library with more "high level" stuff, but that's probably not going to happen until I actually need that stuff somewhere. :)

by on (#95851)
Dwedit wrote:
Zooming Secretary is written in C, and has source code available.

Neat. Is this game completely written in C, without any self-written Assembly?

Disch wrote:
Much, much simpler than C.

Sorry, but I won't believe that.

By the way, does Assembly have functions and the ability to re-use code? Can I play around with one and the same program or is anything that I write fixed until I tediously change many parts in the source code?

One example: If I have a game character that is made of various sprites, like Mega Man, can I write a function in Assembly that moves all the sprite objects with the same call? Or do I have to program the movement of each sprite object individually? Like when I decide that the character shall not consist of two, but of three sprite objects. In C, I would just edit the drawing routine:
Code:
void MoveCharacter(int x, int y)
{
    MoveSprite(spr1, x, y);
    MoveSprite(spr2, x, y);

    // The character shall consist of three sprites now,
    // so I add this:
    MoveSprite(spr3, x, y);
}

My other logic wouldn't change, I would always just call
Code:
MoveCharacter(30, 122);
no matter if my character consists of two, three or 100 single sprites.

Is something like that possible with Assembly? Or do I have to call all the steps for movement for each sprite individually and if I add one sprite, I have to add 20 more commands in my code?

by on (#95852)
6502 has the instructions JSR (jump to subroutine) and RTS (return from subroutine). JSR pushes the current program counter and jumps to your subroutine; RTS does the reverse, returning to the next instruction.

A C function will use these when compiled. Store your arguments somewhere (in registers or somewhere in memory), JSR to the function's code, then "return" will store the return values somewhere and RTS.

So yes, the hardware has a concept of a subroutine.

by on (#95853)
JimDaBim wrote:
Neat. Is this game completely written in C, without any self-written Assembly?

I believe the source code is available, so you can check it out yourself.

Quote:
By the way, does Assembly have functions and the ability to re-use code?

The code is as reusable as you make it, like any other programming language.

Quote:
One example: If I have a game character that is made of various sprites, like Mega Man, can I write a function in Assembly that moves all the sprite objects with the same call?

Sure.

Quote:
Or do I have to program the movement of each sprite object individually?

Newbies often do that, but that's because they're newbies. =)

Quote:
Like when I decide that the character shall not consist of two, but of three sprite objects. In C, I would just edit the drawing routine:

This is fine, although you'd usually avoid this much function calling in a system as weak as the NES, no matter if you are working in C or in ASM.

Quote:
if I add one sprite, I have to add 20 more commands in my code?

Like I said, you typically wouldn't call a function for each individual sprite because that would be too slow. You'd probably have a loop, something like "from sprite X to sprite X+Y do this", in which case you'd just modify the range of the loop and it would cost you no extra commands at all if you wanted to handle more sprites.

Most beginner tutorials use assembly in a very specific way, with lots of hardcoded commands, but don't let that fool you - assembly is as versatile and as reusable as any other language. Maybe even more, because it isn't constrained to data types and logic blocks. You can even apply OOP concepts fairly easily.

by on (#95854)
JimDaBim wrote:
By the way, does Assembly have functions and the ability to re-use code?

Assembly language has subroutines, which do the same thing as functions in C. For example, functions in C compile to subroutines in assembly language. It also has jump tables, which can be turned into method calls.

Quote:
One example: If I have a game character that is made of various sprites, like Mega Man, can I write a function in Assembly that moves all the sprite objects with the same call?

Yes. What typically happens is that you have a subroutine that takes X, Y, and frame arguments, calculates where each of the sprites that make up a frame go in relationship to the given coordinate, and writes all the sprites to a display list. The display list lists all sprites that are considered visible during this frame, and it grows from 1 element to up to 64 over the course of each frame before it's copied to the video chip.

Quote:
The character shall consist of three sprites now

So you add the third sprite to the list of the sprites that make up each frame, and the subroutine reads the longer list.

by on (#95856)
Here are some problems that I see with compiling C for the NES:

1) 6502 has a concept of a call stack, but only partially. JSR/RTS only record the PC, they do not push/pop local variables or function parameters. The 256 bytes on the 6502 is probably not enough space to handle such a call stack, so the compiled code will have to simulate its own stack.

This means that local variables and function parameters are all going to have to be accessed indirectly. This poses several problems on the 6502:

-) LDA (n),Y is 5-6 cycles. LDA abs is 4 cycles. This means for memory accesses alone, you're already running 25-50% slower.
-) Y has to be reserved as the new stack pointer, which means you have one less general purpose register. Those quick nested loops you can write in raw assembly now become cumbersome because C will have to use zero page memory for additional loop counters instead of Y. INY = 2 cycles vs. INC zp = ?5? cycles (recalling these cycle counts from memory, feel free to double check).
-) Since Y is the new stack pointer, if you want to use Y for any other purpose you have to back it up and restore it. Since Y is required for most indirect accesses, that means any pointer dereferencing now entails a STY/LDY/LDY, in addition to the actual dereference itself. Note that the STY+LDY+LDY actually takes longer than LDA (n),Y, so that's over a 100% slowdown.

Granted these can be optimized away, but not completely. And a bunch of those little slowdowns add up real fast.

2) Common C practices would murder performance. Things like using 'int' for general variables, which every C programmer does. If you are adhering to C standards, an int has to be at least 16 bits. Can you imagine what kind of slowdown (and memory consumption!) you'd get in an NES program if every variable is 16 bits wide?




Really, for a C program to really be effective on the NES, it would have to be written with an understanding not only of how the NES architecture operates, but also how the compiler operates. You'd have to do things like use lots of globals, minimize the number of parameters passed to functions, etc, etc... to really get peak performance. Note that these are things you're generally not really supposed to do in C.

by on (#95857)
It's not that hard to optimize around the virtual stack. If you keep your functions void() and manage your temporary/local variables on the ZP/BSS yourself, this takes care of most of it without much effort.

Recursion is a problem, but there are still solutions for that.


And yes of course you should learn good practices for the platform you target. The CC65 manual will tell you to use char instead of int unless you need it, etc. (Or read shiru's guide.) Also, there's not a lot of harm in writing it quickly and inefficiently at first and optimizing later when you actually have performance problems.

by on (#95858)
Disch wrote:
...

Create first. Optimize later. If doing the reverse you'll never get to the create step. If you create something that sucks balls in performance you'll either feel bad enough about it that you'll learn how to fix your mistakes before making your mess public, or you'll seek out a generally helpful bunch of folk like those that usually hang out here, and they'll...usually...help.

Having said that, my comments apply to people that enjoy taking on the majority of the challenge themselves, unlike what appears to be going on in another 40+ page thread here. Oh...isn't he trying to do it in assembly? How many times have you guys said "you forgot a PLA" or "you forgot an RTS"?

by on (#95859)
cpow wrote:
Create first. Optimize later.

And if you feel it makes creating easier, make your game mechanics in Pygame or XNA or another high-level environment before porting them to the NES.

by on (#95860)
cpow wrote:
How many times have you guys said "you forgot a PLA" or "you forgot an RTS"?

Which would become "You forgot an ! (not)" or "You forgot an * (to make something a pointer)" or "You need to cast (uint8)", or "You used | not ||" etc., etc in C. I don't think that's a fair example. It's just the compiler will catch things like "you forgot a semicolon", so that a person might not have to post about it, but it's not as if a new person would not still have trouble or post about what the actual compiler errors MEAN. It's not much different.

In my experience assembly has been much easier to teach and learn, it's just harder to actually USE for something complex which I guess is the current topic. And I mean teach for understanding, not teach for the test. (Here's Hello World with no explanation. Copy paste it, and wow! Magic!)

There were lots of things about C I NEVER got or just copy pasted from tutorials (which is just plain awful) until I learned some assembly language.

Edit: Just to be more on topic for the original post: Since the topic creator already knows C, I don't believe he should develop his game entirely in assembly language. But he'll almost certainly need to learn it regardless for certain parts of the program, so he needs to be aware of that.

by on (#95861)
tepples wrote:
And if you feel it makes creating easier, make your game mechanics in Pygame or XNA or another high-level environment before porting them to the NES.

Yes, this is very good advice. I'm writing a game in Java, myself, intended for the NES eventually.


Kasumi, what's unfair about that example? Messing up your stack is an entire class of errors that are impossible in C because the stack is abstracted away for you. A missing semicolon in C isn't equivalent to missing an RTS or PLA in assembly. One is a compile time error, corrected very quickly, the other is undefined runtime behaviour, which if you're lucky just crashes outright, and if you're unlucky looks fine for now and becomes a nightmare to debug later on.

by on (#95862)
rainwarrior wrote:
Kasumi, what's unfair about that example? Messing up your stack is an entire class of errors that are impossible in C because the stack is abstracted away for you. A missing semicolon in C isn't equivalent to missing an RTS or PLA in assembly. One is a compile time error, corrected very quickly, the other is undefined runtime behaviour, which if you're lucky just crashes outright, and if you're unlucky looks fine for now and becomes a nightmare to debug later on.


That. This was my point, just put out much nicely.

Seriously, I don't even understand the "assembly is easier than C" point. From a beginner's perspective, it's a lot of potential pitfalls your assembler will not even give a hint about it.

Let consider this sentence: " * is easier than assembly (on any target)", then the wildcard matches any high level programming language, except some esoteric programming languages of questionable use (brainf*ck, Malbolge, Piet etc).

by on (#95866)
rainwarrior wrote:
Kasumi, what's unfair about that example? Messing up your stack is an entire class of errors that are impossible in C because the stack is abstracted away for you.

How about messing up on braces in a way that will compile? I've had an error like that. I'm saying that hard-to-debug-because-you're-reading-what-you-think-is-there-rather-than-what's-there errors aren't unique to assembly. Even with a compiler, to a new person, the errors can occasionally be pretty unhelpful. An experienced programmer would never miss an RTS, not mess up their braces, or would catch these things immediately. But I honestly don't think it's so different for a new guy to C or assembly. I didn't think it was a fair example, because I imagine the same person would experience similar errors whether it was C, assembly, Java, or visual basic.

How about something like this:
Code:
int function(int x){
     int mario[10];
   //Do some actions on the mario array. Maybe to count how many times
  //the numbers 0-9 are used in some global data.
   return mario[x]//Then return the count.
}

I thought it was safe to assume that all the values in mario[10] would be initialized to 0, which isn't true here. So this function would never work as expected, and the compiler I was using at the time didn't tell me anything, because (I'm guessing) at least some of mario was touched each time it was run. (Otherwise, it would warn me I was trying to return an uninitialized variable.) That's a close example to something I encountered when I started out. In fact, I believe it wasn't until my first use of malloc that something actually clicked and I understood WHY it would be stupid to expect all arrays to be initialized to 0 at runtime.

I can name quite a few similar things in C I did not understand because it was abstracted from me. Learning C was a battle for me, and 6502 assembly absolutely wasn't. I guess I'm the only one.
~J-@D!~ wrote:
Seriously, I don't even understand the "assembly is easier than C" point. From a beginner's perspective, it's a lot of potential pitfalls your assembler will not even give a hint about it.

I didn't say assembly was easier to program in (in fact I said the opposite), or that it was easier. I said in my experience it's easier to teach and learn, and I think it's because (for 6502 at least) it does about a single thing at a time. Even if you never use the language to actually make anything other than examples, it gives you a really great understanding of programming.

I agree C(++) has a better workflow, and if we weren't programming for NES I'd recommend it every time. I'm sharing my personal experience of 6502 being easier, and saying C(++) still has lots of room for beginner mistakes that seem to be ignored whenever these discussions come up. I'm not even sure 6502 assembly has more pitfalls for a beginner honestly. It's definitely true that a compiler is more helpful than an assembler when such errors occur, though.

by on (#95874)
Making typographical errors or logical errors when programming happens to everyone. It gets better with experience but nobody's perfect. Errors will happen. My issue is not with how easy or hard it is to produce correct code, but with what happens when you fail to do so.

Assembly has very few compile time errors; most of them are syntax errors which are trivial to fix. There is almost nothing that will guard against incorrect logic at compile time; these errors have to be found exclusively at runtime, and fixing runtime errors takes much more development time than compile errors, by a wide margin.

C provides all sorts of things that prevent logical errors at compile time. For instance, in C literals and addresses are distinct types, not distinguished by a # prefix in the instruction they are used, but distinguished by their definition. There is no way to use one as the other without explicitly doing so (e.g. an array operator or a cast).

Similarly 16 bit and 8 bit variables are distinguished by their definition; the compiler won't forget which is which or cast away information unless you explicitly tell it to. It will never forget the carry bit.

etc.

I've probably said to much on this subject, sorry.


Anyhow, as I said, there's a lot you can do with very little (or perhaps no) assembly work, especially starting from Shiru's examples, and I see no reason to discourage someone new to this from using it. Yes, learning assembly is useful, and absolutely critical for doing certain kinds of things, but this doesn't invalidate C for the other things.

I'd rather see someone be able to make a simple game in C, than get discouraged because someone told them they have to learn assembly. If you're ready to learn it, learn it, if not, I'd rather see you finish something than be held back making a bunch of beginner's mistakes in a language you're just learning.

If you already know what you're doing, then my opinion doesn't matter; you've already got your own, and probably have a workflow that suits you.

by on (#95877)
I'm with Kasumi on this one, you're being kinda unfair when comparing C to ASM. For someone who already knows C, like in this case, using it to get started with NES programming makes sense, but for people who know neither C nor ASM, I don't think it's simple to say which one is easier.

I've seen people who knew (eh... kinda knew, that was in college and not many people liked programming at all) Pascal and Visual Basic, who were completely lost when they started learning C, much like newbies here are lost with ASM. You can screw things up pretty badly with C, maybe not as much as with ASM, but I feel like the rules of ASM are easier for beginners to understand, since the building blocks are so small. So I think there's some sort of balance there, that doesn't give a clear advantage of one language over the other if you don't know either one.

EDIT: I do wonder if it's possible to complete a game for the NES in C without knowing ANY assembly though. Shiru succeeded because he knows the NES very well, and was using his own libraries. A person that is not familiar with the overall architecture of the NES and has no clue of what his C code is turning into probably wouldn't achieve the same success. I mean, you have to know what registers are there and when you're supposed to use them, you have to be aware of the limitations for accessing the PPU, things like that, and that's usually easier to see when using ASM.

by on (#95885)
There was a project by someone who don't know 6502 assembly and NES architecture and limitations. He managed to make a demo with large moving and jumping animated character and background with limited two-screen scroll using my library. Then he learned some limitations (8 sprites per scanline), they were a showstopper for him. So - basic NES architecture knowledge is important in this case, while 6502 assembly code is not.

by on (#95887)
Quote:

1) 6502 has a concept of a call stack, but only partially. JSR/RTS only record the PC, they do not push/pop local variables or function parameters. The 256 bytes on the 6502 is probably not enough space to handle such a call stack, so the compiled code will have to simulate its own stack.

No, no, no !!

This is the very reason CC65 performs so poorly.
However, SDCC does not have a call stack, at the expanse of not allowing re-entrant functions (functions that calls themselves).
This is something that is typically never, ever used. Why waste so much performance for something that is never used ?
After all C is just instructions to automate assembly code - and those instructions are free to be interpreted in the way the compiler likes it. A C compiler that does not follow exactly the standards is way better than writing everything in assembly.

Quote:
This means that local variables and function parameters are all going to have to be accessed indirectly. This poses several problems on the 6502:

-) LDA (n),Y is 5-6 cycles. LDA abs is 4 cycles. This means for memory accesses alone, you're already running 25-50% slower.

I'm sure everything can be accessed in RAM and Zero-Page for fast aces if the compiler does it this way.
Quote:
2) Common C practices would murder performance. Things like using 'int' for general variables, which every C programmer does. If you are adhering to C standards, an int has to be at least 16 bits. Can you imagine what kind of slowdown (and memory consumption!) you'd get in an NES program if every variable is 16 bits wide?

It's very common to program micro-controller in C and use "u8" for all variables that can fit in 8-bits.

Now for the reason I insist so much about C is the cross-platform compatibility.

Let's assume you want to do a game for both the NES and GameBoy.
If you code them all in assembly, you'll have to do all the work twice, because both use completely different processors.

If you do it in C, only the hardware-related parts will have to be rewritten, all the game logic can be kept identical to both versions with very minimal work.
Not only that but a C code typically have between 5 to 10 times less lines of codes than it's assembly counterpart.

That means between 10 and 20 times less work to code a game for two platforms. Worth it isn't it ?

by on (#95895)
rainwarrior wrote:
I'd rather see someone be able to make a simple game in C, than get discouraged because someone told them they have to learn assembly. If you're ready to learn it, learn it, if not, I'd rather see you finish something than be held back making a bunch of beginner's mistakes in a language you're just learning.

My point exactly, stated much more eloquently.

by on (#95914)
Bregalad wrote:
Quote:
Let's assume you want to do a game for both the NES and GameBoy.
If you code them all in assembly, you'll have to do all the work twice, because both use completely different processors.


It might be best to write a good chunk of the game in high-level pseudocode and compile it by hand, given that a C-compiler is better suited to GB than NES (Although I tried using one for GB several years back and remember it behaving quirky with 16-bit math).

by on (#95915)
What happens if your program barely cuts it on the NES on CPU and then can't run at the same speed because of the microprocessor needing more clocks that aren't there?

by on (#95917)
3gengames wrote:
What happens if your program barely cuts it on the NES on CPU and then can't run at the same speed because of the microprocessor needing more clocks that aren't there?

Don't tell me you haven't ever seen a game lagging, have you ?

by on (#95918)
I've never seen a C game with more than a couple objects and a couple background tiles to write, either. I mean, games should run at 100% speed 100% of the time, C or no C.

by on (#95922)
It is still possible to make thousands of nice games with just a couple of moving objects on screen (in fact, it was up to 5 objects and 16 tiles in Alter Ego). You know, even the current best selling NES homebrew game does not have much more moving objects and tiles than in 'a C game', and it would be easy to make it in C.

by on (#95923)
I didn't know sales determined how well a game was programmed. In that case, commando is better than all NES homebrews? lol.

by on (#95924)
ROM space is also an issue. Compiled C code will probably take a lot more space, and even if say Super Mario Bros. could be written in C, it wouldn't fit in 32kB.

by on (#95927)
It does not matter how well a game was programmed at all, as long as the game is good. I think it is very obvious that there is a reason why the game is a best seller, and the reason - the game is good. You can make a game like this in C.

I think I repeated it like a hundred times everywhere that the ROM space is the main issue with programming for the NES in C.

by on (#95932)
I've been experimenting with CC65 with AOROM. It was a bit of a pain to get going, but it's been working well so far. This helps a lot with the code space issue. Some things that I needed to do:

1. Compile and link each 32kb bank as a separate binary, and then merge the banks into your ROM manually. (copy /b header.bin+bank0.prg+bank1.prg... game.nes) I couldn't figure out a way to link in the required C runtime stuff to every bank otherwise.

2. Don't use DPCM for sound; this lets you switch banks more freely. (Or saves you from having to duplicate DPCM sounds in every bank at an inconvenient location in the middle of your bank.)

3. Divide banks by function, rather than having a full copy of all code in each bank. e.g. you can put all your music code and data in one bank, JSR to a routine that bankswithches, runs the music update, bankswitches back, and RTSes. Similarly, all the CHR data and code for loading CHR-RAM can go in one bank, etc.

I like AOROM especially, because for a repro it's only one extra (low cost) chip and capacitor vs NROM. Also if it was good enough for Battletoads, it's probably good enough for me.

by on (#95933)
Shiru wrote:
I think I repeated it like a hundred times everywhere that the ROM space is the main issue with programming for the NES in C.


Whoops, just noticed it in your first post.

by on (#95972)
strat wrote:
ROM space is also an issue. Compiled C code will probably take a lot more space, and even if say Super Mario Bros. could be written in C, it wouldn't fit in 32kB.

Again, only a good benchmark will tell us.

Usually optimizations optimize both code size and speed. Then only for the highest level optimizations, the compiler lets you choose between size or speed because optimize one will hurt the other. - Typically use a lot of LUTs will improve speed, but cost bytes.

When I said a compiler should perform at least 2/3 as good as assembly written code, I was counting the size as well as the speed.

by on (#96057)
Sorry for the bump. And it's a bit off topic too (well, out of the original question) but I thought of something and verified a believed misonception.
Bregalad wrote:
[Using a software stack] is the very reason CC65 performs so poorly.
However, SDCC does not have a call stack, at the expanse of not allowing re-entrant functions (functions that calls themselves).
This is something that is typically never, ever used. Why waste so much performance for something that is never used ?


Here there is a misconception that seems to be shared by others, or maybe no one noticed that (myself I did, until I re-read his post):

Being re-entrant means much more than allowing recursion, it means being able to give always the same results given the same input. Such a routine must not rely on global/static variables that are not constant.

Let's consider a subroutine which uses fixed memory locations as parameters and/or temp space. Such a subroutine cannot be used on two different contexts (2 or more threads, or main/ISR) because the other context(s) could corrupt data used in the first context.

Imagine a small subroutine, named "A" here, used in both the main routine and an ISR. If "A" is running in the main context, then an IRQ/NMI fires in the middle of "A", "A" in ISR will execute correctly, however it clobbers the temp space used in "A" in main context, so when returning to the latter, "A" will use bogus data that has been corrupted by the ISR.

How to overcome this problem? HI-TECH C compiler for PIC10/12/16 don't allow using a C function in both the main context and an ISR, forcing you to define 2 identical functions, one used in main context and the other in the ISR. You can also save its temp/parameter space on the stack when entering an ISR, making this subroutine virtually re-entrant, but then you can possibly save that data uselessly, using stack space for nothing. Using a macro with adresses as parameters is like using different functions for different contexts, but wasting ROM space for each macro invocation. Data stacks elegantly solve this problem; that's why, on modern CPUs, some addressing modes often uses the SP as a base address for indirect access, using efficiently the "regular stack" as a data stack too. Those CPUs are better suited for C also.

by on (#96058)
Wonder who and why would use the same function in main loop and ISR in a real NES game, or, even better, who would use a C function in an ISR in a real NES game at all.

I mean, that was said above is correct in theory and in practice for many platforms, but for given platform and application I would say having overhead of reentrance for this reason is impractical - it is better to avoid such situation and save resources.

by on (#96073)
What you say is totally correct, HOWEVER as far I know CC65 doesn't even allow to write ISRs in C...

by on (#96078)
~J-@D!~ wrote:
Let's consider a subroutine which uses fixed memory locations as parameters and/or temp space. Such a subroutine cannot be used on two different contexts (2 or more threads, or main/ISR) because the other context(s) could corrupt data used in the first context.

So let's just stop saying "recursion" and start saying "running in parallel". I personally never had the need to have two instances of the same function running in parallel when coding for the NES. Typically, the focus of the main thread and the focus of IRQs and NMIs are very different: the main thread performs complex calculations and buffers the results, IRQs and NMIs use the buffered data as quickly as possible, so they'd hardly need to make use of the same functions.

by on (#96090)
Re-entrant is a good word, since this describes both recursion and multithreading, and also implies less direct recursion where you may go a few functions deep before reusing one.

As stated the 6502 does not have stack relative addressing like the x86, so it lacks the more efficient/convenient way it is accomplished on later processors. It can still do it quite functionally, just less efficiently.

You can certainly write an interrupt routine in CC65, though there are some potential issues (if you need the virtual stack you may have to create a separate one for the IRQ "thread"; probably easier to just avoid stack usage). This probably isn't an important ability, though, as the things you tend to want to do with an IRQ on the NES usually require precise timing and/or efficiency that using C will make difficult.

Thanks to the OAM DMA for sprites, an all C NMI handler should at least be capable of refreshing all your sprites and a couple of tiles without having to know too much about how it gets compiled.

For convenience, CC65 has a --static-locals compile option which will make all local variables in the translation unit static automatically (and thus all functions are not re-entrant). Personally, I prefer to manage my variables more explicitly, but it's a convenient feature if you need it.