diff options
author | Austin Adams <git@austinjadams.com> | 2019-01-15 17:57:45 -0500 |
---|---|---|
committer | Austin Adams <git@austinjadams.com> | 2019-01-15 17:57:45 -0500 |
commit | cb91d9cd3ff68a7ea78b315fa1a204bbccf90508 (patch) | |
tree | 9dffc41b0e383d99d5d72d2ba48dde1857e99413 | |
parent | a3625b526b7a229e1f79267b722afa7c2a73b507 (diff) | |
download | novice-cb91d9cd3ff68a7ea78b315fa1a204bbccf90508.tar.gz novice-cb91d9cd3ff68a7ea78b315fa1a204bbccf90508.tar.xz |
Add simulator
-rw-r--r-- | novice/assembler/index.ts | 13 | ||||
-rw-r--r-- | novice/assembler/opspec/complx.ts | 2 | ||||
-rw-r--r-- | novice/isa/index.ts | 16 | ||||
-rw-r--r-- | novice/isa/io.ts | 6 | ||||
-rw-r--r-- | novice/isa/isa.ts | 7 | ||||
-rw-r--r-- | novice/isa/lc3.ts | 85 | ||||
-rw-r--r-- | novice/isa/state.ts | 7 | ||||
-rw-r--r-- | novice/simulator/index.ts | 2 | ||||
-rw-r--r-- | novice/simulator/loaders/index.ts | 8 | ||||
-rw-r--r-- | novice/simulator/simulator.test.ts | 90 | ||||
-rw-r--r-- | novice/simulator/simulator.ts | 196 |
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 }; |