aboutsummaryrefslogtreecommitdiffgithub
diff options
context:
space:
mode:
authorAustin Adams <git@austinjadams.com>2019-01-15 00:50:10 -0500
committerAustin Adams <git@austinjadams.com>2019-01-15 00:50:10 -0500
commit81028db3dfd663cef543aef16c418ef13cfe5e05 (patch)
tree9f1f01a11f5679ada45db44ed688ab0c645363f4
parent4f521f30d9fa9de0d2bf2dffef7693f2dee3b3e7 (diff)
downloadnovice-81028db3dfd663cef543aef16c418ef13cfe5e05.tar.gz
novice-81028db3dfd663cef543aef16c418ef13cfe5e05.tar.xz
Add complx loader
-rw-r--r--novice/isa/state.ts6
-rw-r--r--novice/simulator/loaders/complx.test.ts172
-rw-r--r--novice/simulator/loaders/complx.ts93
-rw-r--r--novice/simulator/loaders/loader.ts9
-rw-r--r--novice/simulator/mem.ts6
-rw-r--r--novice/simulator/simulator.ts0
6 files changed, 283 insertions, 3 deletions
diff --git a/novice/isa/state.ts b/novice/isa/state.ts
index 1105d53..7e6c0d8 100644
--- a/novice/isa/state.ts
+++ b/novice/isa/state.ts
@@ -2,12 +2,12 @@
type RegIdentifier = string|[string, number];
interface MachineState {
- // Auto sign-extends and everything
- reg: (reg: RegIdentifier) => number;
// Address of current instruction. NOT incremented!
pc: number;
+ // Auto sign-extends and everything
+ reg(reg: RegIdentifier): number;
// memory accesses
- load: (addr: number) => number;
+ load(addr: number): number;
}
interface MachineStateRegUpdate {
diff --git a/novice/simulator/loaders/complx.test.ts b/novice/simulator/loaders/complx.test.ts
new file mode 100644
index 0000000..55556c2
--- /dev/null
+++ b/novice/simulator/loaders/complx.test.ts
@@ -0,0 +1,172 @@
+import { isas } from '../../isa';
+import { Loader } from './loader';
+import { ComplxObjectFileLoader } from './complx';
+import { Readable } from 'stream';
+import { Memory } from '../mem';
+
+interface Mem extends Memory {
+ data: {[n: number]: number};
+}
+
+describe('complx loader', () => {
+ let mem: Mem;
+ let fp: Readable;
+ let loader: Loader;
+
+ beforeEach(() => {
+ mem = {
+ data: {},
+ load: (addr: number) =>
+ mem.data.hasOwnProperty(addr)? mem.data[addr] : 0,
+ store: (addr: number, val: number) =>
+ mem.data[addr] = val,
+ };
+ fp = new Readable();
+ loader = new ComplxObjectFileLoader();
+ });
+
+ describe('load()', () => {
+ it('loads trivial object file', () => {
+ fp.push(new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x01,
+ 0x13,0x37,
+ ]));
+ fp.push(null);
+
+ loader.load(isas.lc3, fp, mem);
+
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ });
+ });
+
+ it('loads larger object file', () => {
+ fp.push(new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x04,
+ 0x13,0x37,
+ 0x69,0x69,
+ 0xde,0xad,
+ 0xbe,0xef,
+ ]));
+ fp.push(null);
+
+ loader.load(isas.lc3, fp, mem);
+
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ 0x3001: 0x6969,
+ 0x3002: 0xdead,
+ 0x3003: 0xbeef,
+ });
+ });
+
+ it('loads multiple sections', () => {
+ fp.push(new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x02,
+ 0x13,0x37,
+ 0x69,0x69,
+
+ 0x40,0x00,
+ 0x00,0x03,
+ 0x04,0x20,
+ 0xde,0xad,
+ 0xbe,0xef,
+ ]));
+ fp.push(null);
+
+ loader.load(isas.lc3, fp, mem);
+
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ 0x3001: 0x6969,
+ 0x4000: 0x0420,
+ 0x4001: 0xdead,
+ 0x4002: 0xbeef,
+ });
+ });
+
+ it('loads empty section', () => {
+ fp.push(new Uint8Array([
+ 0x40,0x00,
+ 0x00,0x00,
+ 0x30,0x00,
+ 0x00,0x01,
+ 0x69,0x69,
+ ]));
+ fp.push(null);
+
+ loader.load(isas.lc3, fp, mem);
+
+ expect(mem.data).toEqual({
+ 0x3000: 0x6969,
+ });
+ });
+
+ it('loads flaky stream', () => {
+ fp = new Readable({
+ objectMode: true,
+ });
+ fp.push(Buffer.from([0x30]));
+ fp.push(Buffer.from([0x00]));
+ fp.push(Buffer.from([0x00]));
+ fp.push(Buffer.from([0x05]));
+ fp.push(Buffer.from([0x13, 0x37]));
+ fp.push(Buffer.from([0x69, 0x69, 0x04]));
+ fp.push(Buffer.from([0x20]));
+ fp.push(Buffer.from([0xde, 0xad, 0xbe]));
+ fp.push(Buffer.from([0xef]));
+ fp.push(null);
+
+ loader.load(isas.lc3, fp, mem);
+
+ expect(mem.data).toEqual({
+ 0x3000: 0x1337,
+ 0x3001: 0x6969,
+ 0x3002: 0x0420,
+ 0x3003: 0xdead,
+ 0x3004: 0xbeef,
+ });
+ });
+
+ it('errors on too few words', () => {
+ fp.push(new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x02,
+ 0x13,0x37,
+ ]));
+ fp.push(null);
+
+ expect(() => {
+ loader.load(isas.lc3, fp, mem);
+ }).toThrow('expected 1 more words');
+ });
+
+ it('errors on odd number of bytes', () => {
+ fp.push(new Uint8Array([
+ 0x30,0x00,
+ 0x00,0x01,
+ 0x13,0x37,
+ 0x69,
+ ]));
+ fp.push(null);
+
+ expect(() => {
+ loader.load(isas.lc3, fp, mem);
+ }).toThrow('not divisible by 2');
+ });
+
+ it('errors on malformed object file', () => {
+ fp.push(new Uint8Array([
+ 0x40,0x00,
+ ]));
+ fp.push(null);
+
+ expect(() => {
+ loader.load(isas.lc3, fp, mem);
+ }).toThrow('unexpected end-of-file');
+ });
+ });
+});
diff --git a/novice/simulator/loaders/complx.ts b/novice/simulator/loaders/complx.ts
new file mode 100644
index 0000000..5656e58
--- /dev/null
+++ b/novice/simulator/loaders/complx.ts
@@ -0,0 +1,93 @@
+import { Buffer } from 'buffer';
+import { Readable } from 'stream';
+import { Isa } from '../../isa';
+import { Memory } from '../mem';
+import { Loader } from './loader';
+
+type State = 'addr'|'len'|'words';
+
+class ComplxObjectFileLoader implements Loader {
+ public load(isa: Isa, fp: Readable, mem: Memory): void {
+ const wordBytes = Math.ceil(isa.mem.word / 8);
+ const excessBuf = Buffer.alloc(wordBytes);
+ let excessLen = 0;
+ let pc = 0;
+ let wordsLeft = 0;
+
+ let state: State = 'addr';
+
+ while (true) {
+ const buf = fp.read();
+
+ if (!buf) {
+ break;
+ }
+
+ if (buf.length + excessLen < wordBytes) {
+ for (const byte of buf) {
+ excessBuf[excessLen++] = byte;
+ }
+ continue;
+ }
+
+ const excess = (excessLen + buf.length) % wordBytes;
+ const words = (excessLen + buf.length - excess) / wordBytes;
+
+ for (let i = 0; i < words; i++) {
+ let word = 0;
+
+ for (let j = 0; j < wordBytes; j++) {
+ const idx = i * wordBytes + j;
+ const byte = (idx < excessLen) ? excessBuf[idx]
+ : buf[idx - excessLen];
+ word |= byte << (8 * (wordBytes - j - 1));
+ }
+
+ switch (state) {
+ case 'addr':
+ pc = word;
+ state = 'len';
+ break;
+
+ case 'len':
+ if (word) {
+ wordsLeft = word;
+ state = 'words';
+ } else {
+ // If we don't expect any words, go ahead
+ // and skip to the next section
+ state = 'addr';
+ }
+ break;
+
+ case 'words':
+ mem.store(pc, word);
+ pc += Math.ceil(isa.mem.word / isa.mem.addressability);
+
+ if (!--wordsLeft) {
+ state = 'addr';
+ }
+ break;
+ }
+ }
+
+ for (let i = 0; i < excess; i++) {
+ excessBuf[i] = buf[buf.length - excess + i];
+ }
+ excessLen = excess;
+ }
+
+ if (excessLen) {
+ throw new Error(`number of bytes in object file is not ` +
+ `divisible by ${wordBytes}`);
+ }
+ if (state === 'len') {
+ throw new Error('unexpected end-of-file before section length');
+ }
+ if (state === 'words') {
+ throw new Error(`expected ${wordsLeft} more words`);
+ }
+ }
+}
+
+export { ComplxObjectFileLoader };
diff --git a/novice/simulator/loaders/loader.ts b/novice/simulator/loaders/loader.ts
new file mode 100644
index 0000000..208720b
--- /dev/null
+++ b/novice/simulator/loaders/loader.ts
@@ -0,0 +1,9 @@
+import { Readable } from 'stream';
+import { Isa } from '../../isa';
+import { Memory } from '../mem';
+
+interface Loader {
+ load(isa: Isa, fp: Readable, mem: Memory): void;
+}
+
+export { Loader };
diff --git a/novice/simulator/mem.ts b/novice/simulator/mem.ts
new file mode 100644
index 0000000..015c60d
--- /dev/null
+++ b/novice/simulator/mem.ts
@@ -0,0 +1,6 @@
+interface Memory {
+ load(addr: number): number;
+ store(addr: number, val: number): void;
+}
+
+export { Memory };
diff --git a/novice/simulator/simulator.ts b/novice/simulator/simulator.ts
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/novice/simulator/simulator.ts