SIMPL is a character interpreter running within a loop – in pseudocode:
NEXT: Fetch next Character (VM Instruction) from buffer
Decode to form unique Jump Address
Jump to associated code body, execute code – which ends with Jump back to NEXT
This forms the heart of a very simple virtual machine which is capable of executing unique code functions based on the character found in the memory buffer. This elementary execution model is equivalent to most modern processor systems, so with a little bit of work, SIMPL can form the basis of a simulator for most basic processor architectures.
The character may be fetched from a serial terminal input buffer and executed immediately in an interactive manner, or the Instruction Pointer can point to a general area of program memory, and execute the character instructions found there.
It is this combination of using SIMPL either interactively at the terminal or as a means of entering and editing text into memory and subsequently running it from RAM, that makes SIMPL a powerful tool – to have in the programmer’s tool kit.
As a kernel, capable of executing the basic inner kernel loop, SIMPL may be coded in about 300 instructions on most small processors such as the MSP430 – this includes the overhead of setting up the mcu oscillator, UART and GPIO – so that it supports serial terminal interaction. As more functions are added, the code size grows by approximately 8 to 10 bytes per function – so it’s possible to have a small but usable SIMPL running in about 1K bytes.
The users application code will further extend this by perhaps a similar amount – but it still has an extremely small footprint, making it an ideal serial command shell for resource limited processors.
SIMPL when coded in C has been ported to a number of microcontrollers including ATmega, ATtiny, ARM M4, and MSP430.
The kernel has also been rewritten in MSP430 assembly language for speed and compactness, and work is ongoing to port it to the J1 Forth mcu.
The ability to encode the basic inner interpreter virtual machine in the native assembly language of a range of processors makes this a powerful technique of getting the basis of a useful “monitor” programme onto a variety of processors.
It also means that once the kernel has been coded in the native assembly language, then the user is working with a common set of instructions running on the virtual machine, at a level somewhat higher than the native assembly.
This creates a common language instruction set – a kind of “lingua franca” that makes interaction with a wide range of processors much easier. It also opens up the door for different processors to communicate with each other through this common character encoded language.
SIMPL has some of it’s roots in Forth, and indeed Charles Moore’s Forth language has been a source of inspiration for the development of SIMPL. However SIMPL is not a fully fledged language, more like a multi-function tool (Leatherman) which just makes the interpreted interaction with a range of small processors a lot easier, and less painful than the usual edit-compile-run cycle of code development found within a modern C compiler IDE.
Mechanics of SIMPL
Here’s some more info about the various routines that make up the kernel
textRead 33 Instructions 90 bytes
Receive characters from the serial uart and place them into a buffer in RAM. Two modes of operation are possible, immediate mode, where the characters are executed as instructions directly after a carriage return line feed is received, and compile mode, where the character sequences are preceded by a colon and stored in pre-calculated command buffers in RAM.
number 16 instructions 42 bytes
Number interprets sequences of consecutive digits as a 16-bit integer number and places the value in the register that is the top entry of the stack.
next 5 instructions 12 bytes
Next is the routine that all commands return the program flow back to once they have executed. It fetches the next character instruction from the RAM buffer and through a jump table technique passes program control to the code body that performs the task associated with that instruction. The jump table is used to direct the character to the areas of code that will treat them correctly – for example on encountering a numerical digit, program control is passed to the number routine, whilst for upper case alphabetical characters, program control is passed to a routine that handles these separately.
jump_table 96 instructions 192 bytes
Primitives 72 instructions 166 bytes
SIMPL uses a collection of 32 instruction primitives from which other instructions can be synthesised. The code body of these primitives is extendable to some 110 instructions or so.
Upper (called alpha in V1) 10 instructions 26 bytes
Upper handles the capital letters – as these are user commands, and the user is able to write and store in RAM certain functionality based on these characters. When a capital letter is encountered in the instruction buffer, Upper directs control to the correct command buffer.
Lower 86 bytes
Lower is an area of program that interprets the lower case characters and provides a higher level of program complexity than is achievable form the primitives alone.
printnum 30 instructions 106 bytes
This takes a 16 bit integer number from the stack and prints a string of ascii digit characters to the terminal.
Uart Routines 13 instructions 41 bytes
Low level communication with the uart is by way of the get_c and put_c routines
Initialisation 19 instructions 90 bytes
Here the hardware such as the oscillator, GPIO and uart are initialised for correct operation. This is code specific to whichever microcontroller has been chosen
Interpreter 4 instructions 16 bytes
This is the main routine that runs the SIMPL interpreter combining textRead, next, number and Upper.
The loop structure is fundamental to the SIMPL language – but it is one of the trickier parts to code – but with much head-scratching, and a couple of false starts, I got it implemented today.
Cracking the loop code – previously written in C, was key to getting SIMPL running in MSP430 assembly language. Very quickly after the loop code start working – comes LED flashing, tone generation, timing delays and printing character strings.
As it stands, with the loop-code in place, SIMPL is 874 bytes long. I am confidant that we can get SIMPL into 1024 bytes – as per the Hackaday 1K challenge.
The SIMPL loop code is given a single value k – the loop counter which is placed on the stack immediately before the loop-start character which is a left bracket (.
On finding the left bracket, we need to make a note of the current instruction pointer when we enter the loop – so we have a location to jump back to each time around the loop.
We use the temporary registers R7 and R8 to hold the loop parameters – i.e. the start of loop and the loop count
The control is then passed back to the interpreter by $NEXT (jmp next) which steps through the instructions (in the loop) until the right bracket is found.
We then decrement and test the loop counter to see if it is zero – at which point the loop is terminated. If non-zero, we return the instruction pointer to the start of the loop before jumping back to next.
Confused ?- you will be – but it really is that simple! Just 7 instructions and we are looping.
left_par: ; code enters here on getting a left parenthesis
MOV.W tos,R8 ; save tos to R8 (R8 is the loop counter k)
MOV.W ip,R7 ; loop-start = ip the current instruction pointer at start of loop
JMP next ; get the next character and execute it
right_par: ; code enters here on getting a right parenthesis
DEC.W R8 ; decrement loop counter R8
JEQ #next ; and check for zero - terminate loop
MOV.W R7,ip ; set instruction pointer back to the start of the loop
JMP next ; keep looping
SIMPL now runs on a 16MHz MSP430 in just 872 bytes – with serial communications at 115200 baud. Most of the primitive instructions execute in about 1uS – allowing a port pin to be toggled at 1MHz.
I have included the latest assembly source file at this Github Gist