Rudimentary vCPU support for the lcc C compiler

Using, learning, programming and modding the Gigatron and anything related.
Forum rules
Be nice.
pgavlin
Posts: 7
Joined: 22 Apr 2019, 19:38

Rudimentary vCPU support for the lcc C compiler

Post by pgavlin » 22 Apr 2019, 19:42

Hi all,

I'm happy to report that I have a very rough cut at a vCPU backend for the `lcc` C compiler. I chose this compiler for retargeting because of its simplicity and the clarity of the interface between its front- and back-ends. Though I'm certain that there are many, many sharp edges, I have been able to build and run several small programs. Here are a couple of videos of programs I've compiled:

- A port of the "dots" demo: https://www.youtube.com/watch?v=rYGS7Zpkm1c
- An implementation of Conway's Game of Life: https://www.youtube.com/watch?v=avnhPTUZ5E8

Apologies for the low resolution; Youtube won't seem to allow me to upload at the original quality.

The source code for the compiler is here: https://github.com/pgavlin/lcc/tree/pgavlin/gt1

The vCPU backend is mostly contained in https://raw.githubusercontent.com/pgavl ... src/gt1.md. The assembler/linker and runtime may also be of interest: https://github.com/pgavlin/lcc/blob/pgavlin/gt1/asm.py https://github.com/pgavlin/lcc/blob/pgavlin/gt1/rt.py

Needless to say that authoring software in C is not terribly practical on a 32k system: the fragmented memory map limits the amount of stack that is easily available and essentially precludes a heap. I will probably code something up for a 64k memory model that is a bit more capable.

I'm slowly working on putting together a write-up of the design of the compiler, the retargeting process, and the challenges unique to generating code for the vCPU. In the meantime, I'm happy to answer any questions you all have.

Happy hacking! :)

pgavlin
Posts: 7
Joined: 22 Apr 2019, 19:38

Re: Rudimentary vCPU support for the lcc C compiler

Post by pgavlin » 22 Apr 2019, 19:50

Here is a non-exhaustive list of things that do not work as of yet:
- Multiplication, division, modulus, arbitrary-width left and right shifts, and sign extension all require runtime helpers that I haven't yet written
- Comparisons are probably broken with respect to overflow
- Floating point support is fully unimplemented

So it helps to stick to the basics ;)

The compiler should build on any *nix system. Once it's built, it can be invoked from the project root directory like so:

Code: Select all

LCCDIR=$(pwd)/build ./build/lcc [source file] -o [gt1 file]

User avatar
marcelk
Posts: 329
Joined: 13 May 2018, 08:26

Re: Rudimentary vCPU support for the lcc C compiler

Post by marcelk » 22 Apr 2019, 19:53

Congratulations! Just wow! :o It's the first compiled high level language for the system!

This looks like a gift from heaven. I see this can be of enormous help after we have SD card block reading and writing working over the SPI interface: that is to speed up the porting of a FAT library to the Gigatron to help it enter the next phase.

My question: are the local C variables that I see in the videos indeed stack variables? That is: using the ALLOC/STLW/LDLW instructions?

pgavlin
Posts: 7
Joined: 22 Apr 2019, 19:38

Re: Rudimentary vCPU support for the lcc C compiler

Post by pgavlin » 22 Apr 2019, 19:58

> My question: are the local C variables that I see in the videos indeed stack variables? That is: using the ALLOC/STLW/LDLW instructions?

They are not--most of them are allocated to "registers" in the user vars area of the zero page. As it stands, the compiler does not emit the alloc/stlw/ldlw instructions due to the limited space available for the vCPU stack. This does make certain operations more expensive: for example, loading and storing to locals that do not end up in the virtual registers requires a helper call:

Code: Select all

# write the value in vAC to the frame local at $offset from the top of the parameter stack
stw ha
ldi $offset
call stloc

# load the value of the frame local at $offset from the top of the parameter stack
ldi $offset
call ldloc
Implicit in the above is that the C runtime uses its own parameter stack. It would be possible to optimize this a bit and use ALLOC/STLW/LDLW for functions with a small number of frame-based locals.

On the topic of variables: struct-typed variables are unlikely to work at the moment. I haven't tried using them yet, so I'm really not sure if I've missed something there.

User avatar
marcelk
Posts: 329
Joined: 13 May 2018, 08:26

Re: Rudimentary vCPU support for the lcc C compiler

Post by marcelk » 22 Apr 2019, 20:10

