SICO Online Editor

The Single Instruction Computer

sico.py with demos can be downloaded here: sico.zip

SICO is a Single Instruction COmputer that mimics the functionality of a normal computer while using only one computing instruction. This is like going into a forest with no tools and trying to build a house. Since we only have one instruction, most modern conveniences are gone. Things like multiplying numbers or memory allocation need to be built from scratch using SICO's instruction.

The instruction is simple: Given A, B, and C, compute mem[A]-=mem[B]. Then, if mem[A] was less than or equal to mem[B], jump to C. Otherwise, jump by 3. We use the instruction pointer to keep track of our place in memory. The pseudocode below shows a SICO instruction:

A = mem[IP+0] B = mem[IP+1] C = mem[IP+2] IP += 3 if mem[A] <= mem[B]: IP = C mem[A] -= mem[B]

The instruction pointer and memory values are all 64 bit unsigned integers. Interaction with the host environment is done by reading and writing from special memory addresses. For example: writing anything to -1 will end execution.

SICO Assembly Language

Whatever the instruction pointer is pointing to is what gets executed, so there's no need to write out instructions like mov, jmp, etc. We only need an assembly language to help write memory values.

A "Hello, World!" program in assembly:

loop: 0-2 txt ?+1 # Print a letter. len one exit # Decrement [len]. If [len]<=1, exit. ?-5 neg loop # Increment letter pointer. exit: 0-1 0 0 txt: 'H 'e 'l 'l 'o ', ' 'W 'o 'r 'l 'd '! 10 len: len-txt neg: 0-1 one: 1

The syntax of the assembly language

ComponentDescription
Line Comment# comment
Block Comment#| comment |#
Label Declarationlabel:
Label Recalllabel
Sublabellabel: .sub: is treated as label.sub:
Current Address?
Number123 or 0xabc
ASCII Literal'A 'B 'C evaluates to 65 66 67
Operator+ or -. Ex: 1+2-3
Input / OutputRead or write to addresses above 2^63

IO addresses (mod 2^64)

-1Writing ends execution
-2Writing prints to stdout
-3Read from stdin
-4Read timing frequency
-5Read system time
-6Writing sleeps for mem[B]/freq seconds

To print the letter 'A' to stdout:

0-2 chr ?+1 chr: 'A

Synthesized Instructions

Although printing text to the screen is easy, we will need to synthesize more complicated instructions to serve as building blocks when we make more complicated programs. In future articles, we will also show how to turn these synthesized instructions into easy to call functions from within SICO. For now, we are only focusing on basic instructions in order to show how the SICO architecture works.

One of the most common instructions: an unconditional jump to jmp. It also sets [tmp]=0.

tmp tmp jmp

We can abort a SICO program by writing any value to -1. We need to calculate -1 as "0-1" due to the syntax our assembly language.

0-1 0 0

Set [a]=[b]. The series of ?+1 expressions points to the next memory address after the instruction. They simply serve to force the instruction pointer to go to the next instruction regardless of whether or not the instruction would jump.

tmp tmp ?+1 tmp b ?+1 a a ?+1 a tmp ?+1

Jump to jmp if [a]=[b].

tmp1 tmp1 ?+1 tmp1 b ?+1 tmp2 tmp2 ?+1 tmp2 tmp1 ?+1 tmp2 a ?+1 tmp1 tmp1 ?+1 tmp2 tmp1 jmp

We can print the character "A" to the screen by writing it to the special address -2.

0-2 chr ?+1 chr: 'A

Increment [a].

a neg ?+1 neg: 0-1

Decrement [a].

a one ?+1 one: 1

Set [C]=[[A]+[B]]. This is the same as getting the value at an array index, as in C=arr[i] in other languages. This will form the backbone of functions in SICO.

tmp tmp ?+1 tmp A ?+1 tmp B ?+1 ptr ptr ?+1 ptr tmp ?+1 tmp tmp ?+1 tmp ptr:0 ?+1 C C ?+1 C tmp ?+1

Set [[A]+[B]]=[C]. This is the same as assigning a value to an array, as in arr[i]=C.

p0 A ?+1 p0 B ?+1 p1 p0 ?+1 p2 p0 ?+1 tmp tmp ?+1 tmp p1 ?+1 p0 p0 ?+1 p0 tmp ?+1 tmp tmp ?+1 tmp C ?+1 p0:0 p1:0 ?+1 p2:0 tmp ?+1

If we allow a SICO instruction to be atomic, we can actually create a spinlock. When the lock is first acquired, the value of [lock+1] is overwritten from z-1 to z-1-[z-1]=z and we jump to the critical section. When a second thread tries to acquire the lock, it will subtract [z]=0 from z, which will fail to jump, and the thread will be caught by the next instruction, z z lock. When the owning thread is done with the lock, it just needs to subtract [z+1]=1 from [lock] to allow the lock to be acquired by a new thread.

lock: lock+1 z-1 crit # acquire lock z z lock # failed to acquire, try again crit: # # critical section # lock+1 z+1 ?+1 # reopen spinlock z z jmp # jump 0-1 z:0 1

Notes

SICO belongs to the family of one instruction architectures, like the subleq architecture it's based off of.

Whereas subleq uses 2's complement signed arithmetic, SICO uses unsigned arithmetic. There are several reasons for this:

SICO offers 3 ways to guarantee a jump

1. Zeroing an address:A A jmp
2. Subtracting anything from 0:Z A jmp
3. Subtracting -1 from anything:A Z-1 jmp

Subleq can only guarantee a jump by zeroing an address. This gives SICO more options when deciding program flow, since these are all common operations.

Using the instruction A Z jmp, we can test A for equality with 0 using only one instruction and without modifying any variables. This a common value to test for. Subleq, would require 1 to 2 instructions and modifying A for this test.

SICO also uses a different order for operands. For operands A, B, and C, SICO takes [A]=[A]-[B] where subleq takes [B]=[B]-[A]. The order of operands in subleq is perfectly valid, of course, but goes against the left-to-right ordering used in programming. For example, we usually write a=b+c instead of b+c=a. In an early version of SICO, I used the subleq order of operands and found myself constantly having to reorder the operands in my head. Thus I decided swap the roles of A and B.

sico.zip contains sico.py, demos, and interpreters.