Friday 18 June 2021

6502 : CC65 and C Programming

Until now I have used VASM to assemble my 6502 programs and it has done the job well.  I have noticed that there are a few 6502 C compilers and I recall from my bare metal programming that you (only!) need a stack, a library and some initialisation code for a C environment.  There appears to be a WDC compiler which isn't well used and CC65 which is popular and practical.  As a first step I will move from using VASM assembly to CA65, which is a part of the CC65 program suite.

CA65

As CC65 is available as a debian package and I use Windows 10 for my 6502 development environment I installed it under WSL (Windows Subsystem for Linux).  I can now take assembler source programs, which are written in Notepad++ and copy the to directories I use for WSL.  Assembly with CA65 starts with running ca65 at the command line with a few options specified.  In particular we have to specify --type as "none"; CA65 is happy to assemble for various microcomputers (C64, Apple ][, etc) but my home brew system has no extra requirements.  The output listing shows that it creates the same output byte for byte as VASM.  The output file is formatted for linking modules and libraries so although I have nothing to link in I also need to run the linker LD65.  So I replace my vasm command with:
    ca65 -l map -t none testbed.s    and
    ld65 -o testbed.exe -t none testbed.o
Now I can process and load the program via the Arduino Mega as previously.

CC65

Creating a C environment is somewhat more challenging.  Luckily Dirk Grappendorf has set this up for his 6502 which is very similar to mine and has been kind enough to create a github repo containing the code.  I downloaded the repo and set about making my own minimal code base.  My target is a "hello, world" program which outputs a screen message.


There are very few files in the solution:

cc65.lib is the most significant, it is the library of C functions needed for stdio and stdlib.

firmware.cfg defines the different areas of memory in the computer, ROM, RAM layout is specified so the compiler can do the hard work in placing files.

startup.s is the initialisation code which sets up the stack, initialises memory and calls main().

zeropage.s provides the layout for variables in the zero page.

acia.s contains the serial programs init, getc, gets, putc, puts which we will use for screen output.

interrupt.s contains skeleton interrupt processing code (which I am not yet using)

io.inc65 defines a long list of constants for ACIA, VIA and other devices.

Once I edited firmware.cfg for my memory configuration, io.inc65 for my acia addresses and acia.s for my serial port config (19200 baud) I could compile and link the program with a single command:
   cl65 -C firmware.cfg -m firmware.map -o main.exe main.c acia.s zeropage.s startup.s  interrupt.s cc65.lib

This created a 32KB file which can be burned into my ROM.  In fact I use my Arduino Mega to write ROM so I don't want the entire 32KB file.  My program is less than 1KB in size.  I amended firmware.cfg to "pretend" I only had 2KB ROM, which created a much more manageable file.  Amazingly, thanks mainly to Dirk, my C environment works and without too much trouble I can read and write the screen, here is the first test program. 😊😊😊😊😊


C lib

So far we haven't used any C library functions, our example uses Dirks acia_init,acia_getc, and acia_putc for IO.  In fact I can remove include statments for <stdio.h> and <stdlib.h> without issue.  Of course C programs without library functions are not particularly useful, we really want to have them available to us.  I tried adding the abs() function from stdlib.h and it worked fine.  I also successfully tested strcpy() from <string.h>.  

<stdio.h> is more difficult to test; I haven't "linked" my I/O routines into C streams yet and my hardware/software doesn't have interrupts setup so I wouldn't expect I/O to work.  I did hope that I could use the sprintf() function to format output for me and wrote a test program but it didn't work.  I will look into the problem in more detail when I consider cc65.lib.  In fact cc65.lib was provided by Dirk I need to look at how he created it or derived it from cc65 provided libraries.

My mechanism for loading programs into ROM  using an Arduino Mega sketch is wonderful, it is automated and requires no removal/insertion of chips.  Using C and library functions necessarily means that our programs are increasing in size - programs may be 4KB or even more!  The Arduino environment only has 8KB for variables so I had to use the PROGMEM mechanism which directs the compiler to store nominated constants in ROM only.  A 4KB program takes over a minute to load which is not bad really but I will need to look at speeding up the sketch.

SIM65

CC65 also contains a simulator SIM65, which you can use to test your C programs. It is easiest to use with programs containing standard C library functions (printf etc).  You can add in assembly language subroutines from C.

Of course, without the real hardware, low level "driver" functions don't have much meaning but you can "simulate" them.  For example my "term_puts" assembly routine could be replace by one which calls printf.
In fact most or all emulators have this problem, they can't exactly replicate my hardware.  If I aim to design my hardware / software to be similar to Apple ][, BBC Micro, C64 etc I could use their full emulators to test programs but even then my hardware will be quite different from the original product.

I am not sure if I will use this but it is another great tool to have available.



No comments:

Post a Comment