Ok I understand. vCPU isn't really designed with C programs in mind, so many things will never be efficient. But efficiency doesn't matter, because this connects the Gigatron to another domain people are familiar with, and that's what makes it great.

FYI: Our port of Tiny BASIC does proper comparisons of 16-bit signed integers in IF-THEN statements, but not in many other places (such as in FOR-TO-NEXT loops). There's an idiom for it. Tiny BASIC also has multiplication and division/modulo functions that you can steal.

pgavlin
Posts: 7
Joined: 22 Apr 2019, 19:38

Re: Rudimentary vCPU support for the lcc C compiler

Post by pgavlin » 22 Apr 2019, 20:14

> Ok I understand. vCPU isn't really designed with C programs in mind, so many things will never be efficient. But efficiency doesn't matter, because this connects the Gigatron to another domain people are familiar with, and that's what makes it great.

My feelings exactly :)

For a practical example of the parameter stack, here is a little demo that fills the screen with pseudorandom data:

Code: Select all

#define vram ((unsigned char*)(void*)0x0800)
#define _rand ((unsigned char*)(void*)0x0006)
#define giga_xres 160
#define giga_yres 120

static unsigned rr = 5831;
void srand(unsigned seed) {
	rr = seed;
}

unsigned rand() {
	unsigned r = ((rr << 8) + (rr << 1)) ^ (rr + 1);
	rr = r;
	return r;
}

void main() {
	unsigned char x, y, a;
	unsigned char *l0, *l1, *l2;

	srand(*_rand + 5831);

	for (y = 0; y < giga_yres; ++y) {
		l0 = &vram[y << 8];
		for (x = 0; x < giga_xres; ++x, ++l0) {
			*l0 = rand() >> 8;
		}
	}
}
If we look at the generated code for `srand`, here is what we see (the output of the code generator is Python code that is processed by the assembler+linker in the repo linked in the original post):

Code: Select all

# Declare a new function/global label
asm.defun('_srand')
# Save LR
asm.push()
# Save any virtual registers used by this function
asm.ldwi(0x80)
asm.call('enter')
# Load the address of the `rr` static variable and store it into a vreg (`r7`, in this case)
asm.ldwi('_rr')
asm.stw('r7')
# Load the frame variable at offset 4 from the current value of the parameter stack. In this function, the value of `seed` is stored at that offset.
# The frame is laid out like so:
#
# +-------------------------------+
# | Incoming arguments            |
# +-------------------------------+ <- original sp
# | Saved registers               | <- saversize bytes
# +-------------------------------+
# | Locals                        | <- framesize bytes
# +-------------------------------+ <- sp
#
# - Args end at sp + framesize + saversize
# - Frame locals end at sp
asm.ldi(4)
asm.call('ldloc')
# Store the value of `seed` to `rr`
asm.doke('r7')
asm.label('.L1')
# Restore any virtual registers saved in the function prologue
asm.ldwi(0x200)
asm.call('leave')
# Clean the parameter stack. We have a single parameter, so we need to add two bytes.
asm.ldi(2)
asm.addw('sp')
asm.stw('sp')
# Restore LR and return
asm.pop()
asm.ret()

User avatar
marcelk
Posts: 329
Joined: 13 May 2018, 08:26

Re: Rudimentary vCPU support for the lcc C compiler

Post by marcelk » 22 Apr 2019, 23:26

I have got it working, but with some tricks. I have no idea if any of the stuff below is how it should be done, but I get a GT1 file out... I did this on an older Raspberry Pi with an old installation of Raspbian.

Downloading and building LCC:

Code: Select all

$ git clone https://github.com/pgavlin/lcc
$ cd lcc
$ git checkout -b gt1 remotes/origin/pgavlin/gt1
$ export BUILDDIR=build
$ mkdir build
$ export LCCDIR=`pwd`/build
$ export HOSTFILE=etc/gt1h.c 
$ ln -s /usr/lib/gcc/x86_64-linux-gnu/4.9.1 build/gcc
open makefile in text editor
find the line with CFLAGS=-g, then append the option "-std=c99"
$ make all
Compiling a Gigatron program:

Code: Select all

copy the demo program from
https://forum.gigatron.io/viewtopic.php?p=630#p630
to demo.c

$ build/lcc demo.c -o demo.gt1

Traceback (most recent call last):
  File "/home/marcelk/lcc/build/gtlink.py", line 3, in <module>
    import argparse, asm, os, sys
  File "/home/marcelk/lcc/build/asm.py", line 64
    print(f'writing segment {self.address:x}:{self.pc():x}', file=log.f)
                                                          ^
