How cc65 works

All NES assemblers are command line programs. What does that mean? It has no graphic user interface. You don’t type code into it. You have to write your code in a separate program (Notepad++) and save it. And, then open a command prompt, and run the assembler by typing into the command prompt.

Cc65 is much more complicated, it requires several directives to be typed in, with multiple steps, so we’re not going to use the command prompt. But DON’T WORRY! I’m going to simplify it for you…we’re going to write a batch file (compile.bat) to automate the process. Once it’s written, all you should have to do is double click on compile.bat, and if all goes well, it will make your NES file. (Write the .bat file with Notepad++.)

How cc65 compiles..first, you will write your source code in C (with Notepad++). cc65 will compile it into 6502 assembly code. Then ca65 will assemble it into an object file. Then ld65 will link your object files (using a configuration file .cfg) into a completed .nes file. All these steps will be written in the batch file, so in the end, you will be double clicking on a .bat file to do all these steps for you.

I tried to make this as pain free as possible, so I “#include” all the source files into one .c file and “.include” or “.incbin” all .s files in crt0.s. The only thing you need to change in the .bat file is the “name” of the main .c file. And you just need to change the names of the included files in (name).c and crt0.s, if they change.

More about cc65…

The 6502 processor that runs the NES is an 8 bit system. It doesn’t have an easy way to access variables larger than 8 bit, so it is preferred to use ‘unsigned char’ for most variables. Addresses are 16 bit, but nearly everything else is processed 8 bit. And, the only math it knows is adding, substraction, and bit shift multiplication/division (ie. x 2 = << 1).

What this means, is you may want to write your C code very differently, due to the system limitations.

1. Most variables should be defined as unsigned char (8 bit, assumed to have a value 0-255).
2. Everything is global (or static local)
3. I also try to pass no values to functions…

The main slowdown for C code is moving things back and forth from the C stack. Local variables and passed variables use the C stack, which can be up to 5x slower. The alternative to a function argument is to store the values to temporary global variables, just before the function call. This is the kind of thing that could easily cause conflicts, so you might want to immediately put them into static local variables (which are also fast) at the top of the function.

4. Arrays, ideally, should have a max of 256 bytes.
5. Use pointers like arrays. Instead of *(ptr + 1), do ptr[1].
6. Put only one array per line of code, explanation…

So, instead of array1[x] = array2[y]; do…
temp = array2[y];
array1[x] = temp;

If these arrays are global char arrays smaller than 256, and if there’s only one on a line, it can convert it into very fast code. If you put 2 on a line, it passes the address of the right array to a temporary pointer, does some 16 bit math with the y, then it has to do the same to the second array, which takes about 5x as long.

7. use ++g instead of g++

cc65 uses “inc g” for ++g, but always uses “lda g, clc, adc #1, sta g” for the second (4x longer). So if you want to do this…
z = g++; you should instead do…
z = g;
++g;

8. cc65 can’t pass structs by value, nor return a struct.
9. don’t use arrays of structs, use structs with arrays in them.

And when compiling, use the -O directive to optimize the code. There are also i,r,s directives, which are sometimes combined as -Oirs, which can add another level of optimization. However, each of these can also introduce bugs (I haven’t encountered any bugs, so I’m still using all optimizations).

Here’s some more suggestions for cc65 coding…

http://www.cc65.org/doc/coding.html

Why are we so concerned with optimization? Because timing is very important and resources are so slim for an old 8-bit processor. And, clean unaltered C code will compile into very slow code that takes up too much of the limited memory space.

Sharing variables between files…the asm modules (.s files) can share variables/labels with each other with import, export, importzp, exportzp definitions. cc65 can access variables and arrays from asm modules by declaring a variable “extern unsigned char foo;” (and if it’s a zeropage symbol, add the line #pragma zpsym (“foo”);. When it compiles the C code, it will add an import definition for it. I hardly use “extern”. This may be the only time I mention it, except maybe…if you have a large binary file…it’s easiest to “incbin” it in the asm code like this…

.export _foo
_foo:
.incbin “foo.bin”
Then to access it from the C code, do this “extern unsigned char foo[];”. Note the underscore. For some reason, when cc65 compiles, it adds an underscore before every symbol. So, on the asm side you have to add an underscore to every exported label/variable.

You can call functions in cc65 written in ASM with a __fastcall__. This will store rightmost passed variable in the A,X registers, rather than the C stack. This doesn’t help if the function is in C, because it immediately passes it to the C stack to use it as a local variable.

What happens under the hood, when you pass a variable…
(Note, test is a global)

Test(Foo);
void Test (char A) {
test = A;
}
// one passed variable…compiles to 19 lines of code
// 20, if you count the ‘lda Foo’ just before we jumped here

_Test:

jsr pusha
ldy #$00
lda (sp),y
sta _test ; test = A;

jmp incsp1

pusha: ldy sp
beq @L1
dec sp
ldy #0
sta (sp),y
rts

@L1: dec sp+1
dec sp
sta (sp),y
rts

incsp1:

inc sp
bne @L1
inc sp+1
@L1: rts

.

// no passed variables, compiles to 3 lines of code

void Test (void) {
test = A;
}

_Test:
lda _A
sta _test
rts

It’s also possible to inline asm code into the C code. It would look like this…

asm (“Z: bit $2002”) ;
asm (“bpl Z”) ;

Another note. I’m using the standard nes.lib file that comes with cc65. Other people have been using runtime.lib, which doesn’t work if you get one from a different version of cc65. And, I’ve added

--add-source

to the command line, so recompiling will generate a .s (asm) file with c code included, in case you want to look at the generated asm code. And, it generates a labels.txt file, for debugging.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s