Turing complete - self documentation

Posted on Apr 17, 2024

This is my documentation for my own game of Turing Complete. Turing Complete purpose is to guide you through the implementation of a simple CPU, from the first logical gate to your assembly language definition.

⚠️ This page is mainly for myself as a reminder. This is very far from the best design and solutions, but it's the best I could do in a reasonable time :)

Schematic

My own computer in Turing Complete

Raw version, non annnotated

Instructions set

Syntax

Instruction <op code> <arg1> <arg2> <dest>

<arg1> and <arg2> can be set to an immediate value with bitwise OR on the opcode:

  • for arg1, <op code>|128
  • for arg2, <op code>|64

Table

OP CodeAssembly instructionParametersComment
0add<arg1> + <arg2> in register <dest>
1sub<arg1> - <arg2> in register <dest>
2and<arg1> AND <arg2> in register <dest>
3or<arg1> OR <arg2> in register <dest>
4not<arg1> NOT <arg2> in register <dest>
5xor<arg1> XOR <arg2> in register <dest>
6shift_rightshift right <arg1> by <arg2> in register <dest>
7shift_leftshift left <arg1> by <arg2> in register <dest>
8ram_input<src> <unused> <unused>Store <arg1> in RAM at the address stored in R5
9ram_output<unused> <unused> <dest>Copy to <dest> the value stored at the RAM address stored in R5
10mul<arg1> * <arg2> in register <dest>
11push<src> <unused> <unused>Push <arg1> to the stack
12pop<unused> <unused> <dest>Pop the stack into <dest>
13call<unused> <unused> <dest>push PC+4 to the stack and set the PC to dest
14ret<unused> <unused> <unused>pop the stack to the PC
32eqset PC to <dest> if <arg1> = <arg2>
33neqset PC to <dest> if <arg1> != <arg2>
34ltset PC to <dest> if <arg1> < <arg2>
35leset PC to <dest> if <arg1> <= <arg2>
36gtset PC to <dest> if <arg1> > <arg2>
37geset PC to <dest> if <arg1> >= <arg2>

Addresses

Register addressRegister name
0Register 0
1Register 1
2Register 2
3Register 3
4Register 4
5Register 5 - ram pointer
6Program Counter (PC)
7Input/Output

Solution of the level unseen fruit

# Constants definitions for readability
const R0 0
const R1 1
const R2 2
const R3 3
const R4 4

const RAM_PNTR 5
const PROGRAM_CNTR 6
const I_O 7

const _ 0 #UNUSED PARAM

const COUNTER 1

# Go to conveyor belt
add|64|128 2 0 I_O
add|64|128 1 0 I_O
add|64|128 2 0 I_O
add|64|128 1 0 I_O
add|64|128 1 0 I_O
add|64|128 1 0 I_O
add|64|128 1 0 I_O
add|64|128 2 0 I_O
add|64|128 1 0 I_O
add|64|128 0 0 I_O
add|64|128 1 0 I_O


label main_loop_begin
# copy input in R0
add|64|128 3 0 I_O
add|64 I_O 0 R0
# if input = 92, wait for a fruit
cond_eq|64 R0 92 main_loop_begin
# if input != 92, call function check
call _ _ check
jump _ _ main_loop_begin
label main_loop_end

jump _ _ end

# function check - # check if fruit has already been seen
label check
add|64|128 0 0 RAM_PNTR
label check_loop_begin
# load ram
ram_output _ _ R2
add|128 1 RAM_PNTR RAM_PNTR
cond_eq R0 R2 check_push
cond_gt RAM_PNTR COUNTER check_store
jump _ _ check_loop_begin

# function check_store
# if input number is not in memory, store the number
label check_store
call _ _ store
jump _ _ check_loop_end

# function check_push
# else, push the button
label check_push
call _ _ push
jump _ _ check_loop_end
label check_loop_end
cond_le R2 COUNTER check_loop_begin
label check_end
ret

# function store
# Append a new value in RAM
label store
add|128 1 COUNTER COUNTER
add|128 0 COUNTER RAM_PNTR
ram_input R0 _ _
ret

# Push the button
label push
add|64|128 2 0 I_O
add|64|128 4 0 I_O
add|64|128 0 0 I_O
ret

label end

Solution of the level delicious order

The program works in 3 steps:

  1. loads all the values from I_O and store them in memory
  2. performs a bubble sort, suboptimal - O(n²) - but easy and readable
  3. Output the sorted data
const R0 0
const R1 1
const R2 2
const R3 3
const R4 4

const RAM_PNTR 5
const PROGRAM_CNTR 6
const I_O 7

const _ 0

const array_size 15

# Init Registers to 0
add|64|128 0 0 R0
add|64|128 0 0 R1
add|64|128 0 0 R2
add|64|128 0 0 R3
add|64|128 0 0 R4
add|64|128 0 0 RAM_PNTR

# Load in memory
label load
push I_O _ _
add|64 RAM_PNTR 1 RAM_PNTR
cond_le|64 RAM_PNTR array_size load
add|64|128 _ _ RAM_PNTR # set to 0
label copy_mem
pop _ _ R0
ram_input R0 _ _
add|64 RAM_PNTR 1 RAM_PNTR
cond_le|64 RAM_PNTR array_size copy_mem

# Init Registers to 0
add|64|128 0 0 R0
add|64|128 0 0 RAM_PNTR

const I R0
const MAX_I array_size
const J R1
const MAX_J R2

label main_loop

sub|128 MAX_I I MAX_J

label inner_loop

# Compare and swap
# RAM_PNTR <- J
add J 0 RAM_PNTR
# R3 <- RAM
ram_output  _ _ R3
# RAM_PNTR <- J+1
add|64 RAM_PNTR 1 RAM_PNTR
# R4 <- RAM
ram_output  _ _ R4
# if R3 > R4, Swap R3-R4
cond_le R3 R4 no_swap
#  Push R3 to RAM_PNTR[J+1]
ram_input R3 _ _
#  Push R4 to RAM_PNTR[J]
sub|64 RAM_PNTR 1 RAM_PNTR
ram_input R4 _ _
label no_swap

# end inner_loop

add|64 J 1 J
cond_lt J MAX_J inner_loop
add|64|128 0 0 J

# end main_loop
add|64 I 1 I
cond_lt|64 I MAX_I main_loop



# Unload
add|64|128 0 0 RAM_PNTR
label unload
add|64 RAM_PNTR 1 RAM_PNTR
ram_output _ _ I_O
cond_le|64 RAM_PNTR array_size unload