SyntaxError: invalid syntax
Here's where I'm half stuck, because none of my Ubuntu or Raspbian systems are able to upgrade python3 to version 3.6 or up (which I believe is needed for these format strings). My workaround is to replace all instances of print(f' with print(' in build/asm.py.

And then it really works, see evidence below. Amazing! (Ok, the periodicity of rand() in demo.c isn't always very good. It varies from run to run, and the one below happens to be a bad one)

Screenshot 2019-04-23 at 01.19.25.png
Proof it works
Screenshot 2019-04-23 at 01.19.25.png (223.57 KiB) Viewed 2102 times
Attachments
demo.gt1
Compiler output
(375 Bytes) Downloaded 70 times
demo.c
Demo C program
(520 Bytes) Downloaded 77 times

User avatar
marcelk
Posts: 329
Joined: 13 May 2018, 08:26

Re: Rudimentary vCPU support for the lcc C compiler

Post by marcelk » 23 Apr 2019, 00:02

Indeed, there is no heap. There is no malloc. There is no standard lib. There is no non-standard lib either. There is nothing.

Wait: there is 1 function in memory at 0x1f0: vReset(). The closest we have to a system description is interface.json, with locations and names of the standardised items. This can be converted to "interface.h" so we have at least one include file!

pgavlin
Posts: 7
Joined: 22 Apr 2019, 19:38

Re: Rudimentary vCPU support for the lcc C compiler

Post by pgavlin » 23 Apr 2019, 22:17

After a bit of afternoon hacking to support looking up values from ROM, we can now compile and run a simple "Hello, world!" program: https://youtu.be/9-zz2MjGwN0

Code: Select all

#define Sys_Draw4_30 0x04d4
#define SYS_VDrawBits_134 0x04e1
#define sysfn ((unsigned*)(void*)0x0022u)
#define sysargs ((unsigned char*)(void*)0x0024u)
#define sysargsw ((unsigned*)(void*)0x0024u)
#define giga_xres 160
#define giga_yres 120

#define font32up ((unsigned char*)(void*)0x0700)
#define font82up ((unsigned char*)(void*)0x0800)

#define vram ((unsigned char*)(void*)0x0800)
#define rand ((unsigned char*)(void*)0x0006)
#define xbounds 0x9f
#define ybounds 0x77

void cls() {
	unsigned ii, jj;
	unsigned char aa, bb;

	*sysfn = Sys_Draw4_30;
	sysargsw[0] = 0;
	sysargsw[1] = 0;

	aa = 0x08, bb = 0x7f;
	jj = giga_yres / 2;
	do {
		sysargs[4] = 0;
		ii = giga_xres / 4;
		do {
			sysargs[5] = aa;
			__syscall(0xff);

			sysargs[5] = bb;
			__syscall(0xff);

			sysargs[4] += 4;
		} while (--ii, ii > 0);

		aa++, bb--;
	} while (--jj, jj > 0);
}

static unsigned char* pos;

void putc(unsigned char c) {
	unsigned char* bitmap;
	unsigned i;

	i = c - 32;
	if (i < 50) {
		bitmap = font32up;
	} else {
		i -= 50;
		bitmap = font82up;
	}
	bitmap = &bitmap[(i << 2) + i];

	sysargs[0] = 0x3f;
	sysargsw[2] = (unsigned)(void*)pos;
	*sysfn = SYS_VDrawBits_134;

	for (i = 5; i > 0; --i, bitmap++) {
		sysargs[2] = __lookup(0, bitmap) ^ 0xff;
		__syscall(203);
		sysargs[4]++;
	}

	pos += 6;
}

void puts(char* s) {
	unsigned char* ss = (unsigned char*)s;
	for (; *ss != '\0'; ss++) {
		putc(*ss);
	}
}

void main() {
	cls();

	pos = vram;

	puts("Hello, world!");
}

pgavlin
Posts: 7
Joined: 22 Apr 2019, 19:38

Re: Rudimentary vCPU support for the lcc C compiler

Post by pgavlin » 27 Apr 2019, 18:40

With several more fixes, we now have a much more interesting "Hello, world!" program.

Here is the GT1 emulating a GT1: https://gist.github.com/pgavlin/c97f2fd ... 91bfdf5f5b

I haven't uploaded a video for this one, but the speed is surprisingly respectable. Sure, you can watch the characters draw, but I honestly expected it to be quite a bit more agonizing than it is.

I've attached the .gt1 file for anybody who would like to try running it.
Attachments
gtemu.gt1
(2.12 KiB) Downloaded 75 times

Post Reply