Skip to content

From C to Electricity

Thomas Castleman edited this page Aug 27, 2021 · 1 revision

To demonstrate what getting a high-level program to run on the 3000 is like, we will walk through an example, from source program to running on the actual hardware.

Our program will go through the following stages as it makes it way to the machine:

  1. C program (written by a human)
  2. Assembly program (generated by the compiler)
  3. Binary (generated by the assembler)
  4. Binary in the 3000's instruction memory (put there by the loader)
  5. A running program (as the 3000 carries out the instructions)

Note that steps 1-3 occur on an external computer (such as your own laptop), whereas 4-5 occur on the 3000 itself.

Source program

The program we will use as an example is called fib.3000.c, because it prints out Fibonacci numbers (as many as are representable by the 3000, of course).

Here is the code:

/**
 * This program computes Fibonacci numbers, using iteration.
 * 
 * NOTE: run this with the decimal display in unsigned mode.
 */
void main() {
    unsigned n = 0;
    unsigned prev = 0;
    unsigned cur = 1;

    // prev will be <= cur until cur overflows
    while (prev <= cur) {
        print(cur);

        unsigned tmp = prev;
        prev = cur;
        cur = tmp + cur;
    }
}

It maintains prev and cur, which are consecutive Fibonacci numbers, and on each iteration of the loop prints one of them out and sets prev equal to cur and cur equal to prev + cur (thus moving to the next two consecutive Fibonacci numbers).

When cur (the larger of the two) reaches a value that is over 255 (and therefore unrepresentable as an unsigned 8-bit integer), its value will overflow to some small number less than prev. Therefore, the loop breaks when prev > curr, as this indicates that we have hit the limit on representable Fibonacci numbers.

Assembly

To tell the compiler to generate code for our hand-written program, we invoke stew3c as follows:

$ stew3c ./fib.3000.c ./fib.3000.s
Success! `./fib.3000.c` ==> `./fib.3000.s` (26 instructions)

The compiler produces a file, fib.3000.s, which contains the generated code: (comments added for clarity)

user_program_start:
  sts z, 1    ; n = 0
  sts z, 2    ; prev = 0
  stsi 1, 3   ; cur = 1
start_while_0:
  ; get prev and cur into the a and b registers for comparison
  lds 3, a
  sts a, 4
  lds 2, a
  lds 4, b

  ; compare prev and curr, break out of loop if prev > curr
  cmp a, b
  ja condition_failed_1

  ; print(cur)
  lds 3, a
  out a

  ; tmp = prev
  lds 2, a
  sts a, 4

  ; prev = cur
  lds 3, a
  sts a, 2

  ; cur = tmp + cur
  lds 3, a
  sts a, 5
  lds 4, a
  lds 5, b
  add b, a
  sts a, 3

  jmp start_while_0 ; go to top of loop

condition_failed_1:
  hlt   ; when prev > cur, halt the program

Binary

To get a binary that can actually get loaded onto the machine from our assembly program, we invoke the assembler:

$ stew3s ./fib.3000.s ./fib.3000.b
Success! `./fib.3000.s` (26 instructions) ==> `./fib.3000.b` (43 bytes)
9d 01 9d 02 9e 01 03 97
03 9a 04 97 02 98 04 9f
b8 2a 97 03 be 97 02 9a
04 97 03 9a 02 97 03 9a
05 97 04 98 05 04 9a 03
b1 07 c7

This produces a file, fib.3000.b, which contains the raw bytes that are printed in hexadecimal above. The file is just bytes though, so if you try to cat it or open it in a text editor, you'll probably see some weird characters.

To get a better sense of which bytes correspond to which instructions, we can pass the -side-by-side flag to the assembler to get the following output:

              | user_program_start:
00:     9d 01 | 	sts z, 1
02:     9d 02 | 	sts z, 2
04:  9e 01 03 | 	stsi 1, 3
              | start_while_0:
07:     97 03 | 	lds 3, a
09:     9a 04 | 	sts a, 4
0b:     97 02 | 	lds 2, a
0d:     98 04 | 	lds 4, b
0f:        9f | 	cmp a, b
10:     b8 2a | 	ja condition_failed_1
12:     97 03 | 	lds 3, a
14:        be | 	out a
15:     97 02 | 	lds 2, a
17:     9a 04 | 	sts a, 4
19:     97 03 | 	lds 3, a
1b:     9a 02 | 	sts a, 2
1d:     97 03 | 	lds 3, a
1f:     9a 05 | 	sts a, 5
21:     97 04 | 	lds 4, a
23:     98 05 | 	lds 5, b
25:        04 | 	add b, a
26:     9a 03 | 	sts a, 3
28:     b1 07 | 	jmp start_while_0
              | condition_failed_1:
2a:        c7 | 	hlt

Loading

To get our program binary into the instruction memory of the 3000, we use the program loader. The loader is a Python script which can be invoked as follows from the project root:

$ python3 RAM_loader/RAM_loader.py fib.3000.b

This sends each byte of the indicated binary file to the Arduino (which should be connected to the external computer via USB). The Arduino in turn writes each byte to the instruction memory of the 3000, where it can be accessed while the program is running.

Click the below image to see a video of the fib program being loaded onto the 3000.

Loading onto the 3000

Running

To get the program to finally run, we need to stop the clock, put the computer into run mode, reset everything, and then start the clock again.

Click the below image to see a video of the fib program running on the 3000!

Running on the 3000

Clone this wiki locally