aboutsummaryrefslogtreecommitdiff
path: root/src/cpu
diff options
context:
space:
mode:
authorlironh2020-03-21 09:52:53 +0200
committerlironh2020-03-22 21:08:30 +0200
commit8934a7566a038a74464d3d8df9d04fd875e5b1d7 (patch)
treee131c263938081e6c89f39f141f3f20f6da8f851 /src/cpu
parentMerge pull request #19 from gfeun/main-execute-loop-optimization (diff)
downloadavr8js-8934a7566a038a74464d3d8df9d04fd875e5b1d7.tar.gz
avr8js-8934a7566a038a74464d3d8df9d04fd875e5b1d7.tar.bz2
avr8js-8934a7566a038a74464d3d8df9d04fd875e5b1d7.zip
refactor: added peripherals and cpu feature folders
Diffstat (limited to 'src/cpu')
-rw-r--r--src/cpu/cpu.spec.ts8
-rw-r--r--src/cpu/cpu.ts78
-rw-r--r--src/cpu/instruction.spec.ts837
-rw-r--r--src/cpu/instruction.ts726
-rw-r--r--src/cpu/interrupt.spec.ts19
-rw-r--r--src/cpu/interrupt.ts19
6 files changed, 1687 insertions, 0 deletions
diff --git a/src/cpu/cpu.spec.ts b/src/cpu/cpu.spec.ts
new file mode 100644
index 0000000..92df7ee
--- /dev/null
+++ b/src/cpu/cpu.spec.ts
@@ -0,0 +1,8 @@
+import { CPU } from './cpu';
+
+describe('cpu', () => {
+ it('should set initial value of SP to the last address of internal SRAM', () => {
+ const cpu = new CPU(new Uint16Array(1024), 0x1000);
+ expect(cpu.SP).toEqual(0x10ff);
+ });
+});
diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts
new file mode 100644
index 0000000..93f79d0
--- /dev/null
+++ b/src/cpu/cpu.ts
@@ -0,0 +1,78 @@
+/**
+ * AVR 8 CPU data structures
+ * Part of AVR8js
+ *
+ * Copyright (C) 2019, Uri Shaked
+ */
+
+import { u16, u8 } from '../types';
+
+const registerSpace = 0x100;
+
+// eslint-disable-next-line @typescript-eslint/interface-name-prefix
+export interface ICPU {
+ readonly data: Uint8Array;
+ readonly dataView: DataView;
+ readonly progMem: Uint16Array;
+ readonly progBytes: Uint8Array;
+ pc: u16;
+ cycles: number;
+
+ readData(addr: u16): u8;
+ writeData(addr: u16, value: u8): void;
+}
+
+export type CPUMemoryHook = (value: u8, oldValue: u8, addr: u16) => boolean | void;
+export interface CPUMemoryHooks {
+ [key: number]: CPUMemoryHook;
+}
+
+export class CPU implements ICPU {
+ readonly data: Uint8Array = new Uint8Array(this.sramBytes + registerSpace);
+ readonly data16 = new Uint16Array(this.data.buffer);
+ readonly dataView = new DataView(this.data.buffer);
+ readonly progBytes = new Uint8Array(this.progMem.buffer);
+ readonly writeHooks: CPUMemoryHooks = [];
+
+ pc = 0;
+ cycles = 0;
+
+ constructor(public progMem: Uint16Array, private sramBytes = 8192) {
+ this.reset();
+ }
+
+ reset() {
+ this.data.fill(0);
+ this.SP = this.data.length - 1;
+ }
+
+ readData(addr: number) {
+ return this.data[addr];
+ }
+
+ writeData(addr: number, value: number) {
+ const hook = this.writeHooks[addr];
+ if (hook) {
+ if (hook(value, this.data[addr], addr)) {
+ return;
+ }
+ }
+ this.data[addr] = value;
+ }
+
+ get SP() {
+ return this.dataView.getUint16(93, true);
+ }
+
+ set SP(value: number) {
+ this.dataView.setUint16(93, value, true);
+ }
+
+ get SREG() {
+ return this.data[95];
+ }
+
+ get interruptsEnabled() {
+ return this.SREG & 0x80 ? true : false;
+ }
+}
diff --git a/src/cpu/instruction.spec.ts b/src/cpu/instruction.spec.ts
new file mode 100644
index 0000000..2c5244e
--- /dev/null
+++ b/src/cpu/instruction.spec.ts
@@ -0,0 +1,837 @@
+import { CPU } from './cpu';
+import { avrInstruction } from './instruction';
+
+describe('avrInstruction', () => {
+ let cpu: CPU;
+
+ beforeEach(() => {
+ cpu = new CPU(new Uint16Array(0x8000));
+ });
+
+ function loadProgram(bytes: string) {
+ const progBuf = cpu.progBytes;
+ for (let i = 0; i < bytes.length; i += 2) {
+ progBuf[i / 2] = parseInt(bytes.substr(i, 2), 16);
+ }
+ }
+
+ it('should execute `ADC r0, r1` instruction when carry is on', () => {
+ loadProgram('011c');
+ cpu.data[0] = 10; // r0 <- 10
+ cpu.data[1] = 20; // r1 <- 20
+ cpu.data[95] = 0b00000001; // SREG <- -------C
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0]).toEqual(31);
+ expect(cpu.data[95]).toEqual(0); // SREG: --------
+ });
+
+ it('should execute `ADC r0, r1` instruction when carry is on and the result overflows', () => {
+ loadProgram('011c');
+ cpu.data[0] = 10; // r0 <- 10
+ cpu.data[1] = 245; // r1 <- 20
+ cpu.data[95] = 0b00000001; // SREG <- -------C
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0]).toEqual(0);
+ expect(cpu.data[95]).toEqual(0b00100011); // SREG: --H---ZC
+ });
+
+ it('should execute `BCLR 2` instruction', () => {
+ loadProgram('a894');
+ cpu.data[95] = 0xff; // SREG <- 0xff
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0xfb);
+ });
+
+ it('should execute `BLD r4, 7` instruction', () => {
+ loadProgram('47f8');
+ cpu.data[4] = 0x15; // r <- 0x15
+ cpu.data[95] = 0x40; // SREG <- 0x40
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[4]).toEqual(0x95);
+ expect(cpu.data[95]).toEqual(0x40);
+ });
+
+ it('should execute `BRBC 0, +8` instruction when SREG.C is clear', () => {
+ loadProgram('20f4');
+ cpu.data[95] = 0b00001000; // SREG: V
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1 + 8 / 2);
+ expect(cpu.cycles).toEqual(2);
+ });
+
+ it('should execute `BRBC 0, +8` instruction when SREG.C is set', () => {
+ loadProgram('20f4');
+ cpu.data[95] = 0b00000001; // SREG: C
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should execute `BRBS 3, 92` instruction when SREG.V is set', () => {
+ loadProgram('73f1');
+ cpu.data[95] = 0b00001000; // SREG: V
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1 + 92 / 2);
+ expect(cpu.cycles).toEqual(2);
+ });
+
+ it('should execute `BRBS 3, -4` instruction when SREG.V is set', () => {
+ loadProgram('0000f3f3');
+ cpu.data[95] = 0b00001000; // SREG: V
+ avrInstruction(cpu);
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(3); // 1 for NOP, 2 for BRBS
+ });
+
+ it('should execute `BRBS 3, -4` instruction when SREG.V is clear', () => {
+ loadProgram('f3f3');
+ cpu.data[95] = 0x0; // SREG <- 0x0
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should execute `CBI 0x0c, 5`', () => {
+ loadProgram('6598');
+ cpu.data[0x2c] = 0b11111111;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x2c]).toEqual(0b11011111);
+ });
+
+ it('should execute `CALL` instruction', () => {
+ loadProgram('0e945c00');
+ cpu.data[94] = 0;
+ cpu.data[93] = 150; // SP <- 50
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x5c);
+ expect(cpu.cycles).toEqual(5);
+ expect(cpu.data[150]).toEqual(2); // return addr
+ expect(cpu.data[93]).toEqual(148); // SP should be decremented
+ });
+
+ it('should execute `CPC r27, r18` instruction', () => {
+ loadProgram('b207');
+ cpu.data[18] = 0x1;
+ cpu.data[27] = 0x1;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0); // SREG clear
+ });
+
+ it('should execute `CPC r24, r1` instruction and set', () => {
+ loadProgram('8105');
+ cpu.data[1] = 0; // r1 <- 0
+ cpu.data[24] = 0; // r24 <- 0
+ cpu.data[95] = 0b10000001; // SREG: I-------C
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0b10110101); // SREG: I-HS-N-C
+ });
+
+ it('should execute `CPI r26, 0x9` instruction', () => {
+ loadProgram('a930');
+ cpu.data[26] = 0x8;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0b00110101); // SREG: HSNC
+ });
+
+ it('should execute `CPSE r2, r3` when r2 != r3', () => {
+ loadProgram('2310');
+ cpu.data[2] = 10; // r2 <- 10
+ cpu.data[3] = 11; // r3 <- 11
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should execute `CPSE r2, r3` when r2 == r3', () => {
+ loadProgram('23101c92');
+ cpu.data[2] = 10; // r2 <- 10
+ cpu.data[3] = 10; // r3 <- 10
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(2);
+ expect(cpu.cycles).toEqual(2);
+ });
+
+ it('should execute `CPSE r2, r3` when r2 == r3 and followed by 2-word instruction', () => {
+ loadProgram('23100e945c00');
+ cpu.data[2] = 10; // r2 <- 10
+ cpu.data[3] = 10; // r3 <- 10
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(3);
+ expect(cpu.cycles).toEqual(3);
+ });
+
+ it('should execute `ICALL` instruction', () => {
+ loadProgram('0995');
+ cpu.data[94] = 0;
+ cpu.data[93] = 0x80;
+ cpu.dataView.setUint16(30, 0x2020, true); // Z <- 0x2020
+ avrInstruction(cpu);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.pc).toEqual(0x2020);
+ expect(cpu.data[0x80]).toEqual(1); // Return address
+ expect(cpu.data[93]).toEqual(0x7e);
+ });
+
+ it('should execute `IJMP` instruction', () => {
+ loadProgram('0994');
+ cpu.dataView.setUint16(30, 0x1040, true); // Z <- 0x1040
+ avrInstruction(cpu);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.pc).toEqual(0x1040);
+ });
+
+ it('should execute `IN r5, 0xb` instruction', () => {
+ loadProgram('5bb0');
+ cpu.data[0x2b] = 0xaf;
+ avrInstruction(cpu);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.data[5]).toEqual(0xaf);
+ });
+
+ it('should execute `INC r5` instruction', () => {
+ loadProgram('5394');
+ cpu.data[5] = 0x7f;
+ avrInstruction(cpu);
+ expect(cpu.data[5]).toEqual(0x80);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0b00001100); // SREG: NV
+ });
+
+ it('should execute `INC r5` instruction when r5 == 0xff', () => {
+ loadProgram('5394');
+ cpu.data[5] = 0xff;
+ avrInstruction(cpu);
+ expect(cpu.data[5]).toEqual(0);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0b00000010); // SREG: Z
+ });
+
+ it('should execute `JMP 0xb8` instruction', () => {
+ loadProgram('0c945c00');
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x5c);
+ expect(cpu.cycles).toEqual(3);
+ });
+
+ it('should execute `LAC r19` instruction', () => {
+ loadProgram('3693');
+ cpu.data[19] = 0x02; // r19 <- 0x02
+ cpu.dataView.setUint16(30, 0x100, true); // Z <- 0x100
+ cpu.data[0x100] = 0x96;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[19]).toEqual(0x96);
+ expect(cpu.dataView.getUint16(30, true)).toEqual(0x100);
+ expect(cpu.data[0x100]).toEqual(0x94);
+ });
+
+ it('should execute `LAS r17` instruction', () => {
+ loadProgram('1593');
+ cpu.data[17] = 0x11; // r17 <- 0x11
+ cpu.data[30] = 0x80; // Z <- 0x80
+ cpu.data[0x80] = 0x44;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[17]).toEqual(0x44);
+ expect(cpu.data[30]).toEqual(0x80);
+ expect(cpu.data[0x80]).toEqual(0x55);
+ });
+
+ it('should execute `LAT r0` instruction', () => {
+ loadProgram('0792');
+ cpu.data[0] = 0x33; // r0 <- 0x33
+ cpu.data[30] = 0x80; // Z <- 0x80
+ cpu.data[0x80] = 0x66;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0]).toEqual(0x66);
+ expect(cpu.data[30]).toEqual(0x80);
+ expect(cpu.data[0x80]).toEqual(0x55);
+ });
+
+ it('should execute `LDI r28, 0xff` instruction', () => {
+ loadProgram('cfef');
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[28]).toEqual(0xff);
+ });
+
+ it('should execute `LDS r5, 0x150` instruction', () => {
+ loadProgram('50905001');
+ cpu.data[0x150] = 0x7a;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x2);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[5]).toEqual(0x7a);
+ });
+
+ it('should execute `LD r1, X` instruction', () => {
+ loadProgram('1c90');
+ cpu.data[0xc0] = 0x15;
+ cpu.data[26] = 0xc0; // X <- 0xc0
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[1]).toEqual(0x15);
+ expect(cpu.data[26]).toEqual(0xc0); // verify that X was unchanged
+ });
+
+ it('should execute `LD r17, X+` instruction', () => {
+ loadProgram('1d91');
+ cpu.data[0xc0] = 0x15;
+ cpu.data[26] = 0xc0; // X <- 0xc0
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[17]).toEqual(0x15);
+ expect(cpu.data[26]).toEqual(0xc1); // verify that X was incremented
+ });
+
+ it('should execute `LD r1, -X` instruction', () => {
+ loadProgram('1e90');
+ cpu.data[0x98] = 0x22;
+ cpu.data[26] = 0x99; // X <- 0x99
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[1]).toEqual(0x22);
+ expect(cpu.data[26]).toEqual(0x98); // verify that X was decremented
+ });
+
+ it('should execute `LD r8, Y` instruction', () => {
+ loadProgram('8880');
+ cpu.data[0xc0] = 0x15;
+ cpu.data[28] = 0xc0; // Y <- 0xc0
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[8]).toEqual(0x15);
+ expect(cpu.data[28]).toEqual(0xc0); // verify that Y was unchanged
+ });
+
+ it('should execute `LD r3, Y+` instruction', () => {
+ loadProgram('3990');
+ cpu.data[0xc0] = 0x15;
+ cpu.data[28] = 0xc0; // Y <- 0xc0
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[3]).toEqual(0x15);
+ expect(cpu.data[28]).toEqual(0xc1); // verify that Y was incremented
+ });
+
+ it('should execute `LD r0, -Y` instruction', () => {
+ loadProgram('0a90');
+ cpu.data[0x98] = 0x22;
+ cpu.data[28] = 0x99; // Y <- 0x99
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[0]).toEqual(0x22);
+ expect(cpu.data[28]).toEqual(0x98); // verify that Y was decremented
+ });
+
+ it('should execute `LDD r4, Y+2` instruction', () => {
+ loadProgram('4a80');
+ cpu.data[0x82] = 0x33;
+ cpu.data[28] = 0x80; // Y <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[4]).toEqual(0x33);
+ expect(cpu.data[28]).toEqual(0x80); // verify that Y was unchanged
+ });
+
+ it('should execute `LD r5, Z` instruction', () => {
+ loadProgram('5080');
+ cpu.data[0xcc] = 0xf5;
+ cpu.data[30] = 0xcc; // Z <- 0xcc
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[5]).toEqual(0xf5);
+ expect(cpu.data[30]).toEqual(0xcc); // verify that Z was unchanged
+ });
+
+ it('should execute `LD r7, Z+` instruction', () => {
+ loadProgram('7190');
+ cpu.data[0xc0] = 0x25;
+ cpu.data[30] = 0xc0; // Z <- 0xc0
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[7]).toEqual(0x25);
+ expect(cpu.data[30]).toEqual(0xc1); // verify that Y was incremented
+ });
+
+ it('should execute `LD r0, -Z` instruction', () => {
+ loadProgram('0290');
+ cpu.data[0x9e] = 0x66;
+ cpu.data[30] = 0x9f; // Z <- 0x9f
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[0]).toEqual(0x66);
+ expect(cpu.data[30]).toEqual(0x9e); // verify that Y was decremented
+ });
+
+ it('should execute `LDD r15, Z+31` instruction', () => {
+ loadProgram('f78c');
+ cpu.data[0x9f] = 0x33;
+ cpu.data[30] = 0x80; // Z <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[15]).toEqual(0x33);
+ expect(cpu.data[30]).toEqual(0x80); // verify that Z was unchanged
+ });
+
+ it('should execute `LPM` instruction', () => {
+ loadProgram('c895');
+ cpu.progMem[0x40] = 0xa0;
+ cpu.data[30] = 0x80; // Z <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[0]).toEqual(0xa0);
+ expect(cpu.data[30]).toEqual(0x80); // verify that Z was unchanged
+ });
+
+ it('should execute `LPM r2` instruction', () => {
+ loadProgram('2490');
+ cpu.progMem[0x40] = 0xa0;
+ cpu.data[30] = 0x80; // Z <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[2]).toEqual(0xa0);
+ expect(cpu.data[30]).toEqual(0x80); // verify that Z was unchanged
+ });
+
+ it('should execute `LPM r1, Z+` instruction', () => {
+ loadProgram('1590');
+ cpu.progMem[0x40] = 0xa0;
+ cpu.data[30] = 0x80; // Z <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(3);
+ expect(cpu.data[1]).toEqual(0xa0);
+ expect(cpu.data[30]).toEqual(0x81); // verify that Z was incremented
+ });
+
+ it('should execute `LSR r7` instruction', () => {
+ loadProgram('7694');
+ cpu.data[7] = 0x45; // r7 <- 0x45
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[7]).toEqual(0x22);
+ expect(cpu.data[95]).toEqual(0b00011001); // SREG SVC
+ });
+
+ it('should execute `MOV r7, r8` instruction', () => {
+ loadProgram('782c');
+ cpu.data[8] = 0x45; // r7 <- 0x45
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[7]).toEqual(0x45);
+ });
+
+ it('should execute `MOVW r26, r22` instruction', () => {
+ loadProgram('db01');
+ cpu.data[22] = 0x45; // r22 <- 0x45
+ cpu.data[23] = 0x9a; // r23 <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[26]).toEqual(0x45);
+ expect(cpu.data[27]).toEqual(0x9a);
+ });
+
+ it('should execute `MUL r5, r6` instruction', () => {
+ loadProgram('569c');
+ cpu.data[5] = 100; // r5 <- 55
+ cpu.data[6] = 5; // r6 <- 5
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.dataView.getUint16(0, true)).toEqual(500);
+ expect(cpu.data[95]).toEqual(0b0); // SREG: 0
+ });
+
+ it('should execute `MUL r5, r6` instruction and update carry flag when numbers are big', () => {
+ loadProgram('569c');
+ cpu.data[5] = 200; // r5 <- 200
+ cpu.data[6] = 200; // r6 <- 200
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.dataView.getUint16(0, true)).toEqual(40000);
+ expect(cpu.data[95]).toEqual(0b00000001); // SREG: C
+ });
+
+ it('should execute `MUL r0, r1` and update the zero flag', () => {
+ loadProgram('019c');
+ cpu.data[0] = 0; // r0 <- 0
+ cpu.data[1] = 9; // r1 <- 9
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.dataView.getUint16(0, true)).toEqual(0);
+ expect(cpu.data[95]).toEqual(0b00000010); // SREG: Z
+ });
+
+ it('should execute `MULS r18, r19` instruction', () => {
+ loadProgram('2302');
+ cpu.data[18] = -5; // r18 <- -5
+ cpu.data[19] = 100; // r19 <- 100
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.dataView.getInt16(0, true)).toEqual(-500);
+ expect(cpu.data[95]).toEqual(0b00000001); // SREG: C
+ });
+
+ it('should execute `MULSU r16, r17` instruction', () => {
+ loadProgram('0103');
+ cpu.data[16] = -5; // r16 <- -5
+ cpu.data[17] = 200; // r17 <- 200
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.dataView.getInt16(0, true)).toEqual(-1000);
+ expect(cpu.data[95]).toEqual(0b00000001); // SREG: C
+ });
+
+ it('should execute `NEG r20` instruction', () => {
+ loadProgram('4195');
+ cpu.data[20] = 0x56; // r20 <- 0x56
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[20]).toEqual(0xaa);
+ expect(cpu.data[95]).toEqual(0b00010101); // SREG: NC
+ });
+
+ it('should execute `NOP` instruction', () => {
+ loadProgram('0000');
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should execute `OUT 0x3f, r1` instruction', () => {
+ loadProgram('1fbe');
+ cpu.data[1] = 0x5a; // r1 <- 0x5a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x5f]).toEqual(0x5a);
+ });
+
+ it('should execute `POP r26` instruction', () => {
+ loadProgram('af91');
+ cpu.data[94] = 0;
+ cpu.data[93] = 0xff; // SP <- 0xff
+ cpu.data[0x100] = 0x1a;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[26]).toEqual(0x1a);
+ expect(cpu.dataView.getUint16(93, true)).toEqual(0x100); // SP
+ });
+
+ it('should execute `PUSH r11` instruction', () => {
+ loadProgram('bf92');
+ cpu.data[11] = 0x2a;
+ cpu.data[94] = 0;
+ cpu.data[93] = 0xff; // SP <- 0xff
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0x1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0xff]).toEqual(0x2a);
+ expect(cpu.dataView.getUint16(93, true)).toEqual(0xfe); // SP
+ });
+
+ it('should execute `RCALL .+6` instruction', () => {
+ loadProgram('03d0');
+ cpu.data[94] = 0;
+ cpu.data[93] = 0x80; // SP <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(4);
+ expect(cpu.cycles).toEqual(4);
+ expect(cpu.dataView.getUint16(0x80, true)).toEqual(1); // RET address
+ expect(cpu.data[93]).toEqual(0x7e); // SP
+ });
+
+ it('should execute `RCALL .-4` instruction', () => {
+ loadProgram('0000fedf');
+ cpu.data[94] = 0;
+ cpu.data[93] = 0x80; // SP <- 0x80
+ avrInstruction(cpu);
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(0);
+ expect(cpu.cycles).toEqual(5); // 1 for NOP, 4 for RCALL
+ expect(cpu.dataView.getUint16(0x80, true)).toEqual(2); // RET address
+ expect(cpu.data[93]).toEqual(0x7e); // SP
+ });
+
+ it('should execute `RET` instruction', () => {
+ loadProgram('0895');
+ cpu.data[94] = 0;
+ cpu.data[93] = 0x90; // SP <- 0x90
+ cpu.data[0x92] = 16;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(16);
+ expect(cpu.cycles).toEqual(5);
+ expect(cpu.data[93]).toEqual(0x92); // SP should increment
+ });
+
+ it('should execute `RETI` instruction', () => {
+ loadProgram('1895');
+ cpu.data[94] = 0;
+ cpu.data[93] = 0xc0; // SP <- 0xc0
+ cpu.data[0xc2] = 200;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(200);
+ expect(cpu.cycles).toEqual(5);
+ expect(cpu.data[93]).toEqual(0xc2); // SP should increment
+ expect(cpu.data[95]).toEqual(0b10000000); // SREG: I
+ });
+
+ it('should execute `RJMP 2` instruction', () => {
+ loadProgram('01c0');
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(2);
+ expect(cpu.cycles).toEqual(2);
+ });
+
+ it('should execute `ROR r0` instruction', () => {
+ loadProgram('0794');
+ cpu.data[0] = 0x11; // r0 <- 0x11
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0]).toEqual(0x08); // r0 should be right-shifted
+ expect(cpu.data[95]).toEqual(0b00011001); // SREG: SVI
+ });
+
+ it('should execute `SBCI r23, 3`', () => {
+ loadProgram('7340');
+ cpu.data[23] = 3; // r23 <- 3
+ cpu.data[95] = 0b10000001; // SREG <- I------C
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[95]).toEqual(0b10110101); // SREG: I-HS-N-C
+ });
+
+ it('should execute `SBI 0x0c, 5`', () => {
+ loadProgram('659a');
+ cpu.data[0x2c] = 0b00001111;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0x2c]).toEqual(0b00101111);
+ });
+
+ it('should execute `SBIS 0x0c, 5` when bit is clear', () => {
+ loadProgram('659b1c92');
+ cpu.data[0x2c] = 0b00001111;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ });
+
+ it('should execute `SBIS 0x0c, 5` when bit is set', () => {
+ loadProgram('659b1c92');
+ cpu.data[0x2c] = 0b00101111;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(2);
+ expect(cpu.cycles).toEqual(2);
+ });
+
+ it('should execute `SBIS 0x0c, 5` when bit is set and followed by 2-word instruction', () => {
+ loadProgram('659b0e945c00');
+ cpu.data[0x2c] = 0b00101111;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(3);
+ expect(cpu.cycles).toEqual(3);
+ });
+
+ it('should execute `STS 0x151, r31` instruction', () => {
+ loadProgram('f0935101');
+ cpu.data[31] = 0x80; // r31 <- 0x80
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(2);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0x151]).toEqual(0x80);
+ });
+
+ it('should execute `ST X, r1` instruction', () => {
+ loadProgram('1c92');
+ cpu.data[1] = 0x5a; // r1 <- 0x5a
+ cpu.data[26] = 0x9a; // X <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x9a]).toEqual(0x5a);
+ expect(cpu.data[26]).toEqual(0x9a); // verify that X was unchanged
+ });
+
+ it('should execute `ST X+, r1` instruction', () => {
+ loadProgram('1d92');
+ cpu.data[1] = 0x5a; // r1 <- 0x5a
+ cpu.data[26] = 0x9a; // X <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x9a]).toEqual(0x5a);
+ expect(cpu.data[26]).toEqual(0x9b); // verify that X was incremented
+ });
+
+ it('should execute `ST -X, r17` instruction', () => {
+ loadProgram('1e93');
+ cpu.data[17] = 0x88; // r17 <- 0x88
+ cpu.data[26] = 0x99; // X <- 0x99
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0x98]).toEqual(0x88);
+ expect(cpu.data[26]).toEqual(0x98); // verify that X was decremented
+ });
+
+ it('should execute `ST Y, r2` instruction', () => {
+ loadProgram('2882');
+ cpu.data[2] = 0x5b; // r2 <- 0x5b
+ cpu.data[28] = 0x9a; // Y <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x9a]).toEqual(0x5b);
+ expect(cpu.data[28]).toEqual(0x9a); // verify that Y was unchanged
+ });
+
+ it('should execute `ST Y+, r1` instruction', () => {
+ loadProgram('1992');
+ cpu.data[1] = 0x5a; // r1 <- 0x5a
+ cpu.data[28] = 0x9a; // Y <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x9a]).toEqual(0x5a);
+ expect(cpu.data[28]).toEqual(0x9b); // verify that Y was incremented
+ });
+
+ it('should execute `ST -Y, r1` instruction', () => {
+ loadProgram('1a92');
+ cpu.data[1] = 0x5a; // r1 <- 0x5a
+ cpu.data[28] = 0x9a; // Y <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0x99]).toEqual(0x5a);
+ expect(cpu.data[28]).toEqual(0x99); // verify that Y was decremented
+ });
+
+ it('should execute `STD Y+17, r0` instruction', () => {
+ loadProgram('098a');
+ cpu.data[0] = 0xba; // r0 <- 0xba
+ cpu.data[28] = 0x9a; // Y <- 0x9a
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0x9a + 17]).toEqual(0xba);
+ expect(cpu.data[28]).toEqual(0x9a); // verify that Y was unchanged
+ });
+
+ it('should execute `ST Z, r16` instruction', () => {
+ loadProgram('0083');
+ cpu.data[16] = 0xdf; // r2 <- 0xdf
+ cpu.data[30] = 0x40; // Z <- 0x40
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x40]).toEqual(0xdf);
+ expect(cpu.data[30]).toEqual(0x40); // verify that Z was unchanged
+ });
+
+ it('should execute `ST Z+, r0` instruction', () => {
+ loadProgram('0192');
+ cpu.data[0] = 0x55; // r0 <- 0x55
+ cpu.dataView.setUint16(30, 0x155, true); // Z <- 0x155
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[0x155]).toEqual(0x55);
+ expect(cpu.dataView.getUint16(30, true)).toEqual(0x156); // verify that Z was incremented
+ });
+
+ it('should execute `ST -Z, r16` instruction', () => {
+ loadProgram('0293');
+ cpu.data[16] = 0x5a; // r16 <- 0x5a
+ cpu.data[30] = 0xff; // Z <- 0xff
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0xfe]).toEqual(0x5a);
+ expect(cpu.data[30]).toEqual(0xfe); // verify that Z was decremented
+ });
+
+ it('should execute `STD Z+1, r0` instruction', () => {
+ loadProgram('0182');
+ cpu.data[0] = 0xcc; // r0 <- 0xcc
+ cpu.data[30] = 0x50; // Z <- 0x50
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.data[0x51]).toEqual(0xcc);
+ expect(cpu.data[30]).toEqual(0x50); // verify that Z was unchanged
+ });
+
+ it('should execute `SWAP r1` instruction', () => {
+ loadProgram('1294');
+ cpu.data[1] = 0xa5; // r1 <- 0xa5
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[1]).toEqual(0x5a); // r1
+ });
+
+ it('should execute `XCH r21` instruction', () => {
+ loadProgram('5493');
+ cpu.data[21] = 0xa1; // r21 <- 0xa1
+ cpu.data[30] = 0x50; // Z <- 0x50
+ cpu.data[0x50] = 0xb9;
+ avrInstruction(cpu);
+ expect(cpu.pc).toEqual(1);
+ expect(cpu.cycles).toEqual(1);
+ expect(cpu.data[21]).toEqual(0xb9); // r21
+ expect(cpu.data[0x50]).toEqual(0xa1);
+ });
+});
diff --git a/src/cpu/instruction.ts b/src/cpu/instruction.ts
new file mode 100644
index 0000000..7e14aad
--- /dev/null
+++ b/src/cpu/instruction.ts
@@ -0,0 +1,726 @@
+/**
+ * AVR-8 Instruction Simulation
+ * Part of AVR8js
+ * Reference: http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf
+ *
+ * Copyright (C) 2019, Uri Shaked
+ */
+
+import { ICPU } from './cpu';
+import { u16 } from '../types';
+
+function isTwoWordInstruction(opcode: u16) {
+ return (
+ /* LDS */
+ (opcode & 0xfe0f) === 0x9000 ||
+ /* STS */
+ (opcode & 0xfe0f) === 0x9200 ||
+ /* CALL */
+ (opcode & 0xfe0e) === 0x940e ||
+ /* JMP */
+ (opcode & 0xfe0e) === 0x940c
+ );
+}
+
+export function avrInstruction(cpu: ICPU) {
+ const opcode = cpu.progMem[cpu.pc];
+
+ if ((opcode & 0xfc00) === 0x1c00) {
+ /* ADC, 0001 11rd dddd rrrr */
+ const d = cpu.data[(opcode & 0x1f0) >> 4];
+ const r = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ const sum = d + r + (cpu.data[95] & 1);
+ const R = sum & 255;
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= (R ^ r) & (d ^ R) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= sum & 256 ? 1 : 0;
+ sreg |= 1 & ((d & r) | (r & ~R) | (~R & d)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0xc00) {
+ /* ADD, 0000 11rd dddd rrrr */
+ const d = cpu.data[(opcode & 0x1f0) >> 4];
+ const r = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ const R = (d + r) & 255;
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= (R ^ r) & (R ^ d) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= (d + r) & 256 ? 1 : 0;
+ sreg |= 1 & ((d & r) | (r & ~R) | (~R & d)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xff00) === 0x9600) {
+ /* ADIW, 1001 0110 KKdd KKKK */
+ const addr = 2 * ((opcode & 0x30) >> 4) + 24;
+ const value = cpu.dataView.getUint16(addr, true);
+ const R = (value + ((opcode & 0xf) | ((opcode & 0xc0) >> 2))) & 0xffff;
+ cpu.dataView.setUint16(addr, R, true);
+ let sreg = cpu.data[95] & 0xe0;
+ sreg |= R ? 0 : 2;
+ sreg |= 0x8000 & R ? 4 : 0;
+ sreg |= ~value & R & 0x8000 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= ~R & value & 0x8000 ? 1 : 0;
+ cpu.data[95] = sreg;
+ cpu.cycles++;
+ } else if ((opcode & 0xfc00) === 0x2000) {
+ /* AND, 0010 00rd dddd rrrr */
+ const R = cpu.data[(opcode & 0x1f0) >> 4] & cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xf000) === 0x7000) {
+ /* ANDI, 0111 KKKK dddd KKKK */
+ const R = cpu.data[((opcode & 0xf0) >> 4) + 16] & ((opcode & 0xf) | ((opcode & 0xf00) >> 4));
+ cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfe0f) === 0x9405) {
+ /* ASR, 1001 010d dddd 0101 */
+ const value = cpu.data[(opcode & 0x1f0) >> 4];
+ const R = (value >>> 1) | (128 & value);
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xe0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= value & 1;
+ sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xff8f) === 0x9488) {
+ /* BCLR, 1001 0100 1sss 1000 */
+ cpu.data[95] &= ~(1 << ((opcode & 0x70) >> 4));
+ } else if ((opcode & 0xfe08) === 0xf800) {
+ /* BLD, 1111 100d dddd 0bbb */
+ const b = opcode & 7;
+ const d = (opcode & 0x1f0) >> 4;
+ cpu.data[d] = (~(1 << b) & cpu.data[d]) | (((cpu.data[95] >> 6) & 1) << b);
+ } else if ((opcode & 0xfc00) === 0xf400) {
+ /* BRBC, 1111 01kk kkkk ksss */
+ if (!(cpu.data[95] & (1 << (opcode & 7)))) {
+ cpu.pc = cpu.pc + (((opcode & 0x1f8) >> 3) - (opcode & 0x200 ? 0x40 : 0));
+ cpu.cycles++;
+ }
+ } else if ((opcode & 0xfc00) === 0xf000) {
+ /* BRBS, 1111 00kk kkkk ksss */
+ if (cpu.data[95] & (1 << (opcode & 7))) {
+ cpu.pc = cpu.pc + (((opcode & 0x1f8) >> 3) - (opcode & 0x200 ? 0x40 : 0));
+ cpu.cycles++;
+ }
+ } else if ((opcode & 0xff8f) === 0x9408) {
+ /* BSET, 1001 0100 0sss 1000 */
+ cpu.data[95] |= 1 << ((opcode & 0x70) >> 4);
+ } else if ((opcode & 0xfe08) === 0xfa00) {
+ /* BST, 1111 101d dddd 0bbb */
+ const d = cpu.data[(opcode & 0x1f0) >> 4];
+ const b = opcode & 7;
+ cpu.data[95] = (cpu.data[95] & 0xbf) | ((d >> b) & 1 ? 0x40 : 0);
+ } else if ((opcode & 0xfe0e) === 0x940e) {
+ /* CALL, 1001 010k kkkk 111k kkkk kkkk kkkk kkkk */
+ const k = cpu.progMem[cpu.pc + 1] | ((opcode & 1) << 16) | ((opcode & 0x1f0) << 13);
+ const ret = cpu.pc + 2;
+ const sp = cpu.dataView.getUint16(93, true);
+ cpu.data[sp] = 255 & ret;
+ cpu.data[sp - 1] = (ret >> 8) & 255;
+ cpu.dataView.setUint16(93, sp - 2, true);
+ cpu.pc = k - 1;
+ cpu.cycles += 4;
+ } else if ((opcode & 0xff00) === 0x9800) {
+ /* CBI, 1001 1000 AAAA Abbb */
+ const A = opcode & 0xf8;
+ const b = opcode & 7;
+ const R = cpu.readData((A >> 3) + 32);
+ cpu.writeData((A >> 3) + 32, R & ~(1 << b));
+ } else if ((opcode & 0xfe0f) === 0x9400) {
+ /* COM, 1001 010d dddd 0000 */
+ const d = (opcode & 0x1f0) >> 4;
+ const R = 255 - cpu.data[d];
+ cpu.data[d] = R;
+ let sreg = (cpu.data[95] & 0xe1) | 1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0x1400) {
+ /* CP, 0001 01rd dddd rrrr */
+ const val1 = cpu.data[(opcode & 0x1f0) >> 4];
+ const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ const R = val1 - val2;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= 0 !== ((val1 ^ val2) & (val1 ^ R) & 128) ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= val2 > val1 ? 1 : 0;
+ sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0x400) {
+ /* CPC, 0000 01rd dddd rrrr */
+ const arg1 = cpu.data[(opcode & 0x1f0) >> 4];
+ const arg2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ let sreg = cpu.data[95];
+ const r = arg1 - arg2 - (sreg & 1);
+ sreg = (sreg & 0xc0) | (!r && (sreg >> 1) & 1 ? 2 : 0) | (arg2 + (sreg & 1) > arg1 ? 1 : 0);
+ sreg |= 128 & r ? 4 : 0;
+ sreg |= (arg1 ^ arg2) & (arg1 ^ r) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= 1 & ((~arg1 & arg2) | (arg2 & r) | (r & ~arg1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xf000) === 0x3000) {
+ /* CPI, 0011 KKKK dddd KKKK */
+ const arg1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
+ const arg2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
+ const r = arg1 - arg2;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= r ? 0 : 2;
+ sreg |= 128 & r ? 4 : 0;
+ sreg |= (arg1 ^ arg2) & (arg1 ^ r) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= arg2 > arg1 ? 1 : 0;
+ sreg |= 1 & ((~arg1 & arg2) | (arg2 & r) | (r & ~arg1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0x1000) {
+ /* CPSE, 0001 00rd dddd rrrr */
+ if (cpu.data[(opcode & 0x1f0) >> 4] === cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)]) {
+ const nextOpcode = cpu.progMem[cpu.pc + 1];
+ const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
+ cpu.pc += skipSize;
+ cpu.cycles += skipSize;
+ }
+ } else if ((opcode & 0xfe0f) === 0x940a) {
+ /* DEC, 1001 010d dddd 1010 */
+ const value = cpu.data[(opcode & 0x1f0) >> 4];
+ const R = value - 1;
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= 128 === value ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0x2400) {
+ /* EOR, 0010 01rd dddd rrrr */
+ const R = cpu.data[(opcode & 0x1f0) >> 4] ^ cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xff88) === 0x308) {
+ /* FMUL, 0000 0011 0ddd 1rrr */
+ const v1 = cpu.data[((opcode & 0x70) >> 4) + 16];
+ const v2 = cpu.data[(opcode & 7) + 16];
+ const R = (v1 * v2) << 1;
+ cpu.dataView.setUint16(0, R, true);
+ cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | ((v1 * v2) & 0x8000 ? 1 : 0);
+ cpu.cycles++;
+ } else if ((opcode & 0xff88) === 0x380) {
+ /* FMULS, 0000 0011 1ddd 0rrr */
+ const v1 = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16);
+ const v2 = cpu.dataView.getInt8((opcode & 7) + 16);
+ const R = (v1 * v2) << 1;
+ cpu.dataView.setInt16(0, R, true);
+ cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | ((v1 * v2) & 0x8000 ? 1 : 0);
+ cpu.cycles++;
+ } else if ((opcode & 0xff88) === 0x388) {
+ /* FMULSU, 0000 0011 1ddd 1rrr */
+ const v1 = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16);
+ const v2 = cpu.data[(opcode & 7) + 16];
+ const R = (v1 * v2) << 1;
+ cpu.dataView.setInt16(0, R, true);
+ cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 2 : 0) | ((v1 * v2) & 0x8000 ? 1 : 0);
+ cpu.cycles++;
+ } else if (opcode === 0x9509) {
+ /* ICALL, 1001 0101 0000 1001 */
+ const retAddr = cpu.pc + 1;
+ const sp = cpu.dataView.getUint16(93, true);
+ cpu.data[sp] = retAddr & 255;
+ cpu.data[sp - 1] = (retAddr >> 8) & 255;
+ cpu.dataView.setUint16(93, sp - 2, true);
+ cpu.pc = cpu.dataView.getUint16(30, true) - 1;
+ cpu.cycles += 2;
+ } else if (opcode === 0x9409) {
+ /* IJMP, 1001 0100 0000 1001 */
+ cpu.pc = cpu.dataView.getUint16(30, true) - 1;
+ cpu.cycles++;
+ } else if ((opcode & 0xf800) === 0xb000) {
+ /* IN, 1011 0AAd dddd AAAA */
+ const i = cpu.readData(((opcode & 0xf) | ((opcode & 0x600) >> 5)) + 32);
+ cpu.data[(opcode & 0x1f0) >> 4] = i;
+ } else if ((opcode & 0xfe0f) === 0x9403) {
+ /* INC, 1001 010d dddd 0011 */
+ const d = cpu.data[(opcode & 0x1f0) >> 4];
+ const r = (d + 1) & 255;
+ cpu.data[(opcode & 0x1f0) >> 4] = r;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= r ? 0 : 2;
+ sreg |= 128 & r ? 4 : 0;
+ sreg |= 127 === d ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfe0e) === 0x940c) {
+ /* JMP, 1001 010k kkkk 110k kkkk kkkk kkkk kkkk */
+ cpu.pc = (cpu.progMem[cpu.pc + 1] | ((opcode & 1) << 16) | ((opcode & 0x1f0) << 13)) - 1;
+ cpu.cycles += 2;
+ } else if ((opcode & 0xfe0f) === 0x9206) {
+ /* LAC, 1001 001r rrrr 0110 */
+ const r = (opcode & 0x1f0) >> 4;
+ const clear = cpu.data[r];
+ const value = cpu.readData(cpu.dataView.getUint16(30, true));
+ cpu.writeData(cpu.dataView.getUint16(30, true), value & (255 - clear));
+ cpu.data[r] = value;
+ } else if ((opcode & 0xfe0f) === 0x9205) {
+ /* LAS, 1001 001r rrrr 0101 */
+ const r = (opcode & 0x1f0) >> 4;
+ const set = cpu.data[r];
+ const value = cpu.readData(cpu.dataView.getUint16(30, true));
+ cpu.writeData(cpu.dataView.getUint16(30, true), value | set);
+ cpu.data[r] = value;
+ } else if ((opcode & 0xfe0f) === 0x9207) {
+ /* LAT, 1001 001r rrrr 0111 */
+ const r = cpu.data[(opcode & 0x1f0) >> 4];
+ const R = cpu.readData(cpu.dataView.getUint16(30, true));
+ cpu.writeData(cpu.dataView.getUint16(30, true), r ^ R);
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ } else if ((opcode & 0xf000) === 0xe000) {
+ /* LDI, 1110 KKKK dddd KKKK */
+ cpu.data[((opcode & 0xf0) >> 4) + 16] = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
+ } else if ((opcode & 0xfe0f) === 0x9000) {
+ /* LDS, 1001 000d dddd 0000 kkkk kkkk kkkk kkkk */
+ const value = cpu.readData(cpu.progMem[cpu.pc + 1]);
+ cpu.data[(opcode & 0x1f0) >> 4] = value;
+ cpu.pc++;
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x900c) {
+ /* LDX, 1001 000d dddd 1100 */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(26, true));
+ } else if ((opcode & 0xfe0f) === 0x900d) {
+ /* LDX(INC), 1001 000d dddd 1101 */
+ const x = cpu.dataView.getUint16(26, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(x);
+ cpu.dataView.setUint16(26, x + 1, true);
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x900e) {
+ /* LDX(DEC), 1001 000d dddd 1110 */
+ const x = cpu.dataView.getUint16(26, true) - 1;
+ cpu.dataView.setUint16(26, x, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(x);
+ cpu.cycles += 2;
+ } else if ((opcode & 0xfe0f) === 0x8008) {
+ /* LDY, 1000 000d dddd 1000 */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(28, true));
+ } else if ((opcode & 0xfe0f) === 0x9009) {
+ /* LDY(INC), 1001 000d dddd 1001 */
+ const y = cpu.dataView.getUint16(28, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(y);
+ cpu.dataView.setUint16(28, y + 1, true);
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x900a) {
+ /* LDY(DEC), 1001 000d dddd 1010 */
+ const y = cpu.dataView.getUint16(28, true) - 1;
+ cpu.dataView.setUint16(28, y, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(y);
+ cpu.cycles += 2;
+ } else if (
+ (opcode & 0xd208) === 0x8008 &&
+ (opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)
+ ) {
+ /* LDDY, 10q0 qq0d dddd 1qqq */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(
+ cpu.dataView.getUint16(28, true) +
+ ((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8))
+ );
+ cpu.cycles += 2;
+ } else if ((opcode & 0xfe0f) === 0x8000) {
+ /* LDZ, 1000 000d dddd 0000 */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(cpu.dataView.getUint16(30, true));
+ } else if ((opcode & 0xfe0f) === 0x9001) {
+ /* LDZ(INC), 1001 000d dddd 0001 */
+ const z = cpu.dataView.getUint16(30, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(z);
+ cpu.dataView.setUint16(30, z + 1, true);
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x9002) {
+ /* LDZ(DEC), 1001 000d dddd 0010 */
+ const z = cpu.dataView.getUint16(30, true) - 1;
+ cpu.dataView.setUint16(30, z, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(z);
+ cpu.cycles += 2;
+ } else if (
+ (opcode & 0xd208) === 0x8000 &&
+ (opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)
+ ) {
+ /* LDDZ, 10q0 qq0d dddd 0qqq */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.readData(
+ cpu.dataView.getUint16(30, true) +
+ ((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8))
+ );
+ cpu.cycles += 2;
+ } else if (opcode === 0x95c8) {
+ /* LPM, 1001 0101 1100 1000 */
+ cpu.data[0] = cpu.progBytes[cpu.dataView.getUint16(30, true)];
+ cpu.cycles += 2;
+ } else if ((opcode & 0xfe0f) === 0x9004) {
+ /* LPM(REG), 1001 000d dddd 0100 */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[cpu.dataView.getUint16(30, true)];
+ cpu.cycles += 2;
+ } else if ((opcode & 0xfe0f) === 0x9005) {
+ /* LPM(INC), 1001 000d dddd 0101 */
+ const i = cpu.dataView.getUint16(30, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.progBytes[i];
+ cpu.dataView.setUint16(30, i + 1, true);
+ cpu.cycles += 2;
+ } else if ((opcode & 0xfe0f) === 0x9406) {
+ /* LSR, 1001 010d dddd 0110 */
+ const value = cpu.data[(opcode & 0x1f0) >> 4];
+ const R = value >>> 1;
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xe0;
+ sreg |= R ? 0 : 2;
+ sreg |= value & 1;
+ sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0x2c00) {
+ /* MOV, 0010 11rd dddd rrrr */
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ } else if ((opcode & 0xff00) === 0x100) {
+ /* MOVW, 0000 0001 dddd rrrr */
+ const r2 = 2 * (opcode & 0xf);
+ const d2 = 2 * ((opcode & 0xf0) >> 4);
+ cpu.data[d2] = cpu.data[r2];
+ cpu.data[d2 + 1] = cpu.data[r2 + 1];
+ } else if ((opcode & 0xfc00) === 0x9c00) {
+ /* MUL, 1001 11rd dddd rrrr */
+ const R = cpu.data[(opcode & 0x1f0) >> 4] * cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ cpu.dataView.setUint16(0, R, true);
+ cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
+ cpu.cycles++;
+ } else if ((opcode & 0xff00) === 0x200) {
+ /* MULS, 0000 0010 dddd rrrr */
+ const R =
+ cpu.dataView.getInt8(((opcode & 0xf0) >> 4) + 16) * cpu.dataView.getInt8((opcode & 0xf) + 16);
+ cpu.dataView.setInt16(0, R, true);
+ cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
+ cpu.cycles++;
+ } else if ((opcode & 0xff88) === 0x300) {
+ /* MULSU, 0000 0011 0ddd 0rrr */
+ const R = cpu.dataView.getInt8(((opcode & 0x70) >> 4) + 16) * cpu.data[(opcode & 7) + 16];
+ cpu.dataView.setInt16(0, R, true);
+ cpu.data[95] = (cpu.data[95] & 0xfc) | (0xffff & R ? 0 : 2) | (0x8000 & R ? 1 : 0);
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x9401) {
+ /* NEG, 1001 010d dddd 0001 */
+ const d = (opcode & 0x1f0) >> 4;
+ const value = cpu.data[d];
+ const R = 0 - value;
+ cpu.data[d] = R;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= 128 === R ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= R ? 1 : 0;
+ sreg |= 1 & (R | value) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if (opcode === 0) {
+ /* NOP, 0000 0000 0000 0000 */
+ /* NOP */
+ } else if ((opcode & 0xfc00) === 0x2800) {
+ /* OR, 0010 10rd dddd rrrr */
+ const R = cpu.data[(opcode & 0x1f0) >> 4] | cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xf000) === 0x6000) {
+ /* SBR, 0110 KKKK dddd KKKK */
+ const R = cpu.data[((opcode & 0xf0) >> 4) + 16] | ((opcode & 0xf) | ((opcode & 0xf00) >> 4));
+ cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
+ let sreg = cpu.data[95] & 0xe1;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xf800) === 0xb800) {
+ /* OUT, 1011 1AAr rrrr AAAA */
+ cpu.writeData(((opcode & 0xf) | ((opcode & 0x600) >> 5)) + 32, cpu.data[(opcode & 0x1f0) >> 4]);
+ } else if ((opcode & 0xfe0f) === 0x900f) {
+ /* POP, 1001 000d dddd 1111 */
+ const value = cpu.dataView.getUint16(93, true) + 1;
+ cpu.dataView.setUint16(93, value, true);
+ cpu.data[(opcode & 0x1f0) >> 4] = cpu.data[value];
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x920f) {
+ /* PUSH, 1001 001d dddd 1111 */
+ const value = cpu.dataView.getUint16(93, true);
+ cpu.data[value] = cpu.data[(opcode & 0x1f0) >> 4];
+ cpu.dataView.setUint16(93, value - 1, true);
+ cpu.cycles++;
+ } else if ((opcode & 0xf000) === 0xd000) {
+ /* RCALL, 1101 kkkk kkkk kkkk */
+ const k = (opcode & 0x7ff) - (opcode & 0x800 ? 0x800 : 0);
+ const retAddr = cpu.pc + 1;
+ const sp = cpu.dataView.getUint16(93, true);
+ cpu.data[sp] = 255 & retAddr;
+ cpu.data[sp - 1] = (retAddr >> 8) & 255;
+ cpu.dataView.setUint16(93, sp - 2, true);
+ cpu.pc += k;
+ cpu.cycles += 3;
+ } else if (opcode === 0x9508) {
+ /* RET, 1001 0101 0000 1000 */
+ const i = cpu.dataView.getUint16(93, true) + 2;
+ cpu.dataView.setUint16(93, i, true);
+ cpu.pc = (cpu.data[i - 1] << 8) + cpu.data[i] - 1;
+ cpu.cycles += 4;
+ } else if (opcode === 0x9518) {
+ /* RETI, 1001 0101 0001 1000 */
+ const i = cpu.dataView.getUint16(93, true) + 2;
+ cpu.dataView.setUint16(93, i, true);
+ cpu.pc = (cpu.data[i - 1] << 8) + cpu.data[i] - 1;
+ cpu.cycles += 4;
+ cpu.data[95] |= 0x80; // Enable interrupts
+ } else if ((opcode & 0xf000) === 0xc000) {
+ /* RJMP, 1100 kkkk kkkk kkkk */
+ cpu.pc = cpu.pc + ((opcode & 0x7ff) - (opcode & 0x800 ? 0x800 : 0));
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x9407) {
+ /* ROR, 1001 010d dddd 0111 */
+ const d = cpu.data[(opcode & 0x1f0) >> 4];
+ const r = (d >>> 1) | ((cpu.data[95] & 1) << 7);
+ cpu.data[(opcode & 0x1f0) >> 4] = r;
+ let sreg = cpu.data[95] & 0xe0;
+ sreg |= r ? 0 : 2;
+ sreg |= 128 & r ? 4 : 0;
+ sreg |= 1 & d ? 1 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ (sreg & 1) ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfc00) === 0x800) {
+ /* SBC, 0000 10rd dddd rrrr */
+ const val1 = cpu.data[(opcode & 0x1f0) >> 4];
+ const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ let sreg = cpu.data[95];
+ const R = val1 - val2 - (sreg & 1);
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ sreg = (sreg & 0xc0) | (!R && (sreg >> 1) & 1 ? 2 : 0) | (val2 + (sreg & 1) > val1 ? 1 : 0);
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xf000) === 0x4000) {
+ /* SBCI, 0100 KKKK dddd KKKK */
+ const val1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
+ const val2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
+ let sreg = cpu.data[95];
+ const R = val1 - val2 - (sreg & 1);
+ cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
+ sreg = (sreg & 0xc0) | (!R && (sreg >> 1) & 1 ? 2 : 0) | (val2 + (sreg & 1) > val1 ? 1 : 0);
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xff00) === 0x9a00) {
+ /* SBI, 1001 1010 AAAA Abbb */
+ const target = ((opcode & 0xf8) >> 3) + 32;
+ cpu.writeData(target, cpu.readData(target) | (1 << (opcode & 7)));
+ cpu.cycles++;
+ } else if ((opcode & 0xff00) === 0x9900) {
+ /* SBIC, 1001 1001 AAAA Abbb */
+ const value = cpu.readData(((opcode & 0xf8) >> 3) + 32);
+ if (!(value & (1 << (opcode & 7)))) {
+ const nextOpcode = cpu.progMem[cpu.pc + 1];
+ const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
+ cpu.cycles += skipSize;
+ cpu.pc += skipSize;
+ }
+ } else if ((opcode & 0xff00) === 0x9b00) {
+ /* SBIS, 1001 1011 AAAA Abbb */
+ const value = cpu.readData(((opcode & 0xf8) >> 3) + 32);
+ if (value & (1 << (opcode & 7))) {
+ const nextOpcode = cpu.progMem[cpu.pc + 1];
+ const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
+ cpu.cycles += skipSize;
+ cpu.pc += skipSize;
+ }
+ } else if ((opcode & 0xff00) === 0x9700) {
+ /* SBIW, 1001 0111 KKdd KKKK */
+ const i = 2 * ((opcode & 0x30) >> 4) + 24;
+ const a = cpu.dataView.getUint16(i, true);
+ const l = (opcode & 0xf) | ((opcode & 0xc0) >> 2);
+ const R = a - l;
+ cpu.dataView.setUint16(i, R, true);
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 0x8000 & R ? 4 : 0;
+ sreg |= a & ~R & 0x8000 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= l > a ? 1 : 0;
+ sreg |= 1 & ((~a & l) | (l & R) | (R & ~a)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ cpu.cycles++;
+ } else if ((opcode & 0xfe08) === 0xfc00) {
+ /* SBRC, 1111 110r rrrr 0bbb */
+ if (!(cpu.data[(opcode & 0x1f0) >> 4] & (1 << (opcode & 7)))) {
+ const nextOpcode = cpu.progMem[cpu.pc + 1];
+ const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
+ cpu.cycles += skipSize;
+ cpu.pc += skipSize;
+ }
+ } else if ((opcode & 0xfe08) === 0xfe00) {
+ /* SBRS, 1111 111r rrrr 0bbb */
+ if (cpu.data[(opcode & 0x1f0) >> 4] & (1 << (opcode & 7))) {
+ const nextOpcode = cpu.progMem[cpu.pc + 1];
+ const skipSize = isTwoWordInstruction(nextOpcode) ? 2 : 1;
+ cpu.cycles += skipSize;
+ cpu.pc += skipSize;
+ }
+ } else if (opcode === 0x9588) {
+ /* SLEEP, 1001 0101 1000 1000 */
+ /* not implemented */
+ } else if (opcode === 0x95e8) {
+ /* SPM, 1001 0101 1110 1000 */
+ /* not implemented */
+ } else if (opcode === 0x95f8) {
+ /* SPM(INC), 1001 0101 1111 1000 */
+ /* not implemented */
+ } else if ((opcode & 0xfe0f) === 0x9200) {
+ /* STS, 1001 001d dddd 0000 kkkk kkkk kkkk kkkk */
+ const value = cpu.data[(opcode & 0x1f0) >> 4];
+ const addr = cpu.progMem[cpu.pc + 1];
+ cpu.writeData(addr, value);
+ cpu.pc++;
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x920c) {
+ /* STX, 1001 001r rrrr 1100 */
+ cpu.writeData(cpu.dataView.getUint16(26, true), cpu.data[(opcode & 0x1f0) >> 4]);
+ } else if ((opcode & 0xfe0f) === 0x920d) {
+ /* STX(INC), 1001 001r rrrr 1101 */
+ const x = cpu.dataView.getUint16(26, true);
+ cpu.writeData(x, cpu.data[(opcode & 0x1f0) >> 4]);
+ cpu.dataView.setUint16(26, x + 1, true);
+ } else if ((opcode & 0xfe0f) === 0x920e) {
+ /* STX(DEC), 1001 001r rrrr 1110 */
+ const i = cpu.data[(opcode & 0x1f0) >> 4];
+ const x = cpu.dataView.getUint16(26, true) - 1;
+ cpu.dataView.setUint16(26, x, true);
+ cpu.writeData(x, i);
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x8208) {
+ /* STY, 1000 001r rrrr 1000 */
+ cpu.writeData(cpu.dataView.getUint16(28, true), cpu.data[(opcode & 0x1f0) >> 4]);
+ } else if ((opcode & 0xfe0f) === 0x9209) {
+ /* STY(INC), 1001 001r rrrr 1001 */
+ const i = cpu.data[(opcode & 0x1f0) >> 4];
+ const y = cpu.dataView.getUint16(28, true);
+ cpu.writeData(y, i);
+ cpu.dataView.setUint16(28, y + 1, true);
+ } else if ((opcode & 0xfe0f) === 0x920a) {
+ /* STY(DEC), 1001 001r rrrr 1010 */
+ const i = cpu.data[(opcode & 0x1f0) >> 4];
+ const y = cpu.dataView.getUint16(28, true) - 1;
+ cpu.dataView.setUint16(28, y, true);
+ cpu.writeData(y, i);
+ cpu.cycles++;
+ } else if (
+ (opcode & 0xd208) === 0x8208 &&
+ (opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)
+ ) {
+ /* STDY, 10q0 qq1r rrrr 1qqq */
+ cpu.writeData(
+ cpu.dataView.getUint16(28, true) +
+ ((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)),
+ cpu.data[(opcode & 0x1f0) >> 4]
+ );
+ cpu.cycles++;
+ } else if ((opcode & 0xfe0f) === 0x8200) {
+ /* STZ, 1000 001r rrrr 0000 */
+ cpu.writeData(cpu.dataView.getUint16(30, true), cpu.data[(opcode & 0x1f0) >> 4]);
+ } else if ((opcode & 0xfe0f) === 0x9201) {
+ /* STZ(INC), 1001 001r rrrr 0001 */
+ const z = cpu.dataView.getUint16(30, true);
+ cpu.writeData(z, cpu.data[(opcode & 0x1f0) >> 4]);
+ cpu.dataView.setUint16(30, z + 1, true);
+ } else if ((opcode & 0xfe0f) === 0x9202) {
+ /* STZ(DEC), 1001 001r rrrr 0010 */
+ const i = cpu.data[(opcode & 0x1f0) >> 4];
+ const z = cpu.dataView.getUint16(30, true) - 1;
+ cpu.dataView.setUint16(30, z, true);
+ cpu.writeData(z, i);
+ cpu.cycles++;
+ } else if (
+ (opcode & 0xd208) === 0x8200 &&
+ (opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)
+ ) {
+ /* STDZ, 10q0 qq1r rrrr 0qqq */
+ cpu.writeData(
+ cpu.dataView.getUint16(30, true) +
+ ((opcode & 7) | ((opcode & 0xc00) >> 7) | ((opcode & 0x2000) >> 8)),
+ cpu.data[(opcode & 0x1f0) >> 4]
+ );
+ cpu.cycles++;
+ } else if ((opcode & 0xfc00) === 0x1800) {
+ /* SUB, 0001 10rd dddd rrrr */
+ const val1 = cpu.data[(opcode & 0x1f0) >> 4];
+ const val2 = cpu.data[(opcode & 0xf) | ((opcode & 0x200) >> 5)];
+ const R = val1 - val2;
+
+ cpu.data[(opcode & 0x1f0) >> 4] = R;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= val2 > val1 ? 1 : 0;
+ sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xf000) === 0x5000) {
+ /* SUBI, 0101 KKKK dddd KKKK */
+ const val1 = cpu.data[((opcode & 0xf0) >> 4) + 16];
+ const val2 = (opcode & 0xf) | ((opcode & 0xf00) >> 4);
+ const R = val1 - val2;
+ cpu.data[((opcode & 0xf0) >> 4) + 16] = R;
+ let sreg = cpu.data[95] & 0xc0;
+ sreg |= R ? 0 : 2;
+ sreg |= 128 & R ? 4 : 0;
+ sreg |= (val1 ^ val2) & (val1 ^ R) & 128 ? 8 : 0;
+ sreg |= ((sreg >> 2) & 1) ^ ((sreg >> 3) & 1) ? 0x10 : 0;
+ sreg |= val2 > val1 ? 1 : 0;
+ sreg |= 1 & ((~val1 & val2) | (val2 & R) | (R & ~val1)) ? 0x20 : 0;
+ cpu.data[95] = sreg;
+ } else if ((opcode & 0xfe0f) === 0x9402) {
+ /* SWAP, 1001 010d dddd 0010 */
+ const d = (opcode & 0x1f0) >> 4;
+ const i = cpu.data[d];
+ cpu.data[d] = ((15 & i) << 4) | ((240 & i) >>> 4);
+ } else if (opcode === 0x95a8) {
+ /* WDR, 1001 0101 1010 1000 */
+ /* not implemented */
+ } else if ((opcode & 0xfe0f) === 0x9204) {
+ /* XCH, 1001 001r rrrr 0100 */
+ const r = (opcode & 0x1f0) >> 4;
+ const val1 = cpu.data[r];
+ const val2 = cpu.data[cpu.dataView.getUint16(30, true)];
+ cpu.data[cpu.dataView.getUint16(30, true)] = val1;
+ cpu.data[r] = val2;
+ }
+
+ cpu.pc = (cpu.pc + 1) % cpu.progMem.length;
+ cpu.cycles++;
+}
diff --git a/src/cpu/interrupt.spec.ts b/src/cpu/interrupt.spec.ts
new file mode 100644
index 0000000..cc54e3c
--- /dev/null
+++ b/src/cpu/interrupt.spec.ts
@@ -0,0 +1,19 @@
+import { CPU } from './cpu';
+import { avrInterrupt } from './interrupt';
+
+describe('avrInterrupt', () => {
+ it('should execute interrupt handler', () => {
+ const cpu = new CPU(new Uint16Array(0x8000));
+ cpu.pc = 0x520;
+ cpu.data[94] = 0;
+ cpu.data[93] = 0x80; // SP <- 0x80
+ cpu.data[95] = 0b10000001; // SREG <- I------C
+ avrInterrupt(cpu, 5);
+ expect(cpu.cycles).toEqual(2);
+ expect(cpu.pc).toEqual(5);
+ expect(cpu.data[93]).toEqual(0x7e); // SP
+ expect(cpu.data[0x80]).toEqual(0x20); // Return addr low
+ expect(cpu.data[0x7f]).toEqual(0x5); // Return addr high
+ expect(cpu.data[95]).toEqual(0b00000001); // SREG: -------C
+ });
+});
diff --git a/src/cpu/interrupt.ts b/src/cpu/interrupt.ts
new file mode 100644
index 0000000..1c7d835
--- /dev/null
+++ b/src/cpu/interrupt.ts
@@ -0,0 +1,19 @@
+/**
+ * AVR-8 Interrupt Handling
+ * Part of AVR8js
+ * Reference: http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf
+ *
+ * Copyright (C) 2019, Uri Shaked
+ */
+
+import { ICPU } from './cpu';
+
+export function avrInterrupt(cpu: ICPU, addr: number) {
+ const sp = cpu.dataView.getUint16(93, true);
+ cpu.data[sp] = cpu.pc & 0xff;
+ cpu.data[sp - 1] = (cpu.pc >> 8) & 0xff;
+ cpu.dataView.setUint16(93, sp - 2, true);
+ cpu.data[95] &= 0x7f; // clear global interrupt flag
+ cpu.cycles += 2;
+ cpu.pc = addr;
+}