128 lines
4.4 KiB
Nim
128 lines
4.4 KiB
Nim
import std/strformat
|
|
|
|
type
|
|
OpCode = enum opNOP, opLDA, opSTA, opADD, opSUB, opBRZ, opJMP, opSTP
|
|
Instruction = byte
|
|
Address = 0..31
|
|
# Definition of a computer zero.
|
|
Computer = object
|
|
mem: array[Address, byte] # Memory.
|
|
pc: Address # Program counter.
|
|
acc: byte # Accumulator.
|
|
Program = tuple
|
|
name: string # Name of the program.
|
|
code: seq[Instruction] # List of instructions.
|
|
|
|
proc split(inst: Instruction): tuple[code: OpCode, address: Address] =
|
|
## Split an instruction into an opCode and a value.
|
|
(OpCode((inst and 0b11100000u8) shr 5), Address(inst and 0b00011111u8))
|
|
|
|
proc toInst(opCode: OpCode; address: int): Instruction =
|
|
## Build an instruction with given opcode and value.
|
|
if address notin Address.low..Address.high:
|
|
raise newException(ValueError, "address out of range.")
|
|
byte(opCode) shl 5 or byte(address)
|
|
|
|
# Functions to build instructions.
|
|
func NOP(n: int): Instruction = toInst(opNOP, n)
|
|
func LDA(n: int): Instruction = toInst(opLDA, n)
|
|
func STA(n: int): Instruction = toInst(opSTA, n)
|
|
func ADD(n: int): Instruction = toInst(opADD, n)
|
|
func SUB(n: int): Instruction = toInst(opSUB, n)
|
|
func BRZ(n: int): Instruction = toInst(opBRZ, n)
|
|
func JMP(n: int): Instruction = toInst(opJMP, n)
|
|
func STP(n: int): Instruction = toInst(opSTP, n)
|
|
|
|
proc reset(comp: var Computer) =
|
|
## Reset the computer state.
|
|
comp.mem.reset()
|
|
comp.pc = 0
|
|
comp.acc = 0
|
|
|
|
proc load(comp: var Computer; program: Program) =
|
|
## Load a program into a computer.
|
|
comp.reset()
|
|
var idx = 0
|
|
for inst in program.code:
|
|
comp.mem[idx] = inst
|
|
inc idx
|
|
|
|
proc run(comp: var Computer) =
|
|
## Run a computer.
|
|
## The execution starts or resumes at the instruction
|
|
## at current PC, with current value in the accumulator.
|
|
while true:
|
|
let (opCode, address) = comp.mem[comp.pc].split()
|
|
inc comp.pc
|
|
case opCode
|
|
of opNOP:
|
|
discard
|
|
of opLDA:
|
|
comp.acc = comp.mem[address]
|
|
of opSTA:
|
|
comp.mem[address] = comp.acc
|
|
of opADD:
|
|
comp.acc += comp.mem[address]
|
|
of opSUB:
|
|
comp.acc -= comp.mem[address]
|
|
of opBRZ:
|
|
if comp.acc == 0:
|
|
comp.pc = address
|
|
of opJMP:
|
|
comp.pc = address
|
|
of opSTP:
|
|
break
|
|
|
|
proc enterValue(comp: var Computer; value: byte) =
|
|
## Enter a value into memory at address given by the PC,
|
|
## then increment the PC.
|
|
comp.mem[comp.pc] = value
|
|
inc comp.pc
|
|
|
|
|
|
# Programs are built using the functions NOP, LDA, STA, etc.
|
|
# To be able to use Uniform Function Call Syntax (i.e. call without parentheses),
|
|
# all these functions have an argument, even NOP and STP.
|
|
const
|
|
Prog1 = (name: "2+2",
|
|
code: @[LDA 3, ADD 4, STP 0, 2, 2])
|
|
Prog2 = (name: "7*8",
|
|
code: @[LDA 12, ADD 10, STA 12, LDA 11, SUB 13, STA 11, BRZ 8, JMP 0,
|
|
LDA 12, STP 0, 8, 7, 0, 1])
|
|
Prog3 = (name: "Fibonacci",
|
|
code: @[LDA 14, STA 15, ADD 13, STA 14, LDA 15, STA 13, LDA 16, SUB 17,
|
|
BRZ 11, STA 16, JMP 0, LDA 14, STP 0, 1, 1, 0,
|
|
8, 1])
|
|
Prog4 = (name: "List",
|
|
code: @[LDA 13, ADD 15, STA 5, ADD 16, STA 7, NOP 0, STA 14, NOP 0,
|
|
BRZ 11, STA 15, JMP 0, LDA 14, STP 0, LDA 0, 0, 28,
|
|
1, 0, 0, 0, 6, 0, 2, 26,
|
|
5, 20, 3, 30, 1, 22, 4, 24])
|
|
Prog5 = (name: "Prisoner",
|
|
code: @[NOP 0, NOP 0, STP 0, NOP 0, LDA 3, SUB 29, BRZ 18, LDA 3,
|
|
STA 29, BRZ 14, LDA 1, ADD 31, STA 1, JMP 2, LDA 0, ADD 31,
|
|
STA 0, JMP 2, LDA 3, STA 29, LDA 1, ADD 30, ADD 3, STA 1,
|
|
LDA 0, ADD 30, ADD 3, STA 0, JMP 2, 0, 1, 3])
|
|
|
|
var computer: Computer
|
|
|
|
for prog in [Prog1, Prog2, Prog3, Prog4]:
|
|
computer.load prog
|
|
computer.run()
|
|
echo &"Result for {prog.name}: {computer.acc}"
|
|
|
|
# "Prog5" is different as it stops and waits for an input from the user.
|
|
# Input is stored at address 3 (current PC value) and scores are stored at addresses 0 and 1.
|
|
|
|
type Action = enum cooperate, defect
|
|
|
|
echo &"\nResult for {Prog5.name}:"
|
|
computer.load Prog5
|
|
computer.run()
|
|
|
|
for round in 1..5:
|
|
let action = Action(round and 1)
|
|
computer.enterValue(byte(action))
|
|
computer.run()
|
|
echo &"Round {round} Action: {action:9} Player: {computer.mem[0]} Computer: {computer.mem[1]}"
|