two-bit/minecraft-machine.test.ts

199 lines
6.4 KiB
TypeScript

import { minecraftMachine } from "./minecraft-machine"
import { ram } from "./ram"
import { Word, wordFromNumber, wordFromString } from "./word"
const push = wordFromString("00") as Word<2>
const jump = wordFromString("01") as Word<2>
const add = wordFromString("10") as Word<2>
const write = wordFromString("11") as Word<2>
const any = wordFromString("00") as Word<2>
describe("Minecraft machine", () => {
let subject: ReturnType<typeof minecraftMachine>
beforeEach(() => {
const bank1Ram = toWordArray([
"1011",
"0001",
"0000",
"1110"
])
const bank2Ram = toWordArray([
"1111",
"0101",
"1010",
"1000",
"0011"
])
const instructions = [
"0000"
].map(value => wordFromString(value) as Word<4>)
subject = minecraftMachine(bank1Ram, bank2Ram, instructions)
})
test("starts with the program counter pointing at 0x0", () => {
expect(subject.programCounter.value().toString()).toEqual("0000")
})
test("starts with the address register pointing at 0x2", () => {
expect(subject.addressRegister.value().toString()).toEqual("0010")
})
test("starts with the extended address register pointing at the first memory bank", () => {
expect(subject.extendedAddressRegister.value().toString()).toEqual("0000")
})
test("starts with an empty stack", () => {
expect(subject.stack.values().map(word => word.toString())).toEqual(["0000", "0000", "0000", "0000"])
})
test("loads the first ram bank with the provided values starting after reserved addresses, leaving unspecified values as zeros", () => {
expect(wordsToStrings(subject.bank1Ram.values())).toEqual([
"0010", "0000", "1011", "0001",
"0000", "1110", "0000", "0000",
"0000", "0000", "0000", "0000",
"0000", "0000", "0000", "0000"
])
})
test("loads the second ram bank with the provided values starting after reserved addresses, leaving unspecified values as zeros", () => {
expect(wordsToStrings(subject.bank2Ram.values())).toEqual([
"0010", "0000", "0000", "0000",
"0000", "1111", "0101", "1010",
"1000", "0011", "0000", "0000",
"0000", "0000", "0000", "0000"
])
})
describe("push instruction", () => {
beforeEach(() => {
subject = minecraftMachine(toWordArray(["1101", "0110"]), toWordArray(["1010"]), [push.concat(push)])
subject.addressRegister.set(wordFromString("0010") as Word<4>)
})
test("loads value at address register onto the stack", () => {
subject.tick()
expect(wordsToStrings(subject.stack.values())).toEqual(["1101", "0000", "0000", "0000"])
})
test("increments the stack pointer", () => {
subject.tick()
expect(subject.stackPointer.value().toString()).toEqual("01")
})
test("loads the value on top of the existing stack", () => {
subject.tick()
subject.tick()
expect(wordsToStrings(subject.stack.values())).toEqual(["1101", "0110", "0000", "0000"])
})
test("increments the address register", () => {
subject.tick()
expect(subject.addressRegister.value().toString()).toEqual("0011")
})
test("loads value from bank 2 if extended address register is pointing there", () => {
subject.extendedAddressRegister.set(wordFromNumber(1, 4) as Word<4>)
subject.addressRegister.set(wordFromString("0101") as Word<4>)
subject.tick()
expect(wordsToStrings(subject.stack.values())).toEqual(["1010", "0000", "0000", "0000"])
})
test("does not increment the stack pointer if the stack is full", () => {
subject.stackPointer.set(wordFromString("11"))
subject.tick()
expect(subject.stackPointer.value().toString()).toEqual("11")
})
testIncrementsTheProgramCounter(() => subject)
})
describe("jump instruction", () => {
beforeEach(() => {
subject = minecraftMachine(toWordArray([]), toWordArray([]), [jump.concat(any)])
subject.stackPointer.set(wordFromString("01"))
})
describe("when the previous stack value is greater than zero", () => {
beforeEach(() => {
subject.stack.write(wordFromString("0001") as Word<4>, wordFromString("00"))
subject.stack.write(wordFromString("0100") as Word<4>, wordFromString("01"))
subject.tick()
})
test("sets the program counter to the current stack value", () => {
expect(subject.programCounter.value().toString()).toEqual("0100")
})
test("resets the instruction pointer", () => {
expect(subject.instructionPointer.value().toString()).toEqual("0")
})
test("pops the current value", () => {
expect(subject.stackPointer.value().toString()).toEqual("00")
})
})
describe("when the previous stack value is zero", () => {
test("pops the current value", () => {
})
testIncrementsTheProgramCounter(() => subject)
})
})
})
function testIncrementsTheProgramCounter(getSubject: () => ReturnType<typeof minecraftMachine>) {
let subject: ReturnType<typeof minecraftMachine>
beforeEach(() => {
subject = getSubject()
})
test("does not increment the program counter if the current instruction is the first", () => {
subject.tick()
expect(subject.programCounter.value().toString()).toEqual("0000")
})
test("increments the program counter if the current instruction is the second", () => {
subject.tick()
subject.tick()
expect(subject.programCounter.value().toString()).toEqual("0001")
})
test("toggles the instruction pointer", () => {
subject.tick()
expect(subject.instructionPointer.value().toString()).toEqual("1")
subject.tick()
expect(subject.instructionPointer.value().toString()).toEqual("0")
})
}
function toWordArray(values: string[]) {
return values.map(wordFromString) as Word<4>[]
}
function wordsToStrings(values: Word<number>[]) {
return values.map(value => value.toString())
}