Universal Cross Assembler Redux
One disadvantage of rolling your own CPUs is that you have to make all your own tools. True, some people emulate existing instruction set architectures and then leech off the tools for the processor they are emulating. But what fun is that?
As part of my "One Instruction Wonder" articles here in DDJ, I published an article about my Universal Cross Assembler. It is a hack to be sure and it certainly works best under Linux or at least something like Cygwin since it depends on a hodgepodge of Unix-likek tools.
The principle is simple. An awk script transforms an assembler input file so that it looks like a bunch of C macro invocations. Then the system C compiler generates a program by combining these macro calls with some library code. The resulting program emits the object code. A shell script orchestrates the whole process. Once you have everything working, defining a new assembler language takes just a few minutes.
Case in point, I recently built an Altair 8800 replica (I'll talk about that more in the future) and I found I wanted an 8080 assembler. Of course, one answer was to fire up SIMH and emulate a CP/M system and use that. Authentic, but annoying. And yeah, there are plenty of cross assemblers out there like TASM that have 8080 definitions. But I decided it would be easy to retarget my assembler. The actual retarget took about 30 minutes -- a few more when I discovered some typos. However, I also wanted to add an 8 bit raw binary dump to the code. That required a short patch to the shell script driver, and the soloasm.c code. That was another 15 or 20 minutes, but really doesn't count against the total time since it was a general enhancement and not absolutely necessary.
The Altair switches are amenable to octal so I decided that dumping binary and using the od program to convert to octal would be nicer than trying to add a little used octal output mode to the assembler. So the invocation looks like:
axasm -p i8080 -8 test.asm | od -b > test.octal.txt
And a part of the definition file:
#define NOP DB(0) #define MOV(d,s) DB(0x40+(((d)&7)<<3)+((s)&7)) #define MVI(d,i) DB(6+(((d)&7)<<3)); DB(i)
Granted, an 8080 assembler probably isn't useful to most people, but I thought it was a good example of how flexible the assembler is. I currently have it targeting the 8080, the PIC16F84, the RCA1802, and two custom CPUs of my own. I don't think any of the retargets took more than an hour. In fact, I'm not sure any of them took anywhere near that.