e-4917 emulator in Clojure

Clojure, Programming

Introduction

Years ago I was introduced to Richard Buckland’s excellent ‘Higher Computing Series” on Youtube. It featured a little microprocessor called the e-4917.

It taught me how to program computers on the lowest level, and how memory can be both instructions and data.

The e-4917 has 2 registers called R0 and R1 for storage, a register for the current instruction called IS and a pointer to the next instruction in memory called IP. But most importantly it has a whopping 16 memory locations to store numbers in.

Instructions

These are the one bit instructions:

0 = exit
1 = add (R0 = R0 + R1)
2 = subtract (R0 = R0 – R1)
3 = increment R0 (R0 = R0 + 1)
4 = increment R1 (R1 = R1 + 1)
5 = decrement R0 (R0 = R0 – 1)
6 = decrement R1 (R1 = R1 – 1)
7 = ring a bell

These are the two bit instructions. Each instruction takes an argument which is a location in memory. Except for print which uses the next location in memory! When a two bit instruction is ‘found’ it will skip 2 memory locations instead of 1, since the next location is the argument for the instruction.

8   = print the value of the next location in memory
9   = load value from argument location into R0
10 = load value from argument location into R1
11 = write value of R0 in argument location
12 = write value of R1 in argument location
13 = jump to argument location, and continue instructions from there
14 = jump to argument location but only if R0 == 0
15 = jump to argument location but only if R0 != 0

Example programs

A simple program looks like this:

[8, 7, 0]

It prints 7 and then exists. Why? because the program first encounters the instruction called 8, 8 means print the value of the next location in memory which is 7.

Here’s a program that makes a buzzing sound three times:

[7, 7, 7]

This is a complex program that subtracts 6 and 4:

[9, 15, 10, 14, 2, 11, 8, 8,
0, 0, 0, 0, 0, 0, 4, 6]

First it encounters the 2 byte instruction 9, which means load value from the argument location into R0. So it looks at its argument which is 15, so it grabs the value stored in memory location 15 which is 6 and stores it into R0. Since it is a 2 byte instruction it will skip location 1 and move to location 3. There it will find instruction 10 which will store 4 into location R1.

Then it will finally do the subtraction since it encounters the 1 byte instruction 2. Now R0 = 6 – 4 = 2. Now we need to print the result otherwise the program will be useless.

In order to print something we need to use instruction 8. Which prints the value of the next memory location. So in order to print R0 we need to write it to memory next an 8 instruction. Here’s the trick:

We write R0 to memory location 8 with instruction 11. We write to memory location 8 because it is the location after memory location 7 which contains the instruction 8.

To visualize this, this is the memory before instruction 11:

[9, 15, 10, 14, 2, 11, 8, 8,
0, 0, 0, 0, 0, 0, 4, 6]

This is the memory after:

[9, 15, 10, 14, 2, 11, 8, 8,
2, 0, 0, 0, 0, 0, 4, 6]

So when the next instruction 8 is called it will look at memory location 8 and it will see the 2 and it will print it.

This example shows how  instructions and data are both numbers, and that data = code. I especially like that there are two 8 next to each other each meaning a different thing.

Clojure

Data = code is very Clojureish so I thought it would be appropriate to create an emulator in Clojure.

Lets start with the definition of the computer’s state:

The first argument the “State” record is called: memory. It represents all the memory locations and their values. Since data = code the memory is also the program. The other arguments represent the registers of the processor.

We can now define a program which starts at instruction 0 with:

Having a State is not enough we need to be able to run the instructions on it. Lets define the “add” instruction which is represented by the number 1. Remember it adds R0 and R1 and stores the result in R0.

The code is very simple it takes a State which it deconstructs into memory, r0, r1, ip and is. So we can manipulate it easier. It will then return a new state where r0 = (+ ro r1). Also because it has ran the instruction the instruction pointer must be updated, and since it is a one byte instruction it must be increased by one. We will also change the instruction pointer to 1 since that is the operation we’re preforming.

A demonstration of ‘add’ in the repl:

It added 2 and 4 and stored it in R0, yay it works!

Instructions 2 to 6 require little imagination now that we’ve seen how it works:

I’ll skip instruction 7 / 8 and leave it for later since we don’t have a GUI to print something too yet.

Instruction number 9 (load value from argument location into R0) looks like this:

Nth is a function that takes a sequence and returns the value of the second argument which is the location. So ([5 6 1] 1) returns 6.

We use nth to get the value of the location which the argument value of the instruction points to. So if the program looks like this [9 3 0 1] it will load 1 into R0.

Since 9 is a two pointer instruction the instruction pointer must be incremented by 2 instead of 1 so it skips it’s argument.

Instruction number 10 (load value from argument location into R1) is instruction 9’s twin:

Instruction number 11 (write value of R0 in argument location) looks like this:

Assoc is a function that can takes a collection and changes it based on the second and third argument. In this case we use it to change a vector, here the second argument is the value we want to assign to the location in the vector which is the third argument. So (assoc [0, 1 2] 0 3) results in: [10 1 2].

store-r0 uses assoc to change the program / memory. It changes the value of the memory location that the  Argument to instruction 11 points to, to the value that is currently in r0.

Instruction 11 also has a twin in instruction 12 (write value of R1 in argument location). Since you can probably guess by now what it looks like, I’ll leave it be. You can see it in the source however.

13 is the jump instruction, it can be used to change the instruction pointer to anywhere you’d like. Basically a GOTO, just don’t let Dijkstra hear it 😛

In jump nothing happens to the memory, r1 or r2 instead IP is changed to the value of the arguments location.

Instructions 14 and 15 are variations on jump, in fact they use jump:

Now that we can execute every instruction imaginable we need a way to run every instruction until a ‘exit’ instruction is found. We use two functions to do this:

The function ‘run’ simply calls itself recursively until the “step” returned the function “step” is false. This happens when a halt instruction is found. The step function acts as a router for the numbers to the actual Clojure functions.

Now it’s time to create a GUI:

The UI looks like this:

4817-UI

In GUI you find the only variable in this program called “prog”. It is a ref which points to a State. It will be used when the “run” button is clicked. The run button calls the “run” function on that state.

The load button takes the input from the “input” textarea and parses every “int” into a vector. That vector will then be used as the memory / program for the “prog” State.

The step button runs each instruction one step at time and prints all registers each time it is pressed. This way you can see what is going on inside the microprocessor.

Now that we have an output screen is time to define the last two instructions 7 and 8:

Bell is very simple it appends the string “buzz” to the “output” textarea.

Print is the strangest 2 byte instruction, it actually uses it’s argument directly where the others use their arguments indirectly because they are pointers to the “real” arguments. It prints the value of the argument to the “output” textarea.

Note: I recommend the glorious seesaw to create the UI. When I originally wrote the program it was not available. But I would have used it if it were!

And there you have it an e-4917 emulator in Clojure.

Source code can be found on GitHub, note that there’s also an imperative version. It uses the STM to simulate the processor.

Leave a Reply

Your email address will not be published. Required fields are marked *


7 − six =

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">