aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-15 17:57:45 -0500
committerAustin Adams <git@austinjadams.com>2019-01-15 17:57:45 -0500
commitcb91d9cd3ff68a7ea78b315fa1a204bbccf90508 (patch)
tree9dffc41b0e383d99d5d72d2ba48dde1857e99413
parenta3625b526b7a229e1f79267b722afa7c2a73b507 (diff)
downloadnovice-cb91d9cd3ff68a7ea78b315fa1a204bbccf90508.tar.gz
novice-cb91d9cd3ff68a7ea78b315fa1a204bbccf90508.tar.xz
Add simulator
-rw-r--r--novice/assembler/index.ts13
-rw-r--r--novice/assembler/opspec/complx.ts2
-rw-r--r--novice/isa/index.ts16
-rw-r--r--novice/isa/io.ts6
-rw-r--r--novice/isa/isa.ts7
-rw-r--r--novice/isa/lc3.ts85
-rw-r--r--novice/isa/state.ts7
-rw-r--r--novice/simulator/index.ts2
-rw-r--r--novice/simulator/loaders/index.ts8
-rw-r--r--novice/simulator/simulator.test.ts90
-rw-r--r--novice/simulator/simulator.ts196
11 files changed, 381 insertions, 51 deletions
diff --git a/novice/assembler/index.ts b/novice/assembler/index.ts
index 848df0e..42dcfcf 100644
--- a/novice/assembler/index.ts
+++ b/novice/assembler/index.ts
@@ -1,4 +1,4 @@
-import { Isa, isas } from '../isa';
+import { getIsa, Isa, isas } from '../isa';
import { Assembler, AssemblerConfig } from './assembler';
import { BaseMachineCodeGenerator, MachineCodeGenerator } from './codegen';
import { configs } from './configs';
@@ -19,14 +19,6 @@ function getGenerator(): MachineCodeGenerator {
return new BaseMachineCodeGenerator();
}
-function getIsa(isaName: string): Isa {
- if (!isas.hasOwnProperty(isaName)) {
- throw new Error(`no such isa \`${isaName}'\n`);
- }
-
- return isas[isaName];
-}
-
function getOpSpec(opSpecName: string): PseudoOpSpec {
if (!opSpecs.hasOwnProperty(opSpecName)) {
throw new Error(`no such opspec \`${opSpecName}'\n`);
@@ -59,4 +51,5 @@ function getConfig(configName: string): AssemblerConfig {
};
}
-export { Assembler, AssemblerConfig, getParser, getGenerator, getIsa, getOpSpec, getSerializer, getConfig };
+export { Assembler, AssemblerConfig, getParser, getGenerator,
+ getOpSpec, getSerializer, getConfig };
diff --git a/novice/assembler/opspec/complx.ts b/novice/assembler/opspec/complx.ts
index 102015d..85bd4bc 100644
--- a/novice/assembler/opspec/complx.ts
+++ b/novice/assembler/opspec/complx.ts
@@ -24,7 +24,7 @@ const complxOpSpec: PseudoOpSpec = {
{name: 'blkw',
operands: [{kind: 'int', name: 'count'}],
asm: (ctx: AsmContext, operands: OpOperands) => {
- // TODO: needs to be randomized
+ // TODO: needs to be randomized/uninitialized
const result = new Array<number>(operands.ints.count);
for (let i = 0; i < result.length; i++) {
result[i] = 0;
diff --git a/novice/isa/index.ts b/novice/isa/index.ts
index 53a5623..bc9e1f5 100644
--- a/novice/isa/index.ts
+++ b/novice/isa/index.ts
@@ -1,9 +1,19 @@
-import { Instruction, Isa } from './isa';
+import { IO } from './io';
+import { Fields, Instruction, Isa, Reg } from './isa';
import { Lc3Isa } from './lc3';
-import { MachineState, MachineStateUpdate } from './state';
+import { MachineState, MachineStateUpdate, RegIdentifier } from './state';
const isas: {[s: string]: Isa} = {
lc3: Lc3Isa,
};
-export { Isa, Instruction, isas, Lc3Isa, MachineState, MachineStateUpdate };
+function getIsa(isaName: string): Isa {
+ if (!isas.hasOwnProperty(isaName)) {
+ throw new Error(`no such isa \`${isaName}'\n`);
+ }
+
+ return isas[isaName];
+}
+
+export { Isa, Instruction, isas, Lc3Isa, MachineState, MachineStateUpdate,
+ RegIdentifier, Fields, Reg, getIsa, IO };
diff --git a/novice/isa/io.ts b/novice/isa/io.ts
new file mode 100644
index 0000000..8ff9578
--- /dev/null
+++ b/novice/isa/io.ts
@@ -0,0 +1,6 @@
+interface IO {
+ getc(): number;
+ putc(c: number): void;
+}
+
+export { IO };
diff --git a/novice/isa/isa.ts b/novice/isa/isa.ts
index f3dcbaf..373c96c 100644
--- a/novice/isa/isa.ts
+++ b/novice/isa/isa.ts
@@ -1,3 +1,4 @@
+import { IO } from './io';
import { MachineState, MachineStateUpdate, RegIdentifier } from './state';
interface Pc {
@@ -16,6 +17,7 @@ interface Mem {
interface RegSolo {
kind: 'reg';
name: string;
+ sext: boolean;
bits: number;
}
@@ -23,6 +25,7 @@ interface RegRange {
kind: 'reg-range';
count: number;
prefix: string;
+ sext: boolean;
bits: number;
}
@@ -59,7 +62,7 @@ interface Fields {
interface Instruction {
op: string;
fields: Field[];
- sim: (state: MachineState, ir: Fields) => MachineStateUpdate[];
+ sim: (state: MachineState, io: IO, ir: Fields) => MachineStateUpdate[];
}
interface Isa {
@@ -69,4 +72,4 @@ interface Isa {
instructions: Instruction[];
}
-export { Isa, Fields, Instruction };
+export { Isa, Fields, Instruction, Reg };
diff --git a/novice/isa/lc3.ts b/novice/isa/lc3.ts
index 6ea91d9..089f026 100644
--- a/novice/isa/lc3.ts
+++ b/novice/isa/lc3.ts
@@ -1,3 +1,4 @@
+import { IO } from './io';
import { Fields, Isa } from './isa';
import { MachineState, MachineStateUpdate } from './state';
@@ -31,8 +32,8 @@ const Lc3Isa: Isa = {
addressability: 16,
},
regs: [
- {kind: 'reg', name: 'cc', bits: 3},
- {kind: 'reg-range', count: 8, prefix: 'r', bits: 16},
+ {kind: 'reg', name: 'cc', sext: false, bits: 3},
+ {kind: 'reg-range', count: 8, prefix: 'r', sext: true, bits: 16},
],
instructions: [
{op: 'add', fields: [
@@ -42,7 +43,7 @@ const Lc3Isa: Isa = {
{kind: 'const', bits: [ 5, 3], val: 0b000},
{kind: 'reg', bits: [ 2, 0], prefix: 'r', name: 'sr2'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr, val: state.reg(ir.regs.sr1) +
state.reg(ir.regs.sr2)}]),
},
@@ -55,7 +56,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 4, 0], sext: true, label: false,
name: 'imm5'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr, val: state.reg(ir.regs.sr1) +
ir.imms.imm5}]),
},
@@ -67,7 +68,7 @@ const Lc3Isa: Isa = {
{kind: 'const', bits: [ 5, 3], val: 0b000},
{kind: 'reg', bits: [ 2, 0], prefix: 'r', name: 'sr2'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr, val: state.reg(ir.regs.sr1) &
state.reg(ir.regs.sr2)}]),
},
@@ -80,7 +81,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 4, 0], sext: true, label: false,
name: 'imm5'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr, val: state.reg(ir.regs.sr1) &
ir.imms.imm5}]),
},
@@ -88,7 +89,7 @@ const Lc3Isa: Isa = {
{op: 'nop', fields: [
{kind: 'const', bits: [15, 0], val: 0x0000},
],
- sim: (state: MachineState, ir: Fields) => [],
+ sim: (state: MachineState, io: IO, ir: Fields) => [],
},
{op: 'br', fields: [
@@ -96,7 +97,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}],
},
@@ -105,7 +106,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}],
},
@@ -114,7 +115,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
(state.reg('cc') & 0b001)
? [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}]
: [],
@@ -125,7 +126,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
(state.reg('cc') & 0b010)
? [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}]
: [],
@@ -136,7 +137,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
(state.reg('cc') & 0b011)
? [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}]
: [],
@@ -147,7 +148,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
(state.reg('cc') & 0b100)
? [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}]
: [],
@@ -158,7 +159,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
(state.reg('cc') & 0b101)
? [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}]
: [],
@@ -169,7 +170,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
(state.reg('cc') & 0b110)
? [{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset9}]
: [],
@@ -180,14 +181,14 @@ const Lc3Isa: Isa = {
{kind: 'reg', bits: [ 8, 6], prefix: 'r', name: 'baser'},
{kind: 'const', bits: [ 5, 0], val: 0b000000},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'pc', where: state.reg(ir.regs.baser)}],
},
{op: 'ret', fields: [
{kind: 'const', bits: [15, 0], val: 0b1100000111000000},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'pc', where: state.reg(['r', 7])}],
},
@@ -196,7 +197,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [10, 0], sext: true, label: true,
name: 'pcoffset11'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'pc', where: state.pc + 1 + ir.imms.pcoffset11},
{kind: 'reg', reg: ['r', 7], val: state.pc + 1}]),
},
@@ -206,7 +207,7 @@ const Lc3Isa: Isa = {
{kind: 'reg', bits: [ 8, 6], prefix: 'r', name: 'baser'},
{kind: 'const', bits: [ 5, 0], val: 0b000000},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'pc', where: state.reg(ir.regs.baser)},
{kind: 'reg', reg: ['r', 7], val: state.pc + 1}]),
},
@@ -217,7 +218,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr,
val: state.load(state.pc + 1 + ir.imms.pcoffset9)}]),
},
@@ -228,7 +229,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr,
val: state.load(state.pc + 1 + ir.imms.pcoffset9)}]),
},
@@ -240,7 +241,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 5, 0], sext: true, label: false,
name: 'offset6'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr,
val: state.load(state.reg(ir.regs.baser) + 1 +
ir.imms.offset6)}]),
@@ -252,7 +253,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr,
val: state.pc + 1 + ir.imms.pcoffset9}]),
},
@@ -263,7 +264,7 @@ const Lc3Isa: Isa = {
{kind: 'reg', bits: [ 8, 6], prefix: 'r', name: 'sr'},
{kind: 'const', bits: [ 5, 0], val: 0b111111},
],
- sim: (state: MachineState, ir: Fields) => withCcUpdate(
+ sim: (state: MachineState, io: IO, ir: Fields) => withCcUpdate(
[{kind: 'reg', reg: ir.regs.dr, val: ~state.reg(ir.regs.sr)}]),
},
@@ -273,7 +274,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'mem', addr: state.pc + 1 + ir.imms.pcoffset9,
val: state.reg(ir.regs.sr)}],
},
@@ -284,7 +285,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 8, 0], sext: true, label: true,
name: 'pcoffset9'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'mem',
addr: state.load(state.pc + 1 + ir.imms.pcoffset9),
val: state.reg(ir.regs.sr)}],
@@ -297,7 +298,7 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 5, 0], sext: true, label: false,
name: 'offset6'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'mem',
addr: state.reg(ir.regs.baser) + ir.imms.offset6,
val: state.reg(ir.regs.sr)}],
@@ -308,40 +309,56 @@ const Lc3Isa: Isa = {
{kind: 'imm', bits: [ 7, 0], sext: false, label: false,
name: 'trapvect8'},
],
- sim: (state: MachineState, ir: Fields) =>
+ sim: (state: MachineState, io: IO, ir: Fields) =>
[{kind: 'reg', reg: ['r', 7], val: state.pc + 1},
{kind: 'pc', where: state.load(ir.imms.trapvect8)}],
},
- // TODO: implement these
{op: 'getc', fields: [
{kind: 'const', bits: [15, 0], val: 0b1111000000100000},
],
- sim: (state: MachineState, ir: Fields) => [],
+ // TODO: don't echo
+ sim: (state: MachineState, io: IO, ir: Fields) =>
+ [{kind: 'reg', reg: ['r', 0], val: io.getc()}],
},
{op: 'out', fields: [
{kind: 'const', bits: [15, 0], val: 0b1111000000100001},
],
- sim: (state: MachineState, ir: Fields) => [],
+ sim: (state: MachineState, io: IO, ir: Fields) => {
+ io.putc(state.reg(['r', 0]));
+ return [];
+ },
},
{op: 'puts', fields: [
{kind: 'const', bits: [15, 0], val: 0b1111000000100010},
],
- sim: (state: MachineState, ir: Fields) => [],
+ // TODO: stop after X chars if we don't hit a null word
+ sim: (state: MachineState, io: IO, ir: Fields) => {
+ let addr = state.reg(['r', 0]);
+ let c;
+ while (c = state.load(addr++)) {
+ io.putc(c);
+ }
+ return [];
+ },
},
{op: 'in', fields: [
{kind: 'const', bits: [15, 0], val: 0b1111000000100011},
],
- sim: (state: MachineState, ir: Fields) => [],
+ sim: (state: MachineState, io: IO, ir: Fields) => {
+ io.putc('>'.charCodeAt(0));
+ io.putc(' '.charCodeAt(0));
+ return [{kind: 'reg', reg: ['r', 0], val: io.getc()}];
+ },
},
{op: 'halt', fields: [
{kind: 'const', bits: [15, 0], val: 0b1111000000100101},
],
- sim: (state: MachineState, ir: Fields) => [],
+ sim: (state: MachineState, io: IO, ir: Fields) => [{kind: 'halt'}],
},
],
};
diff --git a/novice/isa/state.ts b/novice/isa/state.ts
index 7e6c0d8..22a7373 100644
--- a/novice/isa/state.ts
+++ b/novice/isa/state.ts
@@ -27,8 +27,13 @@ interface MachineStatePcUpdate {
where: number;
}
+interface MachineStateHaltUpdate {
+ kind: 'halt';
+}
+
type MachineStateUpdate = MachineStateRegUpdate|
MachineStateMemUpdate|
- MachineStatePcUpdate;
+ MachineStatePcUpdate|
+ MachineStateHaltUpdate;
export { RegIdentifier, MachineState, MachineStateUpdate };
diff --git a/novice/simulator/index.ts b/novice/simulator/index.ts
new file mode 100644
index 0000000..6eac7c7
--- /dev/null
+++ b/novice/simulator/index.ts
@@ -0,0 +1,2 @@
+import { Simulator } from './simulator';
+export { Simulator };
diff --git a/novice/simulator/loaders/index.ts b/novice/simulator/loaders/index.ts
new file mode 100644
index 0000000..f84b0ea
--- /dev/null
+++ b/novice/simulator/loaders/index.ts
@@ -0,0 +1,8 @@
+import { ComplxObjectFileLoader } from './complx';
+import { Loader } from './loader';
+
+const loaders: {[s: string]: new() => Loader} = {
+ complx: ComplxObjectFileLoader,
+};
+
+export { Loader, loaders };
diff --git a/novice/simulator/simulator.test.ts b/novice/simulator/simulator.test.ts
new file mode 100644
index 0000000..29a998c
--- /dev/null
+++ b/novice/simulator/simulator.test.ts
@@ -0,0 +1,90 @@
+import { Simulator } from '.';
+import { IO, getIsa } from '../isa';
+
+describe('simulator', () => {
+ let stdin: string;
+ let stdout: string;
+ let sim: Simulator;
+
+ beforeEach(() => {
+ const io: IO = {
+ getc() {
+ if (stdin) {
+ const c = stdin.charCodeAt(0);
+ stdin = stdin.slice(1);
+ return c;
+ } else {
+ throw new Error('unexpected EOF on stdin');
+ }
+ },
+ putc(c: number) {
+ stdout += String.fromCharCode(c);
+ },
+ };
+
+ stdin = stdout = "";
+ sim = new Simulator(getIsa('lc3'), io);
+ });
+
+ describe('run()', () => {
+ it('executes a halt', () => {
+ sim.store(0x3000, 0xf025); // halt
+ sim.run();
+
+ expect(sim.halted).toBe(true);
+ expect(sim.pc).toEqual(0x3001);
+ });
+
+ it('adds two numbers', () => {
+ sim.store(0x3000, 0b0101001001100000); // and r1, r1, 0
+ sim.store(0x3001, 0b0101010010100000); // and r2, r2, 0
+ sim.store(0x3002, 0b0001001001101111); // add r1, r1, 15
+ sim.store(0x3003, 0b0001010010100011); // add r2, r2, 3
+ sim.store(0x3004, 0b0001111001000010); // add r7, r1, r2
+ sim.store(0x3005, 0xf025); // halt
+ sim.run();
+
+ expect(sim.halted).toBe(true);
+ expect(sim.pc).toEqual(0x3006);
+ expect(sim.regs).toEqual({
+ solo: {'cc': 0b001},
+ range: {'r': [
+ 0, 15, 3, 0,
+ 0, 0, 0, 18
+ ]},
+ });
+ });
+
+ it('executes hello world', () => {
+ sim.store(0x3000, 0b1110000000000010); // lea r0, 2
+ sim.store(0x3001, 0xf022); // puts
+ sim.store(0x3002, 0xf025); // halt
+ sim.store(0x3003, 'h'.charCodeAt(0)); // .stringz "hello world!\n"
+ sim.store(0x3004, 'e'.charCodeAt(0));
+ sim.store(0x3005, 'l'.charCodeAt(0));
+ sim.store(0x3006, 'l'.charCodeAt(0));
+ sim.store(0x3007, 'o'.charCodeAt(0));
+ sim.store(0x3008, ' '.charCodeAt(0));
+ sim.store(0x3009, 'w'.charCodeAt(0));
+ sim.store(0x300a, 'o'.charCodeAt(0));
+ sim.store(0x300b, 'r'.charCodeAt(0));
+ sim.store(0x300c, 'l'.charCodeAt(0));
+ sim.store(0x300d, 'd'.charCodeAt(0));
+ sim.store(0x300e, '!'.charCodeAt(0));
+ sim.store(0x300f, '\n'.charCodeAt(0));
+ sim.store(0x3010, 0);
+ sim.run();
+
+ expect(sim.halted).toBe(true);
+ expect(sim.pc).toEqual(0x3003);
+ expect(sim.regs).toEqual({
+ solo: {'cc': 0b001},
+ range: {'r': [
+ 0x3003, 0, 0, 0,
+ 0, 0, 0, 0,
+ ]},
+ });
+ expect(stdout).toEqual("hello world!\n");
+ });
+ });
+});
diff --git a/novice/simulator/simulator.ts b/novice/simulator/simulator.ts
index e69de29..4614c81 100644
--- a/novice/simulator/simulator.ts
+++ b/novice/simulator/simulator.ts
@@ -0,0 +1,196 @@
+import { Fields, Instruction, IO, Isa, Reg, RegIdentifier } from '../isa';
+
+class Simulator {
+ public pc: number;
+ public mem: {[addr: number]: number};
+ public regs: {
+ solo: {[name: string]: number};
+ range: {[prefix: string]: number[]};
+ };
+ public halted: boolean;
+ private isa: Isa;
+ private io: IO;
+
+ public constructor(isa: Isa, io: IO) {
+ this.isa = isa;
+ this.io = io;
+ this.pc = isa.pc.resetVector;
+ this.mem = {};
+ this.regs = {
+ solo: {},
+ range: {},
+ };
+ this.halted = false;
+
+ this.resetRegs();
+ }
+
+ public run(): void {
+ while (!this.halted) {
+ const ir = this.load(this.pc);
+ this.pc += this.isa.pc.increment;
+
+ const [instr, fields] = this.decode(ir);
+ // Don't pass the incremented PC
+ const state = {pc: this.pc - this.isa.pc.increment,
+ reg: this.reg.bind(this),
+ load: this.load.bind(this)};
+ const updates = instr.sim(state, this.io, fields);
+
+ for (const update of updates) {
+ switch (update.kind) {
+ case 'reg':
+ this.regSet(update.reg, update.val);
+ break;
+
+ case 'mem':
+ this.store(update.addr, update.val);
+ break;
+
+ case 'pc':
+ this.pc = update.where;
+ break;
+
+ case 'halt':
+ this.halted = true;
+ break;
+ }
+ }
+ }
+ }
+
+ public load(addr: number): number {
+ if (this.mem.hasOwnProperty(addr)) {
+ return this.mem[addr];
+ } else {
+ return 0;
+ }
+ }
+
+ public store(addr: number, val: number): void {
+ this.mem[addr] = val & ~(-1 << this.isa.mem.word);
+ }
+
+ public reg(id: RegIdentifier): number {
+ const reg = this.lookupRegSpec(id);
+ let val;
+
+ if (typeof id === 'string') {
+ val = this.regs.solo[id];
+ } else {
+ val = this.regs.range[id[0]][id[1]];
+ }
+
+ if (reg.sext && (val & (1 << (reg.bits - 1)))) {
+ val |= -1 << reg.bits;
+ }
+
+ return val;
+ }
+
+ public regSet(id: RegIdentifier, val: number) {
+ const reg = this.lookupRegSpec(id);
+
+ val &= ~(-1 << reg.bits);
+
+ if (typeof id === 'string') {
+ this.regs.solo[id] = val;
+ } else {
+ this.regs.range[id[0]][id[1]] = val;
+ }
+ }
+
+ private lookupRegSpec(id: RegIdentifier): Reg {
+ for (const reg of this.isa.regs) {
+ if (typeof id === 'string' && reg.kind === 'reg'
+ && reg.name === id ||
+ typeof id !== 'string' && reg.kind === 'reg-range'
+ && id[0] === reg.prefix) {
+ return reg;
+ }
+ }
+
+ throw new Error(`unknown register identifier ${id}`);
+ }
+
+ private decode(ir: number): [Instruction, Fields] {
+ // TODO: ridiculously inefficient. idea for improvement: binary
+ // tree of depth like 8 to cut down on iteration time
+ const matches = [];
+
+ for (const instr of this.isa.instructions) {
+ let mask = 0;
+ let val = 0;
+ let totalBits = 0;
+
+ for (const field of instr.fields) {
+ if (field.kind !== 'const') {
+ continue;
+ }
+
+ const numBits = field.bits[0] - field.bits[1] + 1;
+ const babymask = ~(-1 << numBits);
+ mask |= babymask << field.bits[1];
+ val |= (field.val & babymask) << field.bits[1];
+ totalBits += numBits;
+ }
+
+ if ((ir & mask) === val) {
+ matches.push({bits: totalBits, instr});
+ }
+ }
+
+ if (!matches.length) {
+ throw new Error(`cannot decode instruction 0x${ir.toString(16)}`);
+ }
+
+ matches.sort((left, right) => right.bits - left.bits);
+
+ const instruction = matches[0].instr;
+ return [instruction, this.genFields(ir, instruction)];
+ }
+
+ private genFields(ir: number, instr: Instruction): Fields {
+ const fields: Fields = {regs: {}, imms: {}};
+
+ for (const field of instr.fields) {
+ if (field.kind === 'const') {
+ continue;
+ }
+
+ const numBits = field.bits[0] - field.bits[1] + 1;
+ let val = (ir >> field.bits[1]) & ~(-1 << numBits);
+
+ if (field.kind === 'reg') {
+ fields.regs[field.name] = [field.prefix, val];
+ } else if (field.kind === 'imm') {
+ // TODO: probs should be helper function
+ if (field.sext && (val & (1 << (numBits - 1)))) {
+ val |= -1 << numBits;
+ }
+ fields.imms[field.name] = val;
+ }
+ }
+
+ return fields;
+ }
+
+ private resetRegs(): void {
+ for (const reg of this.isa.regs) {
+ switch (reg.kind) {
+ case 'reg':
+ this.regs.solo[reg.name] = 0;
+ break;
+
+ case 'reg-range':
+ this.regs.range[reg.prefix] = new Array<number>(reg.count);
+ for (let i = 0; i < reg.count; i++) {
+ this.regs.range[reg.prefix][i] = 0;
+ }
+ break;
+ }
+ }
+ }
+}
+
+export { Simulator };