Porting RL to different Flight Computers


OK - so I sell a flight computer - but I'm not in it for the money - my goal is to get everyone writing and sharing rocketry related software - creating RL is just another step along that path. One of the main purposes behind creating RL is to produce something that allows people to use a highish level language that's also portable between platforms - obviously I'm not going to realize that goal unless someone actually ports it. To this end here's a document on how to port RL to a new microcontroller.

If you sell a flight computer and you want advice about porting RL to it please get in touch with me at paul@taniwha.com.


The RL compiler makes .x files as output - these are byte-code files containing interpretable instructions - they are encoded in an ascii format suitable for downloading as is (it's a slightly modified form of Intel Hex format described below).

Porting a byte code interpreter

This is the easiest way to port stuff - it's also the only way if you have a microcontroller which only provides access for instruction fetch to ROMs.

You could also port the interpreter to execute code out of EEPROM (although it might be slow) so long as your machine has enough SRAM in it for global variables and stack.

The file in the standard source distribution sim.c is a C compilable loader and interpreter interpreter for the .x files - if you have a C compiler for your machine you are more than half way there (otherwise you may need to translate the interpreter it into assembly code). You will need to mek some changes:

You can now either load the interpreter into ROM, or RAM along with the thing to interpret

Translating byte code to you native CPU

This is what I've done for my flight computer. The file in the source distribution called rcvt.c is a translator - it reads the .x file, translates each byte code and outputs the results to an assembly source file for later assembly into a binary program.

In my case I translated the code into some somewhat threaded code - many of the primitives are calls to subroutines - this is mainly to save space.

Feel free to copy as much of this code as is usefull to you.

Machine Model

The computer model for .x files is very simple. It assumes a linear memory, code/data can be loaded anywhere (it's PIC). Addresses are assumed to be 2 bytes (16 bits). All data is stored 'little-endian' (least significant bytes first) - although a 'big-endian' machine would probably work OK as long at it was consistant. Code and data is loaded at the lowest locations in memory, stacks grow down from the top and the chunk in the middle is used by the logging software.

The byte code machine has a number of 'registers' (they don't have to be mapped to real registers although the first 4 are really good candidates for doing so):

At the beginning of the program the PC is set to the first byte code, the GP and SSP are initialised from the file header (see below) and the SP is set to the top of memory. All other registers are undefined.

RL Byte code definition

Here are the definitions of the byte codes in the instruction stream:

Notes	Opcode		value	description

	P_HALT		0	halt
cu2	P_UBYTE_L	1	load a local unsigned byte
				the byte at (FP+N2) is 0 extended into TOS
				TOS = *(unsigned char *)(FP+N2)
cu2	P_UBYTE_G	2	load a global unsigned byte
				the byte at (GP+N2) is 0 extended into TOS
				TOS = *(unsigned char *)(GP+N2)
wb	P_J_NE		3	jump if TOS ne 0 to PC+N2+1
wb	P_J_EQ		4	jump if TOS eq 0 to PC+N2+1
w	P_GE		5	TOS = POP() >= TOS
w	P_GT		6	TOS = POP() > TOS
w	P_LE		7	TOS = POP() <= TOS
w	P_LT		8	TOS = POP() < TOS
w	P_NE		9	TOS = POP() != TOS
w	P_EQ		10	TOS = POP() == TOS
w	P_NOT		11	TOS = !TOS
w	P_SUB		12	TOS = POP() - TOS
w	P_ADD		13	TOS = POP() + TOS
w	P_MUL		14	TOS = POP() * TOS (signed)
w	P_DIV		15	TOS = POP() / TOS (signed)
w	P_MOD		16	TOS = POP() % TOS - undefined TOS is 0
w	P_OR		17	TOS = POP() | TOS
w	P_XOR		18	TOS = POP() ^ TOS
w	P_AND		19	TOS = POP() & TOS
w	P_SHL		20	TOS = POP() << TOS
w	P_SHR		21	TOS = POP() >> TOS
b	P_JMP		22	jump unconditionally to PC+N2+1
w	P_COMP		23	TOS = ~TOS
bw	P_CALL		24	call subroutine 
				TOS = PC+3
				PC = PC+N2+1
w	P_CALLI		25	call-indirect subroutine (from TOS - TOS is popped)
				tmp = TOS
				TOS = PC+3
				PC = tmp
2w	P_COPYN		26	N+1 words are popped from TOS, old TOS is pushed back on
				SP += N2
2w	P_CUTN		27	N words are popped from TOS
				SP += N2
2w	P_CONST		28	TOS gets a constant value	
				TOS = N2
2w	P_ADDR_L	29	TOS gets the address of a local value  (2 byte offset)
				TOS = FP+N2
2w	P_ADDR_G	30	TOS gets the address of a global value  (2 byte offset)
				TOS = GP+N2
2c	P_BYTE_L	31	TOS gets a local byte value  (2 byte offset) [sign extended]
				TOS = *(char *)(FP+N2)
2c	P_BYTE_G	32	TOS gets a global byte value  (2 byte offset) [sign extended]
				TOS = *(char *)(GP+N2)
2w	P_WORD_L	33	TOS gets a local word value  (2 byte offset)
				TOS = *(short *)(FP+N2)
2w	P_WORD_G	34	TOS gets a global word value  (2 byte offset)
				TOS = *(short *)(GP+N2)
2c	P_SBYTE_L	35	store a local byte value  (2 byte offset)
				*(char *)(FP+N2) = TOS
2c	P_SBYTE_G	36	store a global byte value  (2 byte offset)
				*(char *)(GP+N2) = TOS
2w	P_SWORD_L	37	store a local word value  (2 byte offset)
				*(short *)(FP+N2) = TOS
2w	P_SWORD_G	38	store a global word value  (2 byte offset)
				*(short *)(GP+N2) = TOS
c2	P_LOAD_B	39	tos is replaced by byte from indirection of old tos value [sign extended]
				TOS = *(char *)(TOS+N2)
w2	P_LOAD_W	40	tos is replaced by word from indirection of old tos value
				TOS = *(short *)(TOS+N2)
w	P_DUP		41	push top of stack TOS is duplicated onto stack
c2	P_STORE_B	42	*tos = tos-1 (byte) tos/tos-1 are discarded
				*(char *)(tos+N2) = POP()
c2	P_STORE_W	43	*tos = tos-1 (word) tos/tos-1 are discarded
				*(word *)(tos+N2) = POP()
wb	P_ADDR_P	44	TOS gets address of a code address (relative) (2 byte offset)
				TOS = PC+N2+1
w	P_ZERO		45	TOS gets 0
				TOS = 0
w	P_ONE		46	TOS gets 1
				TOS = 1
w2	P_ENTER		47	allocates space for local variables (subtracts sp by 2 byte offset)
				if (N2 != 0xffff) {
					SP -= N2;
					FP = SP;
w2	P_RET		48	recovers space for local variables  and returns from subroutine (subtracts sp by 2 byte offset)
				if (N2 != 0xffff) {
					SP += N2;
					FP = POP();
				PC = POP();
w2	P_ADDR_S	49	push the address of a string (2 byte offset)
				TOS = SSP+N2
	P_POLL		50	call into environment for busy work - this is the core of the
				system support - see below for more info
w	P_INTERRUPT	51	push true if interrupt N has occured (and clear the flag)(2 byte number)
				if (INT[N2]) {
				} else {
					TOS = 0;
ul	P_SAVE_TIME	52	saves current time
l4	P_CMP_TIME	53	pushes true if current time is N > than the last saved one (2 byte constant)
				if (TIME_FLAG && TIME >= (SAVED_TIME+N4)) {	// unsigned long math here
					TOS = 1;
					TIME_FLAG = 0;
				} else {
					TOS = 0;
w	P_INPUT		54	pushes false if no input char available, and the char true it it is
				if (input_available) {
					TOS = getchar();
				} else {
					TOS = 0;
w	P_OUTPUT	55	pushes truth value - true if we can send a character without stalling
				TOS = output_possible
w	P_LAUNCH	56	pushes truth value - true if we have launched
					TOS = 1;
					LAUNCH_FOUND = 1;
				} else {
					TOS = 0;
w	P_ARM		57	TOS is passed to the arming mechanism
w	P_SAFE		58	TOS is passed to the safing mechanism
w	P_FIRE		59	TOS is passed to activate a pyro channel
ul	P_TIME		60	pushes absolute time
				TOS = TIME
ul	P_LTIME		61	pushes time since launch event
w	P_SET_LOG_FULL	62	pops TOS is log full value
wb	P_SET_POLL	63	pops TOS is log address value
				LOG_POLL = PC+N2+1
w	P_BEEP		64	TOS passed to control the beeper (if any)
w	P_PSTR		65	TOS used as the address of a string to print
				printf("%s", TOS);
w	P_PHEX		66	TOS used as a hex value to print
				printf("%04x", TOS);
l	P_PVAL		67	TOS used as a decimal value to print
				printf("%d", TOS);
w	P_PCHR		68	TOS used as a character value to print
w	P_EESAVE	69	3 TOS values saves data to eeprom tos is eeprom address, tos-1 main mem address tos-2 is count
				do_eesave(TOS, POP(), POP())
w	P_EELOAD	70	3 TOS values loads data from eeprom tos is main mem address, tos-1 eeprom address tos-2 is count
				do_eeload(TOS, POP(), POP())
w	P_GET		71	get value from external source (tos gives value and is replaced with old one)
				TOS = get(TOS);
w	P_SET		72	set value to external source (tos is the value tos-1 points to the source, both are removed)
				set(TOS, POP());
w	P_POP		73	discard TOS
				TOS = POP()
l	P_GE_L		74	TOS = POP() >= TOS
l	P_GT_L		75	TOS = POP() <= TOS
l	P_LE_L		76	TOS = POP() > TOS
l	P_LT_L		77	TOS = POP() <= TOS
l	P_NE_L		78	TOS = POP() < TOS
l	P_EQ_L		79	TOS = POP() == TOS
l	P_NOT_L		80	TOS = POP() != TOS
l	P_SUB_L		81	TOS = POP() - TOS
l	P_ADD_L		82	TOS = POP() + TOS
l	P_MUL_L		83	TOS = POP() * TOS
l	P_DIV_L		84	TOS = POP() / TOS
l	P_MOD_L		85	TOS = POP() % TOS
l	P_OR_L		86	TOS = POP() | TOS
l	P_XOR_L		87	TOS = POP() ^ TOS
l	P_AND_L		88	TOS = POP() & TOS
l	P_SHL_L		89	TOS = POP() << TOS
l	P_SHR_L		90	TOS = POP() >> TOS
l	P_COMP_L	91	TOS = ~TOS
l4	P_CONST_L	92	TOS = N4
l2	P_LONG_L	93	load long local
				TOS = *(long *)(FP+N2)
l2	P_LONG_G	94	load long global
				TOS = *(long *)(GP+N2)
l2	P_SLONG_L	95	store long local
				*(long *)(FP+N2) = TOS
l2	P_SLONG_G	96	store long global
				*(long *)(GP+N2) = TOS
l2	P_LOAD_L	97	load long
				TOS = *(long *)(TOS+N2)
l	P_DUP_L		98	push a long
l2	P_STORE_L	99	store long
				*(long *)(TOS+N2) = POP()
l	P_ZERO_L	100	load long 0
				TOS = 0;
l	P_ONE_L		101	load long 1
				TOS = 1
l	P_WIDEN		102	widen signed from short to long
				TOS = TOS&0xffff|(TOS&0x8000?0xffff0000:0)
w	P_SHORTEN	103	shorten from long to short
				TOS = TOS&0xffff
l	P_PVALU		104	TOS used as an unsigned decimal value to print
				printf("%u", TOS);
l	P_PHEXL		105	print a long hex value from TOS
				printf("%08x", TOS)
l	P_POP_L		106	discard the long TOS
				TOS = POP()
w2	P_EQ_C		107	test for equality
				TOS = TOS == N2
l4	P_EQ_L_C	108	test for equality (LONG)
				TOS = TOS == N4
w2	P_ADD_C		109	add constant
				TOS = TOS + N2
l4	P_ADD_L_C	110	add constant long
				TOS = TOS + N4
w	P_INDEX2	111	index by 2
				TOS = TOS<<1
w	P_INDEX4	112	index by 4
				TOS = TOS<<2
w	P_LSHORTEN	113	preserve a long logical value in a 16-bit 
				TOS = TOS|(TOS>>16)
ul	P_RTIME		114	get real time
w	P_LOG_FULL	115	get the log full state
w	P_LOG_BASE	116	get the log base address (see below)
w	P_LOG_END	117	get the log end address (see below)	
wbX	P_JEQV		118	jump if TOS == constant
				if (TOS == XN2)
					PC += NN2
lbY	P_JEQV_L	119	jump if TOS == constant
				if (TOS == XN4)
                                        PC += NN2
wbX	P_JNEV		120	jump if TOS != constant
                                if (TOS != XN2)
                                        PC += NN2
lbY	P_JNEV_L	121	jump if TOS != constant
                                if (TOS != XN4)
                                        PC += NN2
l	P_WIDENU	122	unsigned widen from short to long
				TOS = TOS&0xffff
uc2	P_LOAD_UB	123	tos is replaced by byte from indirection of old tos value [zero extend
                                TOS = *(unsigned char *)(TOS+N2)
	P_FOUND_LAUNCH	124	a launch event was detected
				LAUNCHED = 1
uw	P_MULU		125	unsigned multiply
				TOS = POP()*TOS
uw	P_DIVU		126	unsigned deivide
				TOS = POP()/TOS
ul	P_MULU_L	127	unsigned multiply
				TOS = POP()*TOS
ul	P_DIVU_L	128	unsigned divide
				TOS = POP()/TOS
uw	P_GEU		129	unsigned compare
				TOS = POP >= TOP
ul	P_GEU_L		130	unsigned compare
				TOS = POP >= TOP
uw	P_GTU		131	unsigned compare
				TOS = POP > TOP
ul	P_GTU_L		132	unsigned compare
				TOS = POP > TOP
uw	P_LEU		133	unsigned compare
				TOS = POP <= TOP
ul	P_LEU_L		134	unsigned compare
				TOS = POP <= TOP
uw	P_LTU		135	unsigned compare
				TOS = POP < TOP
ul	P_LTU_L		136	unsigned compare
				TOS = POP < TOP
wb	P_SET_TRAMPOLINE 137	set trampoline return address (see below)
wb	P_SET_EXTRA	138	set extra callback routine (see below)
				_EXTRA = PC+N2+1
ul	P_STIME		139	get time we last did a P_SAVE_TIME

Key to Notes:

	2 	- opcode is followed by a 2 byte constant
	4 	- opcode is followed by a 4 byte constant
	b 	- opcode is followed by a 2 byte constant relative to the PC+1
	X 	- opcode is followed by a 2 byte constant after a 'b' branch field
	Y 	- opcode is followed by a 4 byte constant after a 'b' branch field
	u	- operation involves unsigned math or zero-extension
	c	- operation is onbyte quantities
	w	- operation is on 2-byte quantities (stack PUSH/POP operations inc/dec the stack by 2)
	l	- operation is on 4-byte quantities (stack PUSH/POP operations inc/dec the stack by 2)

Run time support

You need to provide a number of things to hook into your flight computer's environment:


The logging code is actually written in rl (look in intrinsics.c for its source). It only gets included if the program includes any logging calls. It uses a couple of calls into your environment to figure out where the logging buffer should be P_LOG_BASE and P_LOG_END should return the start and end of the space available for the log (usually from the end of the globals to the bottom of the space set aside for the stack).

In addition the logging code needs to be called out of the P_POLL and a number of the loggable action operations (P_SET, P_FIRE, P_ARM and P_SAFE).

(examples of the following are in the simulator source).

The following byte codes include the addresses of code that should be saved away - they will be executed at the very beginning of the program P_SET_POLL, P_SET_TRAMPOLINE and P_SET_EXTRA.

In your P_POLL routine after you copy RTIME to TIME you must check to see if a P_SET_POLL value was given, you call the routine pointed to by P_SET_POLL:

		if (SET_POLL != 0) {
			TOS = PC+1;
For the P_SET, P_FIRE, P_ARM and P_SAFE byte codes after you perform the operation you must push PC+1 followed by the parameters in reverse order on to the stack (if the only have 1 push a 0 forst) followed by a code identifying the routine, the set the return address (TOS) to the TRAMPOLINE value (this is a pointer to the code that will clean up the stack for you as you exit) and jump to the SET_EXTRA address:

	P_FIRE:	do fire stuff
		if (SET_EXTRA) {
                        TOS = SET_TRAMPOLINE;
                        PC = SET_EXTRA;

	P_ARM:	do arm stuff
		if (SET_EXTRA) {
                        TOS = SET_TRAMPOLINE;
                        PC = SET_EXTRA;

	P_SAFE:	do fire stuff
		if (SET_EXTRA) {
                        TOS = SET_TRAMPOLINE;
                        PC = SET_EXTRA;

		do fire stuff
		if (SET_EXTRA) {
                        TOS = SET_TRAMPOLINE;
                        PC = SET_EXTRA;

(Note: all pushes are 2 byte pushes)

Launch Detection

The launch detection code is also written in rl and included by the compiler only if required - it uses the same hooks as the logging stuff above.

.x file format

The .x file consists of 2 layers of encoding - the outer layer is basicly Intel Hex file format - except that the lines start with '!' characters rather than ':' ones - so you can tell what type of file you are loading. The inner layer consists of a portion containing byte codes and another containing a file header.

Intel hex formatted data consists of lines of data, each starts with a : (or in this cate !) followed by a string of pairs of hex digits - consisting of the folllwing in order:

In the case of the HEX records for .x files the first record's address always starts at 0 and the increase through the file - you can load them to any contiguous place in memory (but usually at the lowest place you can).

The last record in the file is special it's flag field has the value 1 and it has the C structure (from code.h):

	struct code_hdr {
                unsigned        char    magic;                  // always 0xc5
                unsigned        char    cmd;                    // 0 - do nothing, just load
                                                                // 1 - run
                                                                // 2 - save
                unsigned        char    dest;                   // save set to save in
                unsigned        char    code_size[2];   	// number of bytes of code
                unsigned        char    string_size[2]; 	// number of bytes of strings
                unsigned        char    global_size[2];		// number of bytes of globals
                unsigned        char    stack_size[2];  	// number of bytes of stack
the values in the last 4 fields are actually 16-bit integers - stored little-endian.

At the begining of the program if the program was loaded at address BASE then PC will be set to BASE, SSP to BASE+code_size, GP to BASE+code_size+string_size.

stack_size is the number of bytes of stack to allocate - everything between the top of memory-stack_size and BASE+code_size+string_size+global_size is free for use as the system log.


Testing your implementation - I currently have a single, relativly simple test program (test.r in the source release) that you can use to hit a lot of parts of your implementation - it doesn't cover everything but does hit a lot of the math stuff - just compile it up and run it, if it works you will know (try running it in the simulator to see how it should work).

What's missing?

I don't know - you tell me - I've designed something around the capabilities of my flight computer while trying to guess what other people might need - I'd like to know what other people would like.