199 lines
6.4 KiB
TypeScript
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())
|
|
} |