The Rhinestone Compiler

About the Compiler

The Rhinestone Compiler is the current version of LCC1802 - an Ansi C compiler for the RCA/Intersil 1802 microprocessor. It is a feature-complete compiler supporting 16 and 32 bit integers and 32 bit floating point.

LCC1802 is based on the LCC retargetable C compiler by Chris Fraser and David Hanson. The copyright notice is included in the zip file. LCC is available at:

LCC was built to be quick to adapt for a new target machine. In addition to the targets supplied with LCC, the Compiler supports -target=xr18DH to output code for the 1802. This is the default for the Rhinestone Compiler. The customization for the 1802 instructions is almost all contained in a single "machine description" file xr18DH.md on the downloads page. There are several examples on the web of lcc machine descriptions - the one I had the most luck adapting is described at:

For testing purposes I have copied the assembler modules and support programs into the \bin directory in the zip. This is a bit scungy but It was all i could think of for the test distribution. Apologies to the author and I encourage you to download the whole thing from the link above.

LCC1802 was developed by Bill Rowe between November 2012 and October 2013 and is freely available for use or modification consistent with the above notices.

Obtaining the Compiler

The zip file contains a single directory: lcc42\ with three sub-directories: \bin, \include and \examples. No installation is needed but the unzipped file has to be c:\lcc42\bin etc. The \bin directory contains the compiler and assembler modules and the \examples directory contains a few sample programs each in its own sub-directory such as lcc42\examples\blink\blink.c. The \include directory contains include files for C and assembler programs. The binaries were compiled with Microsoft Visual C++ Express 2010. There may be a required runtime module but i have run it on systems that don't have vcc and I did not see a problem. If you need it, the runtime package is at:

Running The Compiler

Inside each example directory are is a windows .bat file to run the compiler and assembler. Blink is the simplest example and the one most likely to run on an arbitrary system. It just toggles the Q line off and on once a second. From a command prompt in the lcc42\examples\blink directory:

lcc blink.c

runs the compiler and assembler with the result going into file a.hex where you can load it into your 1802 or emulator.

To change the name of the output file, use "-o" for example

lcc blink.c -o blink.hex

To see the generated assembler, use "-S" for example:

lcc blink.c -S

generates blink.asm with the compiler output in it (you don't need -o). To run the assembler, use:

lcc blink.asm

lcc has a variety of other options that you can see on the man page linked to above and play with. It comes with support for intel processors (-target=x86) but there's a separate product called lcc-win32 with commercial quality windows output.

The compiler makes some effort to be compatible with general 1802 conventions. It uses R2 through R6 in the usual ways and is careful about the stack. The command line switch -volatile tells the compiler that generated code should not use registers 0&1 so that interrupts can be handled. There is still a problem with the 1861 video though since the generated code is peppered with long branches.

Limitations and oddities;

Syntactically, it will take anything you throw at it, if it's legal C it will compile it, if it's not you'll get an error message.

I don't know if anyone else will notice this but static variables are initialized only when the program is loaded for the first time - if you re-start it the last value carries over. Char variables are stored as 8 bits but whenever they're loaded or moved, they are sign extended to the full register.

Some errors only show up at the assembler stage - especially invoking a function with the wrong name. The compiler routinely deletes the assembler file and listing so you have to note what line number the error is on then rerun the compile to get the assembler input with -S. To see the assembler listing, compile with -S then run that through the compiler with "lcc blink.asm" for example.

The floating point support routines take up about 2k. They are not included unless you use floating point BUT, if you #include nstdlib.c you ARE using floating point. If you put "#define nofloats 1" anywhere in your code before including nstdlib you can avoid the overhead (of course if you've actually used floats you will have problems).

I/O Routines

The only I/O available is through assembly language routines. Parameters to a routine are passed in R12 and R13 and the output is returned in R15.

IO1802.inc contains _putc(char) an assembler routine that sends a single character via OUT 5 which, on my olduino, goes to the PC serial port. It also contains _inp(unsigned char port) and _out(unsigned char port, unsigned char value which take parameters for port and value and(for inp) return a character. You can replace include\putc.inc with your own routines for input and output. A routine called _name is seen as name by a C program.

;IO1802.inc contains input/output runtime routines for LCC1802

;The port is in regArg1, the output byte is in regArg2

align 64

_putc:

_out5:

gloregArg1

decsp

strsp

out5

Cretn

_inp:;raw port input

;stores a small tailored program on the stack and executes it

decsp;work backwards

ldi0xD3;return instruction

stxd

gloregarg1;get the port number

ani0x07;clean it

bz+; inp(0) isn't valid

ori0x68;make it an input instruction

stxd;store it for execution

cpy2rt1,sp;rt1&sp now point to an empty byte to be overwritten by the inp instruction

incrt1;rt1 points to the 6x instruction

seprt1;execute it

;we will come back to here with the input byte in D

incsp;step over the work area

ploretVal;save it to return

ldi0

phiretval;clear top byte

+incsp;need to get rid of the 6x instruction

incsp;and the D3

Cretn;and we're done

_out:;raw port output

;stores a small tailored program on the stack and executes it

decsp;work backwards

ldi0xD3;return instruction

stxd

cpy2rt1,sp;rt1 will point to the inp instruction

gloregarg1;get the port number

ani0x07;clean it

ori0x60;make it an out instruction - 60 is harmless

stxd;store it for execution

gloregarg2;get the byte to be written

strsp;store it where sp points

seprt1;execute it

;we will come back to here with sp stepped up by one

+incsp;need to get rid of the 6x instruction

incsp;and the D3

Cretn;and we're done

In your routines you can use R8-R11 freely. R12-13 are for passing parameters to subroutines, R15 is where your return value goes, R6 is the return address used by scrt - don't muck with it. It would be a good idea to use the compilers symbolics for the registers: rt1 and rt2 for temps regArg1, regArg2, retVal and retAddr because they may change.

Changes In This Edition

The Rhinestone Compiler was optimized using the Dhrystone.c benchmark code as a target. There are changes to speed up strcpy, strcmp, multiplication and division along with some optimizations to 1802 code generation - mostly included via the peephole optimizer file lcc1802.opt in the include directory. The peephole optimizer only runs if the -O option is included. A new file, nstdlib.inc in the include directory has assembly versions of the strcmp/strcpy routines - it's automatically included by nstdlib.c. The mulu2 and divu2 routines in the epilog were extensively reworked.