From 8934a7566a038a74464d3d8df9d04fd875e5b1d7 Mon Sep 17 00:00:00 2001 From: lironh Date: Sat, 21 Mar 2020 09:52:53 +0200 Subject: refactor: added peripherals and cpu feature folders --- src/cpu.spec.ts | 8 - src/cpu.ts | 78 ---- src/cpu/cpu.spec.ts | 8 + src/cpu/cpu.ts | 78 ++++ src/cpu/instruction.spec.ts | 837 ++++++++++++++++++++++++++++++++++++++++++ src/cpu/instruction.ts | 726 ++++++++++++++++++++++++++++++++++++ src/cpu/interrupt.spec.ts | 19 + src/cpu/interrupt.ts | 19 + src/gpio.spec.ts | 86 ----- src/gpio.ts | 149 -------- src/index.ts | 14 +- src/instruction.spec.ts | 837 ------------------------------------------ src/instruction.ts | 726 ------------------------------------ src/interrupt.spec.ts | 19 - src/interrupt.ts | 19 - src/peripherals/gpio.spec.ts | 86 +++++ src/peripherals/gpio.ts | 149 ++++++++ src/peripherals/timer.spec.ts | 204 ++++++++++ src/peripherals/timer.ts | 244 ++++++++++++ src/peripherals/twi.spec.ts | 390 ++++++++++++++++++++ src/peripherals/twi.ts | 200 ++++++++++ src/peripherals/usart.spec.ts | 159 ++++++++ src/peripherals/usart.ts | 134 +++++++ src/timer.spec.ts | 204 ---------- src/timer.ts | 244 ------------ src/twi.spec.ts | 390 -------------------- src/twi.ts | 200 ---------- src/usart.spec.ts | 159 -------- src/usart.ts | 134 ------- 29 files changed, 3260 insertions(+), 3260 deletions(-) delete mode 100644 src/cpu.spec.ts delete mode 100644 src/cpu.ts create mode 100644 src/cpu/cpu.spec.ts create mode 100644 src/cpu/cpu.ts create mode 100644 src/cpu/instruction.spec.ts create mode 100644 src/cpu/instruction.ts create mode 100644 src/cpu/interrupt.spec.ts create mode 100644 src/cpu/interrupt.ts delete mode 100644 src/gpio.spec.ts delete mode 100644 src/gpio.ts delete mode 100644 src/instruction.spec.ts delete mode 100644 src/instruction.ts delete mode 100644 src/interrupt.spec.ts delete mode 100644 src/interrupt.ts create mode 100644 src/peripherals/gpio.spec.ts create mode 100644 src/peripherals/gpio.ts create mode 100644 src/peripherals/timer.spec.ts create mode 100644 src/peripherals/timer.ts create mode 100644 src/peripherals/twi.spec.ts create mode 100644 src/peripherals/twi.ts create mode 100644 src/peripherals/usart.spec.ts create mode 100644 src/peripherals/usart.ts delete mode 100644 src/timer.spec.ts delete mode 100644 src/timer.ts delete mode 100644 src/twi.spec.ts delete mode 100644 src/twi.ts delete mode 100644 src/usart.spec.ts delete mode 100644 src/usart.ts (limited to 'src') diff --git a/src/cpu.spec.ts b/src/cpu.spec.ts deleted file mode 100644 index 92df7ee..0000000 --- a/src/cpu.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -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.ts b/src/cpu.ts deleted file mode 100644 index 4288285..0000000 --- a/src/cpu.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * 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/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; +} diff --git a/src/gpio.spec.ts b/src/gpio.spec.ts deleted file mode 100644 index 2fa866d..0000000 --- a/src/gpio.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { CPU } from './cpu'; -import { AVRIOPort, portBConfig, PinState } from './gpio'; - -describe('GPIO', () => { - it('should invoke the listeners when the port is written to', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - const listener = jest.fn(); - port.addListener(listener); - cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f - cpu.writeData(0x25, 0x55); // PORTB <- 0x55 - expect(listener).toHaveBeenCalledWith(0x05, 0); - expect(cpu.data[0x23]).toEqual(0x5); // PINB should return port value - }); - - it('should toggle the pin when writing to the PIN register', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - const listener = jest.fn(); - port.addListener(listener); - cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f - cpu.writeData(0x25, 0x55); // PORTB <- 0x55 - cpu.writeData(0x23, 0x01); // PINB <- 0x0f - expect(listener).toHaveBeenCalledWith(0x04, 0x5); - expect(cpu.data[0x23]).toEqual(0x4); // PINB should return port value - }); - - describe('removeListener', () => { - it('should remove the given listener', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - const listener = jest.fn(); - port.addListener(listener); - cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f - port.removeListener(listener); - cpu.writeData(0x25, 0x99); // PORTB <- 0x99 - expect(listener).not.toHaveBeenCalled(); - }); - }); - - describe('pinState', () => { - it('should return PinState.High when the pin set to output and HIGH', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - cpu.writeData(0x24, 0x1); // DDRB <- 0x1 - cpu.writeData(0x25, 0x1); // PORTB <- 0x1 - expect(port.pinState(0)).toEqual(PinState.High); - }); - - it('should return PinState.Low when the pin set to output and LOW', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - cpu.writeData(0x24, 0x8); // DDRB <- 0x8 - cpu.writeData(0x25, 0xf7); // PORTB <- 0xF7 (~8) - expect(port.pinState(3)).toEqual(PinState.Low); - }); - - it('should return PinState.Input by default (reset state)', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - expect(port.pinState(1)).toEqual(PinState.Input); - }); - - it('should return PinState.InputPullUp when the pin is set to input with pullup', () => { - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - cpu.writeData(0x24, 0); // DDRB <- 0 - cpu.writeData(0x25, 0x2); // PORTB <- 0x2 - expect(port.pinState(1)).toEqual(PinState.InputPullUp); - }); - - it('should reflect the current port state when called inside a listener', () => { - // Related issue: https://github.com/wokwi/avr8js/issues/9 - const cpu = new CPU(new Uint16Array(1024)); - const port = new AVRIOPort(cpu, portBConfig); - const listener = jest.fn(() => { - expect(port.pinState(0)).toBe(PinState.High); - }); - port.addListener(listener); - expect(port.pinState(0)).toBe(PinState.Input); - cpu.writeData(0x24, 0x01); // DDRB <- 0x01 - cpu.writeData(0x25, 0x01); // PORTB <- 0x01 - expect(listener).toHaveBeenCalled(); - }); - }); -}); diff --git a/src/gpio.ts b/src/gpio.ts deleted file mode 100644 index d0df06d..0000000 --- a/src/gpio.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * AVR-8 GPIO Port implementation - * Part of AVR8js - * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf - * - * Copyright (C) 2019, 2020, Uri Shaked - */ -import { CPU } from './cpu'; -import { u8 } from './types'; - -export interface AVRPortConfig { - // Register addresses - PIN: u8; - DDR: u8; - PORT: u8; -} - -export type GPIOListener = (value: u8, oldValue: u8) => void; - -export const portAConfig: AVRPortConfig = { - PIN: 0x20, - DDR: 0x21, - PORT: 0x22 -}; - -export const portBConfig: AVRPortConfig = { - PIN: 0x23, - DDR: 0x24, - PORT: 0x25 -}; - -export const portCConfig: AVRPortConfig = { - PIN: 0x26, - DDR: 0x27, - PORT: 0x28 -}; - -export const portDConfig: AVRPortConfig = { - PIN: 0x29, - DDR: 0x2a, - PORT: 0x2b -}; - -export const portEConfig: AVRPortConfig = { - PIN: 0x2c, - DDR: 0x2d, - PORT: 0x2e -}; - -export const portFConfig: AVRPortConfig = { - PIN: 0x2f, - DDR: 0x30, - PORT: 0x31 -}; - -export const portGConfig: AVRPortConfig = { - PIN: 0x32, - DDR: 0x33, - PORT: 0x34 -}; - -export const portHConfig: AVRPortConfig = { - PIN: 0x100, - DDR: 0x101, - PORT: 0x102 -}; - -export const portJConfig: AVRPortConfig = { - PIN: 0x103, - DDR: 0x104, - PORT: 0x105 -}; - -export const portKConfig: AVRPortConfig = { - PIN: 0x106, - DDR: 0x107, - PORT: 0x108 -}; - -export const portLConfig: AVRPortConfig = { - PIN: 0x109, - DDR: 0x10a, - PORT: 0x10b -}; - -export enum PinState { - Low, - High, - Input, - InputPullUp -} - -export class AVRIOPort { - private listeners: GPIOListener[] = []; - - constructor(private cpu: CPU, private portConfig: AVRPortConfig) { - cpu.writeHooks[portConfig.PORT] = (value: u8, oldValue: u8) => { - const ddrMask = cpu.data[portConfig.DDR]; - cpu.data[portConfig.PORT] = value; - value &= ddrMask; - cpu.data[portConfig.PIN] = (cpu.data[portConfig.PIN] & ~ddrMask) | value; - this.writeGpio(value, oldValue & ddrMask); - return true; - }; - cpu.writeHooks[portConfig.PIN] = (value: u8) => { - // Writing to 1 PIN toggles PORT bits - const oldPortValue = cpu.data[portConfig.PORT]; - const ddrMask = cpu.data[portConfig.DDR]; - const portValue = oldPortValue ^ value; - cpu.data[portConfig.PORT] = portValue; - cpu.data[portConfig.PIN] = (cpu.data[portConfig.PIN] & ~ddrMask) | (portValue & ddrMask); - this.writeGpio(portValue & ddrMask, oldPortValue & ddrMask); - return true; - }; - } - - addListener(listener: GPIOListener) { - this.listeners.push(listener); - } - - removeListener(listener: GPIOListener) { - this.listeners = this.listeners.filter((l) => l !== listener); - } - - /** - * Get the state of a given GPIO pin - * - * @param index Pin index to return from 0 to 7 - * @returns PinState.Low or PinState.High if the pin is set to output, PinState.Input if the pin is set - * to input, and PinState.InputPullUp if the pin is set to input and the internal pull-up resistor has - * been enabled. - */ - pinState(index: number) { - const ddr = this.cpu.data[this.portConfig.DDR]; - const port = this.cpu.data[this.portConfig.PORT]; - const bitMask = 1 << index; - if (ddr & bitMask) { - return port & bitMask ? PinState.High : PinState.Low; - } else { - return port & bitMask ? PinState.InputPullUp : PinState.Input; - } - } - - private writeGpio(value: u8, oldValue: u8) { - for (const listener of this.listeners) { - listener(value, oldValue); - } - } -} diff --git a/src/index.ts b/src/index.ts index ef0b514..58afde3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,10 +4,10 @@ * Copyright (C) 2019, 2020, Uri Shaked */ -export { CPU, ICPU, CPUMemoryHook, CPUMemoryHooks } from './cpu'; -export { avrInstruction } from './instruction'; -export { avrInterrupt } from './interrupt'; -export { AVRTimer, timer0Config, timer1Config, timer2Config } from './timer'; +export { CPU, ICPU, CPUMemoryHook, CPUMemoryHooks } from './cpu/cpu'; +export { avrInstruction } from './cpu/instruction'; +export { avrInterrupt } from './cpu/interrupt'; +export { AVRTimer, timer0Config, timer1Config, timer2Config } from './peripherals/timer'; export { AVRIOPort, GPIOListener, @@ -24,6 +24,6 @@ export { portKConfig, portLConfig, PinState -} from './gpio'; -export { AVRUSART, usart0Config } from './usart'; -export * from './twi'; +} from './peripherals/gpio'; +export { AVRUSART, usart0Config } from './peripherals/usart'; +export * from './peripherals/twi'; diff --git a/src/instruction.spec.ts b/src/instruction.spec.ts deleted file mode 100644 index 2c5244e..0000000 --- a/src/instruction.spec.ts +++ /dev/null @@ -1,837 +0,0 @@ -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/instruction.ts b/src/instruction.ts deleted file mode 100644 index 9ee52de..0000000 --- a/src/instruction.ts +++ /dev/null @@ -1,726 +0,0 @@ -/** - * 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/interrupt.spec.ts b/src/interrupt.spec.ts deleted file mode 100644 index cc54e3c..0000000 --- a/src/interrupt.spec.ts +++ /dev/null @@ -1,19 +0,0 @@ -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/interrupt.ts b/src/interrupt.ts deleted file mode 100644 index 1c7d835..0000000 --- a/src/interrupt.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * 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; -} diff --git a/src/peripherals/gpio.spec.ts b/src/peripherals/gpio.spec.ts new file mode 100644 index 0000000..5a282e6 --- /dev/null +++ b/src/peripherals/gpio.spec.ts @@ -0,0 +1,86 @@ +import { CPU } from '../cpu/cpu'; +import { AVRIOPort, portBConfig, PinState } from './gpio'; + +describe('GPIO', () => { + it('should invoke the listeners when the port is written to', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + const listener = jest.fn(); + port.addListener(listener); + cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f + cpu.writeData(0x25, 0x55); // PORTB <- 0x55 + expect(listener).toHaveBeenCalledWith(0x05, 0); + expect(cpu.data[0x23]).toEqual(0x5); // PINB should return port value + }); + + it('should toggle the pin when writing to the PIN register', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + const listener = jest.fn(); + port.addListener(listener); + cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f + cpu.writeData(0x25, 0x55); // PORTB <- 0x55 + cpu.writeData(0x23, 0x01); // PINB <- 0x0f + expect(listener).toHaveBeenCalledWith(0x04, 0x5); + expect(cpu.data[0x23]).toEqual(0x4); // PINB should return port value + }); + + describe('removeListener', () => { + it('should remove the given listener', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + const listener = jest.fn(); + port.addListener(listener); + cpu.writeData(0x24, 0x0f); // DDRB <- 0x0f + port.removeListener(listener); + cpu.writeData(0x25, 0x99); // PORTB <- 0x99 + expect(listener).not.toHaveBeenCalled(); + }); + }); + + describe('pinState', () => { + it('should return PinState.High when the pin set to output and HIGH', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + cpu.writeData(0x24, 0x1); // DDRB <- 0x1 + cpu.writeData(0x25, 0x1); // PORTB <- 0x1 + expect(port.pinState(0)).toEqual(PinState.High); + }); + + it('should return PinState.Low when the pin set to output and LOW', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + cpu.writeData(0x24, 0x8); // DDRB <- 0x8 + cpu.writeData(0x25, 0xf7); // PORTB <- 0xF7 (~8) + expect(port.pinState(3)).toEqual(PinState.Low); + }); + + it('should return PinState.Input by default (reset state)', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + expect(port.pinState(1)).toEqual(PinState.Input); + }); + + it('should return PinState.InputPullUp when the pin is set to input with pullup', () => { + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + cpu.writeData(0x24, 0); // DDRB <- 0 + cpu.writeData(0x25, 0x2); // PORTB <- 0x2 + expect(port.pinState(1)).toEqual(PinState.InputPullUp); + }); + + it('should reflect the current port state when called inside a listener', () => { + // Related issue: https://github.com/wokwi/avr8js/issues/9 + const cpu = new CPU(new Uint16Array(1024)); + const port = new AVRIOPort(cpu, portBConfig); + const listener = jest.fn(() => { + expect(port.pinState(0)).toBe(PinState.High); + }); + port.addListener(listener); + expect(port.pinState(0)).toBe(PinState.Input); + cpu.writeData(0x24, 0x01); // DDRB <- 0x01 + cpu.writeData(0x25, 0x01); // PORTB <- 0x01 + expect(listener).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/peripherals/gpio.ts b/src/peripherals/gpio.ts new file mode 100644 index 0000000..a667967 --- /dev/null +++ b/src/peripherals/gpio.ts @@ -0,0 +1,149 @@ +/** + * AVR-8 GPIO Port implementation + * Part of AVR8js + * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf + * + * Copyright (C) 2019, 2020, Uri Shaked + */ +import { CPU } from '../cpu/cpu'; +import { u8 } from '../types'; + +export interface AVRPortConfig { + // Register addresses + PIN: u8; + DDR: u8; + PORT: u8; +} + +export type GPIOListener = (value: u8, oldValue: u8) => void; + +export const portAConfig: AVRPortConfig = { + PIN: 0x20, + DDR: 0x21, + PORT: 0x22 +}; + +export const portBConfig: AVRPortConfig = { + PIN: 0x23, + DDR: 0x24, + PORT: 0x25 +}; + +export const portCConfig: AVRPortConfig = { + PIN: 0x26, + DDR: 0x27, + PORT: 0x28 +}; + +export const portDConfig: AVRPortConfig = { + PIN: 0x29, + DDR: 0x2a, + PORT: 0x2b +}; + +export const portEConfig: AVRPortConfig = { + PIN: 0x2c, + DDR: 0x2d, + PORT: 0x2e +}; + +export const portFConfig: AVRPortConfig = { + PIN: 0x2f, + DDR: 0x30, + PORT: 0x31 +}; + +export const portGConfig: AVRPortConfig = { + PIN: 0x32, + DDR: 0x33, + PORT: 0x34 +}; + +export const portHConfig: AVRPortConfig = { + PIN: 0x100, + DDR: 0x101, + PORT: 0x102 +}; + +export const portJConfig: AVRPortConfig = { + PIN: 0x103, + DDR: 0x104, + PORT: 0x105 +}; + +export const portKConfig: AVRPortConfig = { + PIN: 0x106, + DDR: 0x107, + PORT: 0x108 +}; + +export const portLConfig: AVRPortConfig = { + PIN: 0x109, + DDR: 0x10a, + PORT: 0x10b +}; + +export enum PinState { + Low, + High, + Input, + InputPullUp +} + +export class AVRIOPort { + private listeners: GPIOListener[] = []; + + constructor(private cpu: CPU, private portConfig: AVRPortConfig) { + cpu.writeHooks[portConfig.PORT] = (value: u8, oldValue: u8) => { + const ddrMask = cpu.data[portConfig.DDR]; + cpu.data[portConfig.PORT] = value; + value &= ddrMask; + cpu.data[portConfig.PIN] = (cpu.data[portConfig.PIN] & ~ddrMask) | value; + this.writeGpio(value, oldValue & ddrMask); + return true; + }; + cpu.writeHooks[portConfig.PIN] = (value: u8) => { + // Writing to 1 PIN toggles PORT bits + const oldPortValue = cpu.data[portConfig.PORT]; + const ddrMask = cpu.data[portConfig.DDR]; + const portValue = oldPortValue ^ value; + cpu.data[portConfig.PORT] = portValue; + cpu.data[portConfig.PIN] = (cpu.data[portConfig.PIN] & ~ddrMask) | (portValue & ddrMask); + this.writeGpio(portValue & ddrMask, oldPortValue & ddrMask); + return true; + }; + } + + addListener(listener: GPIOListener) { + this.listeners.push(listener); + } + + removeListener(listener: GPIOListener) { + this.listeners = this.listeners.filter((l) => l !== listener); + } + + /** + * Get the state of a given GPIO pin + * + * @param index Pin index to return from 0 to 7 + * @returns PinState.Low or PinState.High if the pin is set to output, PinState.Input if the pin is set + * to input, and PinState.InputPullUp if the pin is set to input and the internal pull-up resistor has + * been enabled. + */ + pinState(index: number) { + const ddr = this.cpu.data[this.portConfig.DDR]; + const port = this.cpu.data[this.portConfig.PORT]; + const bitMask = 1 << index; + if (ddr & bitMask) { + return port & bitMask ? PinState.High : PinState.Low; + } else { + return port & bitMask ? PinState.InputPullUp : PinState.Input; + } + } + + private writeGpio(value: u8, oldValue: u8) { + for (const listener of this.listeners) { + listener(value, oldValue); + } + } +} diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts new file mode 100644 index 0000000..adcef04 --- /dev/null +++ b/src/peripherals/timer.spec.ts @@ -0,0 +1,204 @@ +import { CPU } from '../cpu/cpu'; +import { AVRTimer, timer0Config, timer2Config } from './timer'; + +describe('timer', () => { + let cpu: CPU; + + beforeEach(() => { + cpu = new CPU(new Uint16Array(0x1000)); + }); + + it('should update timer every tick when prescaler is 1', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 + }); + + it('should update timer every 64 ticks when prescaler is 3', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x45] = 0x3; // TCCR0B.CS <- 3 + cpu.cycles = 64; + timer.tick(); + expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 + }); + + it('should not update timer if it has been disabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x45] = 0; // TCCR0B.CS <- 0 + cpu.cycles = 100000; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should stay 0 + }); + + it('should set TOV if timer overflows', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR + }); + + it('should set TOV if timer overflows in PWM Phase Correct mode', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f + cpu.writeData(0x44, 0x1); // WGM0 <- 1 (PWM, Phase Correct) + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR + }); + + it('should set TOV if timer overflows in FAST PWM mode', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f + cpu.writeData(0x44, 0x3); // WGM0 <- 3 (FAST PWM) + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR + }); + + it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 + cpu.data[95] = 0x80; // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + expect(cpu.data[0x35]).toEqual(0); // TOV bit in TIFR should be clear + expect(cpu.pc).toEqual(0x20); + expect(cpu.cycles).toEqual(3); + }); + + it('should not generate an overflow interrupt when global interrupts disabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 + cpu.data[95] = 0x0; // SREG: -------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR should be set + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should not generate an overflow interrupt when TOIE0 is clear', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.data[0x46] = 0xff; // TCNT0 <- 0xff + cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 + cpu.data[0x6e] = 0; // TIMSK0: clear + cpu.data[95] = 0x80; // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR should be set + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should set OCF0A flag when timer equals OCRA', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10 + cpu.writeData(0x47, 0x11); // OCR0A <- 0x11 + cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal) + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(2); // TIFR0 should have OCF0A bit on + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should clear the timer in CTC mode if it equals to OCRA', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10 + cpu.writeData(0x47, 0x11); // OCR0A <- 0x11 + cpu.writeData(0x44, 0x2); // WGM0 <- 2 (CTC) + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should set OCF0B flag when timer equals OCRB', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x10); // TCNT0 <- 0x50 + cpu.writeData(0x48, 0x11); // OCR0B <- 0x51 + cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal) + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x35]).toEqual(4); // TIFR0 should have OCF0B bit on + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 + cpu.writeData(0x47, 0x21); // OCR0A <- 0x21 + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.writeData(0x6e, 0x2); // TIMSK0: OCIEA + cpu.writeData(95, 0x80); // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + expect(cpu.data[0x35]).toEqual(0); // OCFA bit in TIFR should be clear + expect(cpu.pc).toEqual(0x1c); + expect(cpu.cycles).toEqual(3); + }); + + it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 + cpu.writeData(0x47, 0x21); // OCR0A <- 0x21 + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.writeData(0x6e, 0); // TIMSK0 + cpu.writeData(95, 0x80); // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(1); + }); + + it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => { + const timer = new AVRTimer(cpu, timer0Config); + cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 + cpu.writeData(0x48, 0x21); // OCR0B <- 0x21 + cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 + cpu.writeData(0x6e, 0x4); // TIMSK0: OCIEB + cpu.writeData(95, 0x80); // SREG: I------- + cpu.cycles = 1; + timer.tick(); + expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 + expect(cpu.data[0x35]).toEqual(0); // OCFB bit in TIFR should be clear + expect(cpu.pc).toEqual(0x1e); + expect(cpu.cycles).toEqual(3); + }); + + it('timer2 should count every 256 ticks when prescaler is 6 (issue #5)', () => { + const timer = new AVRTimer(cpu, timer2Config); + cpu.data[0xb1] = 0x6; // TCCR1B.CS <- 6 + + cpu.cycles = 511; + timer.tick(); + expect(cpu.data[0xb2]).toEqual(1); // TCNT2 should be 2 + + cpu.cycles = 512; + timer.tick(); + expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2 + }); +}); diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts new file mode 100644 index 0000000..7e563c2 --- /dev/null +++ b/src/peripherals/timer.ts @@ -0,0 +1,244 @@ +/** + * AVR-8 Timers + * Part of AVR8js + * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf + * + * Copyright (C) 2019, Uri Shaked + */ + +import { CPU } from '../cpu/cpu'; +import { avrInterrupt } from '../cpu/interrupt'; + +const timer01Dividers = { + 0: 0, + 1: 1, + 2: 8, + 3: 64, + 4: 256, + 5: 1024, + 6: 0, // TODO: External clock source on T0 pin. Clock on falling edge. + 7: 0 // TODO: External clock source on T0 pin. Clock on rising edge. +}; + +const WGM_NORMAL = 0; +const WGM_PWM_PHASE_CORRECT = 1; +const WGM_CTC = 2; +const WGM_FASTPWM = 3; + +const TOV = 1; +const OCFA = 2; +const OCFB = 4; + +const TOIE = 1; +const OCIEA = 2; +const OCIEB = 4; + +type u8 = number; + +interface TimerDividers { + 0: number; + 1: number; + 2: number; + 3: number; + 4: number; + 5: number; + 6: number; + 7: number; +} + +interface AVRTimerConfig { + bits: 8 | 16; + captureInterrupt: u8; + compAInterrupt: u8; + compBInterrupt: u8; + ovfInterrupt: u8; + + // Register addresses + TIFR: u8; + OCRA: u8; + OCRB: u8; + ICR: u8; + TCNT: u8; + TCCRA: u8; + TCCRB: u8; + TCCRC: u8; + TIMSK: u8; + + dividers: TimerDividers; +} + +export const timer0Config: AVRTimerConfig = { + bits: 8, + captureInterrupt: 0, // not available + compAInterrupt: 0x1c, + compBInterrupt: 0x1e, + ovfInterrupt: 0x20, + TIFR: 0x35, + OCRA: 0x47, + OCRB: 0x48, + ICR: 0, // not available + TCNT: 0x46, + TCCRA: 0x44, + TCCRB: 0x45, + TCCRC: 0, // not available + TIMSK: 0x6e, + dividers: timer01Dividers +}; + +export const timer1Config: AVRTimerConfig = { + bits: 16, + captureInterrupt: 0x14, + compAInterrupt: 0x16, + compBInterrupt: 0x18, + ovfInterrupt: 0x1a, + TIFR: 0x36, + OCRA: 0x88, + OCRB: 0x8a, + ICR: 0x86, + TCNT: 0x84, + TCCRA: 0x80, + TCCRB: 0x81, + TCCRC: 0x82, + TIMSK: 0x6f, + dividers: timer01Dividers +}; + +export const timer2Config: AVRTimerConfig = { + bits: 8, + captureInterrupt: 0, // not available + compAInterrupt: 0x0e, + compBInterrupt: 0x10, + ovfInterrupt: 0x12, + TIFR: 0x37, + OCRA: 0xb3, + OCRB: 0xb4, + ICR: 0, // not available + TCNT: 0xb2, + TCCRA: 0xb0, + TCCRB: 0xb1, + TCCRC: 0, // not available + TIMSK: 0x70, + dividers: { + 0: 1, + 1: 1, + 2: 8, + 3: 32, + 4: 64, + 5: 128, + 6: 256, + 7: 1024 + } +}; + +export class AVRTimer { + private mask = (1 << this.config.bits) - 1; + private lastCycle = 0; + private ocrA: u8 = 0; + private ocrB: u8 = 0; + + constructor(private cpu: CPU, private config: AVRTimerConfig) { + cpu.writeHooks[config.TCNT] = (value: u8) => { + this.TCNT = value; + this.timerUpdated(value); + return true; + }; + cpu.writeHooks[config.OCRA] = (value: u8) => { + // TODO implement buffering when timer running in PWM mode + this.ocrA = value; + }; + cpu.writeHooks[config.OCRB] = (value: u8) => { + this.ocrB = value; + }; + } + + reset() { + this.lastCycle = 0; + this.ocrA = 0; + this.ocrB = 0; + } + + get TIFR() { + return this.cpu.data[this.config.TIFR]; + } + + set TIFR(value: u8) { + this.cpu.data[this.config.TIFR] = value; + } + + get TCNT() { + return this.cpu.data[this.config.TCNT]; + } + + set TCNT(value: u8) { + this.cpu.data[this.config.TCNT] = value; + } + + get TCCRA() { + return this.cpu.data[this.config.TCCRA]; + } + + get TCCRB() { + return this.cpu.data[this.config.TCCRB]; + } + + get TIMSK() { + return this.cpu.data[this.config.TIMSK]; + } + + get CS() { + return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; + } + + get WGM() { + return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3); + } + + tick() { + const divider = this.config.dividers[this.CS]; + const delta = this.cpu.cycles - this.lastCycle; + if (divider && delta >= divider) { + const counterDelta = Math.floor(delta / divider); + this.lastCycle += counterDelta * divider; + const val = this.TCNT; + const newVal = (val + counterDelta) & this.mask; + this.TCNT = newVal; + this.timerUpdated(newVal); + if ( + (this.WGM === WGM_NORMAL || + this.WGM === WGM_PWM_PHASE_CORRECT || + this.WGM === WGM_FASTPWM) && + val > newVal + ) { + this.TIFR |= TOV; + } + } + if (this.cpu.interruptsEnabled) { + if (this.TIFR & TOV && this.TIMSK & TOIE) { + avrInterrupt(this.cpu, this.config.ovfInterrupt); + this.TIFR &= ~TOV; + } + if (this.TIFR & OCFA && this.TIMSK & OCIEA) { + avrInterrupt(this.cpu, this.config.compAInterrupt); + this.TIFR &= ~OCFA; + } + if (this.TIFR & OCFB && this.TIMSK & OCIEB) { + avrInterrupt(this.cpu, this.config.compBInterrupt); + this.TIFR &= ~OCFB; + } + } + } + + private timerUpdated(value: u8) { + if (this.ocrA && value === this.ocrA) { + this.TIFR |= OCFA; + if (this.WGM === WGM_CTC) { + // Clear Timer on Compare Match (CTC) Mode + this.TCNT = 0; + this.TIFR |= TOV; + } + } + if (this.ocrB && value === this.ocrB) { + this.TIFR |= OCFB; + } + } +} diff --git a/src/peripherals/twi.spec.ts b/src/peripherals/twi.spec.ts new file mode 100644 index 0000000..542cf2a --- /dev/null +++ b/src/peripherals/twi.spec.ts @@ -0,0 +1,390 @@ +import { CPU } from '../cpu/cpu'; +import { AVRTWI, twiConfig } from './twi'; +import { assemble } from '../utils/assembler'; +import { avrInstruction } from '../cpu/instruction'; + +const FREQ_16MHZ = 16e6; + +function asmProgram(source: string) { + const { bytes, errors, lines } = assemble(source); + if (errors.length) { + throw new Error('Assembly failed: ' + errors); + } + return { program: new Uint16Array(bytes.buffer), lines }; +} + +function runInstructions(cpu: CPU, twi: AVRTWI, count: number) { + for (let i = 0; i < count; i++) { + if (cpu.progMem[cpu.pc] === 0x9598) { + console.log(cpu.data[0xbc].toString(16)); + console.log(cpu.data[16]); + throw new Error('BREAK instruction encountered'); + } + avrInstruction(cpu); + twi.tick(); + } +} + +describe('TWI', () => { + const TWINT = 7; + const TWSTA = 5; + const TWEN = 2; + + it('should correctly calculate the sclFrequency from TWBR', () => { + const cpu = new CPU(new Uint16Array(1024)); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + cpu.writeData(0xb8, 0x48); // TWBR <- 0x48 + cpu.writeData(0xb9, 0); // TWSR <- 0 (prescaler: 1) + expect(twi.sclFrequency).toEqual(100000); + }); + + it('should take the prescaler into consideration when calculating sclFrequency', () => { + const cpu = new CPU(new Uint16Array(1024)); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + cpu.writeData(0xb8, 0x03); // TWBR <- 0x03 + cpu.writeData(0xb9, 0x01); // TWSR <- 1 (prescaler: 4) + expect(twi.sclFrequency).toEqual(400000); + }); + + it('should trigger data an interrupt if TWINT is set', () => { + const cpu = new CPU(new Uint16Array(1024)); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + cpu.writeData(0xbc, 0x81); // TWCR <- TWINT | TWIE + cpu.data[95] = 0x80; // SREG: I------- + twi.tick(); + expect(cpu.pc).toEqual(0x30); // 2-wire Serial Interface Vector + expect(cpu.cycles).toEqual(2); + expect(cpu.data[0xbc] & 0x80).toEqual(0); // UCSR0A should clear TWINT + }); + + describe('Master mode', () => { + it('should call the startEvent handler when TWSTA bit is written 1', () => { + const cpu = new CPU(new Uint16Array(1024)); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + jest.spyOn(twi.eventHandler, 'start'); + cpu.writeData(0xbc, (1 << TWINT) | (1 << TWSTA) | (1 << TWEN)); + twi.tick(); + expect(twi.eventHandler.start).toHaveBeenCalledWith(false); + }); + + it('should successfully transmit a byte to a slave', () => { + // based on the example in page 225 of the datasheet: + // https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf + const { program } = asmProgram(` + ; register addresses + _REPLACE TWSR, 0xb9 + _REPLACE TWDR, 0xbb + _REPLACE TWCR, 0xbc + + ; TWCR bits + _REPLACE TWEN, 0x04 + _REPLACE TWSTO, 0x10 + _REPLACE TWSTA, 0x20 + _REPLACE TWINT, 0x80 + + ; TWSR states + _REPLACE START, 0x8 ; TWI start + _REPLACE MT_SLA_ACK, 0x18 ; Slave Adresss ACK has been received + _REPLACE MT_DATA_ACK, 0x28 ; Data ACK has been received + + ; Send start condition + ldi r16, TWEN + sbr r16, TWSTA + sbr r16, TWINT + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the START condition has been transmitted + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from START go to ERROR + lds r16, TWSR + andi r16, 0xf8 + cpi r16, START + brne error + + ; Load SLA_W into TWDR Register. Clear TWINT bit in TWCR to start transmission of address + ; 0x44 = Address 0x22, write mode (R/W bit clear) + _REPLACE SLA_W, 0x44 + ldi r16, SLA_W + sts TWDR, r16 + ldi r16, TWINT + sbr r16, TWEN + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the SLA+W has been transmitted, and ACK/NACK has been received. + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_SLA_ACK go to ERROR + lds r16, TWSR + andi r16, 0xf8 + cpi r16, MT_SLA_ACK + brne error + + ; Load DATA into TWDR Register. Clear TWINT bit in TWCR to start transmission of data + _replace DATA, 0x55 + ldi r16, DATA + sts TWDR, r16 + ldi r16, TWINT + sbr r16, TWEN + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the DATA has been transmitted, and ACK/NACK has been received + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_ACK go to ERROR + lds r16, TWSR + andi r16, 0xf8 + cpi r16, MT_DATA_ACK + brne error + + ; Transmit STOP condition + ldi r16, TWINT + sbr r16, TWEN + sbr r16, TWSTO + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the STOP condition has been sent + call wait_for_twint + + ; Check value of TWI Status Register. The masked value should be 0xf8 once done + lds r16, TWSR + andi r16, 0xf8 + cpi r16, 0xf8 + brne error + + ; Indicate success by loading 0x42 into r17 + ldi r17, 0x42 + + loop: + jmp loop + + ; Busy-waits for the TWINT flag to be set + wait_for_twint: + lds r16, TWCR + andi r16, TWINT + breq wait_for_twint + ret + + ; In case of an error, toggle a breakpoint + error: + break + `); + const cpu = new CPU(program); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + twi.eventHandler = { + start: jest.fn(), + stop: jest.fn(), + connectToSlave: jest.fn(), + writeByte: jest.fn(), + readByte: jest.fn() + }; + + // Step 1: wait for start condition + runInstructions(cpu, twi, 4); + expect(twi.eventHandler.start).toHaveBeenCalledWith(false); + + runInstructions(cpu, twi, 16); + twi.completeStart(); + + // Step 2: wait for slave connect in write mode + runInstructions(cpu, twi, 16); + expect(twi.eventHandler.connectToSlave).toHaveBeenCalledWith(0x22, true); + + runInstructions(cpu, twi, 16); + twi.completeConnect(true); + + // Step 3: wait for first data byte + runInstructions(cpu, twi, 16); + expect(twi.eventHandler.writeByte).toHaveBeenCalledWith(0x55); + + runInstructions(cpu, twi, 16); + twi.completeWrite(true); + + // Step 4: wait for stop condition + runInstructions(cpu, twi, 16); + expect(twi.eventHandler.stop).toHaveBeenCalled(); + + runInstructions(cpu, twi, 16); + twi.completeStop(); + + // Step 5: wait for the assembly code to indicate success by settings r17 to 0x42 + runInstructions(cpu, twi, 16); + expect(cpu.data[17]).toEqual(0x42); + }); + + it('should successfully receive a byte from a slave', () => { + const { program } = asmProgram(` + ; register addresses + _REPLACE TWSR, 0xb9 + _REPLACE TWDR, 0xbb + _REPLACE TWCR, 0xbc + + ; TWCR bits + _REPLACE TWEN, 0x04 + _REPLACE TWSTO, 0x10 + _REPLACE TWSTA, 0x20 + _REPLACE TWEA, 0x40 + _REPLACE TWINT, 0x80 + + ; TWSR states + _REPLACE START, 0x8 ; TWI start + _REPLACE MT_SLAR_ACK, 0x40 ; Slave Adresss ACK has been received + _REPLACE MT_DATA_RECV, 0x50 ; Data has been received + _REPLACE MT_DATA_RECV_NACK, 0x58 ; Data has been received, NACK has been returned + + ; Send start condition + ldi r16, TWEN + sbr r16, TWSTA + sbr r16, TWINT + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the START condition has been transmitted + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from START go to ERROR + lds r16, TWSR + andi r16, 0xf8 + ldi r18, START + cpse r16, r18 + jmp error ; only jump if r16 != r18 (START) + + ; Load SLA_R into TWDR Register. Clear TWINT bit in TWCR to start transmission of address + ; 0xa1 = Address 0x50, read mode (R/W bit set) + _REPLACE SLA_R, 0xa1 + ldi r16, SLA_R + sts TWDR, r16 + ldi r16, TWINT + sbr r16, TWEN + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the SLA+W has been transmitted, and ACK/NACK has been received. + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_SLA_ACK go to ERROR + lds r16, TWSR + andi r16, 0xf8 + cpi r16, MT_SLAR_ACK + brne error + + ; Clear TWINT bit in TWCR to receive the next byte, set TWEA to send ACK + ldi r16, TWINT + sbr r16, TWEA + sbr r16, TWEN + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the DATA has been received, and ACK has been transmitted + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_RECV go to ERROR + lds r16, TWSR + andi r16, 0xf8 + cpi r16, MT_DATA_RECV + brne error + + ; Validate that we recieved the desired data - first byte should be 0x66 + lds r16, TWDR + cpi r16, 0x66 + brne error + + ; Clear TWINT bit in TWCR to receive the next byte, this time we don't ACK + ldi r16, TWINT + sbr r16, TWEN + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the DATA has been received, and NACK has been transmitted + call wait_for_twint + + ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_RECV_NACK go to ERROR + lds r16, TWSR + andi r16, 0xf8 + cpi r16, MT_DATA_RECV_NACK + brne error + + ; Validate that we recieved the desired data - second byte should be 0x77 + lds r16, TWDR + cpi r16, 0x77 + brne error + + ; Transmit STOP condition + ldi r16, TWINT + sbr r16, TWEN + sbr r16, TWSTO + sts TWCR, r16 + + ; Wait for TWINT Flag set. This indicates that the STOP condition has been sent + call wait_for_twint + + ; Check value of TWI Status Register. The masked value should be 0xf8 once done + lds r16, TWSR + andi r16, 0xf8 + cpi r16, 0xf8 + brne error + + ; Indicate success by loading 0x42 into r17 + ldi r17, 0x42 + + loop: + jmp loop + + ; Busy-waits for the TWINT flag to be set + wait_for_twint: + lds r16, TWCR + andi r16, TWINT + breq wait_for_twint + ret + + ; In case of an error, toggle a breakpoint + error: + break + `); + const cpu = new CPU(program); + const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); + twi.eventHandler = { + start: jest.fn(), + stop: jest.fn(), + connectToSlave: jest.fn(), + writeByte: jest.fn(), + readByte: jest.fn() + }; + + // Step 1: wait for start condition + runInstructions(cpu, twi, 4); + expect(twi.eventHandler.start).toHaveBeenCalledWith(false); + + runInstructions(cpu, twi, 16); + twi.completeStart(); + + // Step 2: wait for slave connect in read mode + runInstructions(cpu, twi, 16); + expect(twi.eventHandler.connectToSlave).toHaveBeenCalledWith(0x50, false); + + runInstructions(cpu, twi, 16); + twi.completeConnect(true); + + // Step 3: send the first byte to the master, expect ack + runInstructions(cpu, twi, 16); + expect(twi.eventHandler.readByte).toHaveBeenCalledWith(true); + + runInstructions(cpu, twi, 16); + twi.completeRead(0x66); + + // Step 4: send the first byte to the master, expect nack + runInstructions(cpu, twi, 16); + expect(twi.eventHandler.readByte).toHaveBeenCalledWith(false); + + runInstructions(cpu, twi, 16); + twi.completeRead(0x77); + + // Step 5: wait for stop condition + runInstructions(cpu, twi, 24); + expect(twi.eventHandler.stop).toHaveBeenCalled(); + + runInstructions(cpu, twi, 16); + twi.completeStop(); + + // Step 6: wait for the assembly code to indicate success by settings r17 to 0x42 + runInstructions(cpu, twi, 16); + expect(cpu.data[17]).toEqual(0x42); + }); + }); +}); diff --git a/src/peripherals/twi.ts b/src/peripherals/twi.ts new file mode 100644 index 0000000..ec0a206 --- /dev/null +++ b/src/peripherals/twi.ts @@ -0,0 +1,200 @@ +import { CPU } from '../cpu/cpu'; +import { avrInterrupt } from '../cpu/interrupt'; +import { u8 } from '../types'; + +export interface TWIEventHandler { + start(repeated: boolean): void; + + stop(): void; + + connectToSlave(addr: u8, write: boolean): void; + + writeByte(value: u8): void; + + readByte(ack: boolean): void; +} + +export interface TWIConfig { + twiInterrupt: u8; + + TWBR: u8; + TWCR: u8; + TWSR: u8; + TWDR: u8; + TWAR: u8; + TWAMR: u8; +} + +/* eslint-disable @typescript-eslint/no-unused-vars */ +// Register bits: +const TWCR_TWINT = 0x80; // TWI Interrupt Flag +const TWCR_TWEA = 0x40; // TWI Enable Acknowledge Bit +const TWCR_TWSTA = 0x20; // TWI START Condition Bit +const TWCR_TWSTO = 0x10; // TWI STOP Condition Bit +const TWCR_TWWC = 0x8; //TWI Write Collision Flag +const TWCR_TWEN = 0x4; // TWI Enable Bit +const TWCR_TWIE = 0x1; // TWI Interrupt Enable +const TWSR_TWS_MASK = 0xf8; // TWI Status +const TWSR_TWPS1 = 0x2; // TWI Prescaler Bits +const TWSR_TWPS0 = 0x1; // TWI Prescaler Bits +const TWSR_TWPS_MASK = TWSR_TWPS1 | TWSR_TWPS0; // TWI Prescaler mask +const TWAR_TWA_MASK = 0xfe; // TWI (Slave) Address Register +const TWAR_TWGCE = 0x1; // TWI General Call Recognition Enable Bit + +const STATUS_BUS_ERROR = 0x0; +const STATUS_TWI_IDLE = 0xf8; +// Master states +const STATUS_START = 0x08; +const STATUS_REPEATED_START = 0x10; +const STATUS_SLAW_ACK = 0x18; +const STATUS_SLAW_NACK = 0x20; +const STATUS_DATA_SENT_ACK = 0x28; +const STATUS_DATA_SENT_NACK = 0x30; +const STATUS_DATA_LOST_ARBITRATION = 0x38; +const STATUS_SLAR_ACK = 0x40; +const STATUS_SLAR_NACK = 0x48; +const STATUS_DATA_RECEIVED_ACK = 0x50; +const STATUS_DATA_RECEIVED_NACK = 0x58; +// TODO: add slave states +/* eslint-enable @typescript-eslint/no-unused-vars */ + +export const twiConfig: TWIConfig = { + twiInterrupt: 0x30, + TWBR: 0xb8, + TWSR: 0xb9, + TWAR: 0xba, + TWDR: 0xbb, + TWCR: 0xbc, + TWAMR: 0xbd +}; + +// A simple TWI Event Handler that sends a NACK for all events +export class NoopTWIEventHandler implements TWIEventHandler { + constructor(protected twi: AVRTWI) {} + + start() { + this.twi.completeStart(); + } + + stop() { + this.twi.completeStop(); + } + + connectToSlave() { + this.twi.completeConnect(false); + } + + writeByte() { + this.twi.completeWrite(false); + } + + readByte() { + this.twi.completeRead(0xff); + } +} + +export class AVRTWI { + public eventHandler: TWIEventHandler = new NoopTWIEventHandler(this); + + private nextTick: (() => void) | null = null; + + constructor(private cpu: CPU, private config: TWIConfig, private freqMHz: number) { + this.updateStatus(STATUS_TWI_IDLE); + this.cpu.writeHooks[config.TWCR] = (value) => { + const clearInt = value & TWCR_TWINT; + if (clearInt) { + value &= ~TWCR_TWINT; + } + const { status } = this; + if (clearInt && value & TWCR_TWEN) { + const twdrValue = this.cpu.data[this.config.TWDR]; + this.nextTick = () => { + if (value & TWCR_TWSTA) { + this.eventHandler.start(status !== STATUS_TWI_IDLE); + } else if (value & TWCR_TWSTO) { + this.eventHandler.stop(); + } else if (status === STATUS_START) { + this.eventHandler.connectToSlave(twdrValue >> 1, twdrValue & 0x1 ? false : true); + } else if (status === STATUS_SLAW_ACK || status === STATUS_DATA_SENT_ACK) { + this.eventHandler.writeByte(twdrValue); + } else if (status === STATUS_SLAR_ACK || status === STATUS_DATA_RECEIVED_ACK) { + const ack = !!(value & TWCR_TWEA); + this.eventHandler.readByte(ack); + } + }; + this.cpu.data[config.TWCR] = value; + return true; + } + }; + } + + tick() { + if (this.nextTick) { + this.nextTick(); + this.nextTick = null; + } + if (this.cpu.interruptsEnabled) { + const { TWCR, twiInterrupt } = this.config; + if (this.cpu.data[TWCR] & TWCR_TWIE && this.cpu.data[TWCR] & TWCR_TWINT) { + avrInterrupt(this.cpu, twiInterrupt); + this.cpu.data[TWCR] &= ~TWCR_TWINT; + } + } + } + + get prescaler() { + switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) { + case 0: + return 1; + case 1: + return 4; + case 2: + return 16; + case 3: + return 64; + } + // We should never get here: + throw new Error('Invalid prescaler value!'); + } + + get sclFrequency() { + return this.freqMHz / (16 + 2 * this.cpu.data[this.config.TWBR] * this.prescaler); + } + + completeStart() { + this.updateStatus(this.status === STATUS_TWI_IDLE ? STATUS_START : STATUS_REPEATED_START); + } + + completeStop() { + this.cpu.data[this.config.TWCR] &= ~TWCR_TWSTO; + this.updateStatus(STATUS_TWI_IDLE); + } + + completeConnect(ack: boolean) { + if (this.cpu.data[this.config.TWDR] & 0x1) { + this.updateStatus(ack ? STATUS_SLAR_ACK : STATUS_SLAR_NACK); + } else { + this.updateStatus(ack ? STATUS_SLAW_ACK : STATUS_SLAW_NACK); + } + } + + completeWrite(ack: boolean) { + this.updateStatus(ack ? STATUS_DATA_SENT_ACK : STATUS_DATA_SENT_NACK); + } + + completeRead(value: u8) { + const ack = !!(this.cpu.data[this.config.TWCR] & TWCR_TWEA); + this.cpu.data[this.config.TWDR] = value; + this.updateStatus(ack ? STATUS_DATA_RECEIVED_ACK : STATUS_DATA_RECEIVED_NACK); + } + + private get status() { + return this.cpu.data[this.config.TWSR] & TWSR_TWS_MASK; + } + + private updateStatus(value: u8) { + const { TWCR, TWSR } = this.config; + this.cpu.data[TWSR] = (this.cpu.data[TWSR] & ~TWSR_TWS_MASK) | value; + this.cpu.data[TWCR] |= TWCR_TWINT; + } +} diff --git a/src/peripherals/usart.spec.ts b/src/peripherals/usart.spec.ts new file mode 100644 index 0000000..d9843dc --- /dev/null +++ b/src/peripherals/usart.spec.ts @@ -0,0 +1,159 @@ +import { CPU } from '../cpu/cpu'; +import { AVRUSART, usart0Config } from './usart'; + +const FREQ_16MHZ = 16e6; +const FREQ_11_0529MHZ = 11059200; + +describe('USART', () => { + it('should correctly calculate the baudRate from UBRR', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_11_0529MHZ); + cpu.writeData(0xc5, 0); // UBRR0H <- 0 + cpu.writeData(0xc4, 5); // UBRR0L <- 5 + expect(usart.baudRate).toEqual(115200); + }); + + it('should correctly calculate the baudRate from UBRR in double-speed mode', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc5, 3); // UBRR0H <- 3 + cpu.writeData(0xc4, 64); // UBRR0L <- 64 + cpu.writeData(0xc0, 2); // UCSR0A: U2X0 + expect(usart.baudRate).toEqual(2400); + }); + + it('should return 5-bits per byte when UCSZ = 0', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0); + expect(usart.bitsPerChar).toEqual(5); + }); + + it('should return 6-bits per byte when UCSZ = 1', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x2); + expect(usart.bitsPerChar).toEqual(6); + }); + + it('should return 7-bits per byte when UCSZ = 2', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x4); + expect(usart.bitsPerChar).toEqual(7); + }); + + it('should return 8-bits per byte when UCSZ = 3', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x6); + expect(usart.bitsPerChar).toEqual(8); + }); + + it('should return 9-bits per byte when UCSZ = 7', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc0, 0x6); + cpu.writeData(0xc1, 0x4); + expect(usart.bitsPerChar).toEqual(9); + }); + + it('should invoke onByteTransmit when UDR0 is written to', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + usart.onByteTransmit = jest.fn(); + cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN + cpu.writeData(0xc6, 0x61); // UDR0 + expect(usart.onByteTransmit).toHaveBeenCalledWith(0x61); + }); + + it('should set UDRE and TXC flags after UDR0', () => { + const cpu = new CPU(new Uint16Array(1024)); + new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN + cpu.writeData(0xc0, 0); // UCSR0A <- 0 + cpu.writeData(0xc6, 0x61); // UDR0 + expect(cpu.data[0xc0]).toEqual(0x40 | 0x20); // UCSR0A: TXC | UDRE + }); + + describe('tick()', () => { + it('should trigger data register empty interrupt if UDRE is set', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc1, 0x28); // UCSR0B <- UDRIE | TXEN + cpu.writeData(0xc6, 0x61); // UDR0 + cpu.data[95] = 0x80; // SREG: I------- + usart.tick(); + expect(cpu.pc).toEqual(0x26); + expect(cpu.cycles).toEqual(2); + expect(cpu.data[0xc0] & 0x20).toEqual(0); // UCSR0A should clear UDRE + }); + + it('should trigger data tx complete interrupt if TXCIE is set', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc1, 0x48); // UCSR0B <- TXCIE | TXEN + cpu.writeData(0xc6, 0x61); // UDR0 + cpu.data[95] = 0x80; // SREG: I------- + usart.tick(); + expect(cpu.pc).toEqual(0x28); + expect(cpu.cycles).toEqual(2); + expect(cpu.data[0xc0] & 0x40).toEqual(0); // UCSR0A should clear TXC + }); + + it('should not trigger any interrupt if interrupts are disabled', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + cpu.writeData(0xc1, 0x28); // UCSR0B <- UDRIE | TXEN + cpu.writeData(0xc6, 0x61); // UDR0 + cpu.data[95] = 0; // SREG: 0 (disable interrupts) + usart.tick(); + expect(cpu.pc).toEqual(0); + expect(cpu.cycles).toEqual(0); + expect(cpu.data[0xc0]).toEqual(0x40 | 0x20); // UCSR0A: TXC | UDRE + }); + }); + + describe('onLineTransmit', () => { + it('should call onLineTransmit with the current line buffer after every newline', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + usart.onLineTransmit = jest.fn(); + cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN + cpu.writeData(0xc6, 0x48); // 'H' + cpu.writeData(0xc6, 0x65); // 'e' + cpu.writeData(0xc6, 0x6c); // 'l' + cpu.writeData(0xc6, 0x6c); // 'l' + cpu.writeData(0xc6, 0x6f); // 'o' + cpu.writeData(0xc6, 0xa); // '\n' + expect(usart.onLineTransmit).toHaveBeenCalledWith('Hello'); + }); + + it('should not call onLineTransmit if no newline was received', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + usart.onLineTransmit = jest.fn(); + cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN + cpu.writeData(0xc6, 0x48); // 'H' + cpu.writeData(0xc6, 0x69); // 'i' + expect(usart.onLineTransmit).not.toHaveBeenCalled(); + }); + + it('should clear the line buffer after each call to onLineTransmit', () => { + const cpu = new CPU(new Uint16Array(1024)); + const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + usart.onLineTransmit = jest.fn(); + cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN + cpu.writeData(0xc6, 0x48); // 'H' + cpu.writeData(0xc6, 0x69); // 'i' + cpu.writeData(0xc6, 0xa); // '\n' + cpu.writeData(0xc6, 0x74); // 't' + cpu.writeData(0xc6, 0x68); // 'h' + cpu.writeData(0xc6, 0x65); // 'e' + cpu.writeData(0xc6, 0x72); // 'r' + cpu.writeData(0xc6, 0x65); // 'e' + cpu.writeData(0xc6, 0xa); // '\n' + expect(usart.onLineTransmit).toHaveBeenCalledWith('there'); + }); + }); +}); diff --git a/src/peripherals/usart.ts b/src/peripherals/usart.ts new file mode 100644 index 0000000..b93c0ea --- /dev/null +++ b/src/peripherals/usart.ts @@ -0,0 +1,134 @@ +import { CPU } from '../cpu/cpu'; +import { avrInterrupt } from '../cpu/interrupt'; +import { u8 } from '../types'; + +export interface USARTConfig { + rxCompleteInterrupt: u8; + dataRegisterEmptyInterrupt: u8; + txCompleteInterrupt: u8; + + UCSRA: u8; + UCSRB: u8; + UCSRC: u8; + UBRRL: u8; + UBRRH: u8; + UDR: u8; +} + +export const usart0Config: USARTConfig = { + rxCompleteInterrupt: 0x24, + dataRegisterEmptyInterrupt: 0x26, + txCompleteInterrupt: 0x28, + UCSRA: 0xc0, + UCSRB: 0xc1, + UCSRC: 0xc2, + UBRRL: 0xc4, + UBRRH: 0xc5, + UDR: 0xc6 +}; + +export type USARTTransmitCallback = (value: u8) => void; +export type USARTLineTransmitCallback = (value: string) => void; + +/* eslint-disable @typescript-eslint/no-unused-vars */ +// Register bits: +const UCSRA_RXC = 0x80; // USART Receive Complete +const UCSRA_TXC = 0x40; // USART Transmit Complete +const UCSRA_UDRE = 0x20; // USART Data Register Empty +const UCSRA_FE = 0x10; // Frame Error +const UCSRA_DOR = 0x8; // Data OverRun +const UCSRA_UPE = 0x4; // USART Parity Error +const UCSRA_U2X = 0x2; // Double the USART Transmission Speed +const UCSRA_MPCM = 0x1; // Multi-processor Communication Mode +const UCSRB_RXCIE = 0x80; // RX Complete Interrupt Enable +const UCSRB_TXCIE = 0x40; // TX Complete Interrupt Enable +const UCSRB_UDRIE = 0x20; // USART Data Register Empty Interrupt Enable +const UCSRB_RXEN = 0x10; // Receiver Enable +const UCSRB_TXEN = 0x8; // Transmitter Enable +const UCSRB_UCSZ2 = 0x4; // Character Size 2 +const UCSRB_RXB8 = 0x2; // Receive Data Bit 8 +const UCSRB_TXB8 = 0x1; // Transmit Data Bit 8 +const UCSRC_UMSEL1 = 0x80; // USART Mode Select 1 +const UCSRC_UMSEL0 = 0x40; // USART Mode Select 0 +const UCSRC_UPM1 = 0x20; // Parity Mode 1 +const UCSRC_UPM0 = 0x10; // Parity Mode 0 +const UCSRC_USBS = 0x8; // Stop Bit Select +const UCSRC_UCSZ1 = 0x4; // Character Size 1 +const UCSRC_UCSZ0 = 0x2; // Character Size 0 +const UCSRC_UCPOL = 0x1; // Clock Polarity +/* eslint-enable @typescript-eslint/no-unused-vars */ + +export class AVRUSART { + public onByteTransmit: USARTTransmitCallback | null = null; + public onLineTransmit: USARTLineTransmitCallback | null = null; + + private lineBuffer = ''; + + constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) { + this.cpu.writeHooks[config.UCSRA] = (value) => { + this.cpu.data[config.UCSRA] = value | UCSRA_UDRE | UCSRA_TXC; + return true; + }; + this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => { + if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) { + // Enabling the transmission - mark UDR as empty + this.cpu.data[config.UCSRA] |= UCSRA_UDRE; + } + }; + this.cpu.writeHooks[config.UDR] = (value) => { + if (this.onByteTransmit) { + this.onByteTransmit(value); + } + if (this.onLineTransmit) { + const ch = String.fromCharCode(value); + if (ch === '\n') { + this.onLineTransmit(this.lineBuffer); + this.lineBuffer = ''; + } else { + this.lineBuffer += ch; + } + } + this.cpu.data[config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC; + }; + } + + tick() { + if (this.cpu.interruptsEnabled) { + const ucsra = this.cpu.data[this.config.UCSRA]; + const ucsrb = this.cpu.data[this.config.UCSRB]; + if (ucsra & UCSRA_UDRE && ucsrb & UCSRB_UDRIE) { + avrInterrupt(this.cpu, this.config.dataRegisterEmptyInterrupt); + this.cpu.data[this.config.UCSRA] &= ~UCSRA_UDRE; + } + if (ucsrb & UCSRA_TXC && ucsrb & UCSRB_TXCIE) { + avrInterrupt(this.cpu, this.config.txCompleteInterrupt); + this.cpu.data[this.config.UCSRA] &= ~UCSRA_TXC; + } + } + } + + get baudRate() { + const UBRR = (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL]; + const multiplier = this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16; + return Math.floor(this.freqMHz / (multiplier * (1 + UBRR))); + } + + get bitsPerChar() { + const ucsz = + ((this.cpu.data[this.config.UCSRA] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) | + (this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2); + switch (ucsz) { + case 0: + return 5; + case 1: + return 6; + case 2: + return 7; + case 3: + return 8; + default: // 4..6 are reserved + case 7: + return 9; + } + } +} diff --git a/src/timer.spec.ts b/src/timer.spec.ts deleted file mode 100644 index 4d631a7..0000000 --- a/src/timer.spec.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { CPU } from './cpu'; -import { AVRTimer, timer0Config, timer2Config } from './timer'; - -describe('timer', () => { - let cpu: CPU; - - beforeEach(() => { - cpu = new CPU(new Uint16Array(0x1000)); - }); - - it('should update timer every tick when prescaler is 1', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 - }); - - it('should update timer every 64 ticks when prescaler is 3', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x45] = 0x3; // TCCR0B.CS <- 3 - cpu.cycles = 64; - timer.tick(); - expect(cpu.data[0x46]).toEqual(1); // TCNT should be 1 - }); - - it('should not update timer if it has been disabled', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x45] = 0; // TCCR0B.CS <- 0 - cpu.cycles = 100000; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should stay 0 - }); - - it('should set TOV if timer overflows', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 - expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR - }); - - it('should set TOV if timer overflows in PWM Phase Correct mode', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff - cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f - cpu.writeData(0x44, 0x1); // WGM0 <- 1 (PWM, Phase Correct) - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 - expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR - }); - - it('should set TOV if timer overflows in FAST PWM mode', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff - cpu.writeData(0x47, 0x7f); // OCRA <- 0x7f - cpu.writeData(0x44, 0x3); // WGM0 <- 3 (FAST PWM) - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 - expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR - }); - - it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 - cpu.data[95] = 0x80; // SREG: I------- - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 - expect(cpu.data[0x35]).toEqual(0); // TOV bit in TIFR should be clear - expect(cpu.pc).toEqual(0x20); - expect(cpu.cycles).toEqual(3); - }); - - it('should not generate an overflow interrupt when global interrupts disabled', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.data[0x6e] = 0x1; // TIMSK0: TOIE0 - cpu.data[95] = 0x0; // SREG: -------- - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR should be set - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); - }); - - it('should not generate an overflow interrupt when TOIE0 is clear', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.data[0x46] = 0xff; // TCNT0 <- 0xff - cpu.data[0x45] = 0x1; // TCCR0B.CS <- 1 - cpu.data[0x6e] = 0; // TIMSK0: clear - cpu.data[95] = 0x80; // SREG: I------- - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x35]).toEqual(1); // TOV bit in TIFR should be set - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); - }); - - it('should set OCF0A flag when timer equals OCRA', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10 - cpu.writeData(0x47, 0x11); // OCR0A <- 0x11 - cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal) - cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x35]).toEqual(2); // TIFR0 should have OCF0A bit on - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); - }); - - it('should clear the timer in CTC mode if it equals to OCRA', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.writeData(0x46, 0x10); // TCNT0 <- 0x10 - cpu.writeData(0x47, 0x11); // OCR0A <- 0x11 - cpu.writeData(0x44, 0x2); // WGM0 <- 2 (CTC) - cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0); // TCNT should be 0 - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); - }); - - it('should set OCF0B flag when timer equals OCRB', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.writeData(0x46, 0x10); // TCNT0 <- 0x50 - cpu.writeData(0x48, 0x11); // OCR0B <- 0x51 - cpu.writeData(0x44, 0x0); // WGM0 <- 0 (Normal) - cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x35]).toEqual(4); // TIFR0 should have OCF0B bit on - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); - }); - - it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 - cpu.writeData(0x47, 0x21); // OCR0A <- 0x21 - cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 - cpu.writeData(0x6e, 0x2); // TIMSK0: OCIEA - cpu.writeData(95, 0x80); // SREG: I------- - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 - expect(cpu.data[0x35]).toEqual(0); // OCFA bit in TIFR should be clear - expect(cpu.pc).toEqual(0x1c); - expect(cpu.cycles).toEqual(3); - }); - - it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 - cpu.writeData(0x47, 0x21); // OCR0A <- 0x21 - cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 - cpu.writeData(0x6e, 0); // TIMSK0 - cpu.writeData(95, 0x80); // SREG: I------- - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); - }); - - it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => { - const timer = new AVRTimer(cpu, timer0Config); - cpu.writeData(0x46, 0x20); // TCNT0 <- 0x20 - cpu.writeData(0x48, 0x21); // OCR0B <- 0x21 - cpu.writeData(0x45, 0x1); // TCCR0B.CS <- 1 - cpu.writeData(0x6e, 0x4); // TIMSK0: OCIEB - cpu.writeData(95, 0x80); // SREG: I------- - cpu.cycles = 1; - timer.tick(); - expect(cpu.data[0x46]).toEqual(0x21); // TCNT should be 0x21 - expect(cpu.data[0x35]).toEqual(0); // OCFB bit in TIFR should be clear - expect(cpu.pc).toEqual(0x1e); - expect(cpu.cycles).toEqual(3); - }); - - it('timer2 should count every 256 ticks when prescaler is 6 (issue #5)', () => { - const timer = new AVRTimer(cpu, timer2Config); - cpu.data[0xb1] = 0x6; // TCCR1B.CS <- 6 - - cpu.cycles = 511; - timer.tick(); - expect(cpu.data[0xb2]).toEqual(1); // TCNT2 should be 2 - - cpu.cycles = 512; - timer.tick(); - expect(cpu.data[0xb2]).toEqual(2); // TCNT2 should be 2 - }); -}); diff --git a/src/timer.ts b/src/timer.ts deleted file mode 100644 index 4a120e7..0000000 --- a/src/timer.ts +++ /dev/null @@ -1,244 +0,0 @@ -/** - * AVR-8 Timers - * Part of AVR8js - * Reference: http://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf - * - * Copyright (C) 2019, Uri Shaked - */ - -import { CPU } from './cpu'; -import { avrInterrupt } from './interrupt'; - -const timer01Dividers = { - 0: 0, - 1: 1, - 2: 8, - 3: 64, - 4: 256, - 5: 1024, - 6: 0, // TODO: External clock source on T0 pin. Clock on falling edge. - 7: 0 // TODO: External clock source on T0 pin. Clock on rising edge. -}; - -const WGM_NORMAL = 0; -const WGM_PWM_PHASE_CORRECT = 1; -const WGM_CTC = 2; -const WGM_FASTPWM = 3; - -const TOV = 1; -const OCFA = 2; -const OCFB = 4; - -const TOIE = 1; -const OCIEA = 2; -const OCIEB = 4; - -type u8 = number; - -interface TimerDividers { - 0: number; - 1: number; - 2: number; - 3: number; - 4: number; - 5: number; - 6: number; - 7: number; -} - -interface AVRTimerConfig { - bits: 8 | 16; - captureInterrupt: u8; - compAInterrupt: u8; - compBInterrupt: u8; - ovfInterrupt: u8; - - // Register addresses - TIFR: u8; - OCRA: u8; - OCRB: u8; - ICR: u8; - TCNT: u8; - TCCRA: u8; - TCCRB: u8; - TCCRC: u8; - TIMSK: u8; - - dividers: TimerDividers; -} - -export const timer0Config: AVRTimerConfig = { - bits: 8, - captureInterrupt: 0, // not available - compAInterrupt: 0x1c, - compBInterrupt: 0x1e, - ovfInterrupt: 0x20, - TIFR: 0x35, - OCRA: 0x47, - OCRB: 0x48, - ICR: 0, // not available - TCNT: 0x46, - TCCRA: 0x44, - TCCRB: 0x45, - TCCRC: 0, // not available - TIMSK: 0x6e, - dividers: timer01Dividers -}; - -export const timer1Config: AVRTimerConfig = { - bits: 16, - captureInterrupt: 0x14, - compAInterrupt: 0x16, - compBInterrupt: 0x18, - ovfInterrupt: 0x1a, - TIFR: 0x36, - OCRA: 0x88, - OCRB: 0x8a, - ICR: 0x86, - TCNT: 0x84, - TCCRA: 0x80, - TCCRB: 0x81, - TCCRC: 0x82, - TIMSK: 0x6f, - dividers: timer01Dividers -}; - -export const timer2Config: AVRTimerConfig = { - bits: 8, - captureInterrupt: 0, // not available - compAInterrupt: 0x0e, - compBInterrupt: 0x10, - ovfInterrupt: 0x12, - TIFR: 0x37, - OCRA: 0xb3, - OCRB: 0xb4, - ICR: 0, // not available - TCNT: 0xb2, - TCCRA: 0xb0, - TCCRB: 0xb1, - TCCRC: 0, // not available - TIMSK: 0x70, - dividers: { - 0: 1, - 1: 1, - 2: 8, - 3: 32, - 4: 64, - 5: 128, - 6: 256, - 7: 1024 - } -}; - -export class AVRTimer { - private mask = (1 << this.config.bits) - 1; - private lastCycle = 0; - private ocrA: u8 = 0; - private ocrB: u8 = 0; - - constructor(private cpu: CPU, private config: AVRTimerConfig) { - cpu.writeHooks[config.TCNT] = (value: u8) => { - this.TCNT = value; - this.timerUpdated(value); - return true; - }; - cpu.writeHooks[config.OCRA] = (value: u8) => { - // TODO implement buffering when timer running in PWM mode - this.ocrA = value; - }; - cpu.writeHooks[config.OCRB] = (value: u8) => { - this.ocrB = value; - }; - } - - reset() { - this.lastCycle = 0; - this.ocrA = 0; - this.ocrB = 0; - } - - get TIFR() { - return this.cpu.data[this.config.TIFR]; - } - - set TIFR(value: u8) { - this.cpu.data[this.config.TIFR] = value; - } - - get TCNT() { - return this.cpu.data[this.config.TCNT]; - } - - set TCNT(value: u8) { - this.cpu.data[this.config.TCNT] = value; - } - - get TCCRA() { - return this.cpu.data[this.config.TCCRA]; - } - - get TCCRB() { - return this.cpu.data[this.config.TCCRB]; - } - - get TIMSK() { - return this.cpu.data[this.config.TIMSK]; - } - - get CS() { - return (this.TCCRB & 0x7) as 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; - } - - get WGM() { - return ((this.TCCRB & 0x8) >> 1) | (this.TCCRA & 0x3); - } - - tick() { - const divider = this.config.dividers[this.CS]; - const delta = this.cpu.cycles - this.lastCycle; - if (divider && delta >= divider) { - const counterDelta = Math.floor(delta / divider); - this.lastCycle += counterDelta * divider; - const val = this.TCNT; - const newVal = (val + counterDelta) & this.mask; - this.TCNT = newVal; - this.timerUpdated(newVal); - if ( - (this.WGM === WGM_NORMAL || - this.WGM === WGM_PWM_PHASE_CORRECT || - this.WGM === WGM_FASTPWM) && - val > newVal - ) { - this.TIFR |= TOV; - } - } - if (this.cpu.interruptsEnabled) { - if (this.TIFR & TOV && this.TIMSK & TOIE) { - avrInterrupt(this.cpu, this.config.ovfInterrupt); - this.TIFR &= ~TOV; - } - if (this.TIFR & OCFA && this.TIMSK & OCIEA) { - avrInterrupt(this.cpu, this.config.compAInterrupt); - this.TIFR &= ~OCFA; - } - if (this.TIFR & OCFB && this.TIMSK & OCIEB) { - avrInterrupt(this.cpu, this.config.compBInterrupt); - this.TIFR &= ~OCFB; - } - } - } - - private timerUpdated(value: u8) { - if (this.ocrA && value === this.ocrA) { - this.TIFR |= OCFA; - if (this.WGM === WGM_CTC) { - // Clear Timer on Compare Match (CTC) Mode - this.TCNT = 0; - this.TIFR |= TOV; - } - } - if (this.ocrB && value === this.ocrB) { - this.TIFR |= OCFB; - } - } -} diff --git a/src/twi.spec.ts b/src/twi.spec.ts deleted file mode 100644 index 369f22a..0000000 --- a/src/twi.spec.ts +++ /dev/null @@ -1,390 +0,0 @@ -import { CPU } from './cpu'; -import { AVRTWI, twiConfig } from './twi'; -import { assemble } from './utils/assembler'; -import { avrInstruction } from './instruction'; - -const FREQ_16MHZ = 16e6; - -function asmProgram(source: string) { - const { bytes, errors, lines } = assemble(source); - if (errors.length) { - throw new Error('Assembly failed: ' + errors); - } - return { program: new Uint16Array(bytes.buffer), lines }; -} - -function runInstructions(cpu: CPU, twi: AVRTWI, count: number) { - for (let i = 0; i < count; i++) { - if (cpu.progMem[cpu.pc] === 0x9598) { - console.log(cpu.data[0xbc].toString(16)); - console.log(cpu.data[16]); - throw new Error('BREAK instruction encountered'); - } - avrInstruction(cpu); - twi.tick(); - } -} - -describe('TWI', () => { - const TWINT = 7; - const TWSTA = 5; - const TWEN = 2; - - it('should correctly calculate the sclFrequency from TWBR', () => { - const cpu = new CPU(new Uint16Array(1024)); - const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - cpu.writeData(0xb8, 0x48); // TWBR <- 0x48 - cpu.writeData(0xb9, 0); // TWSR <- 0 (prescaler: 1) - expect(twi.sclFrequency).toEqual(100000); - }); - - it('should take the prescaler into consideration when calculating sclFrequency', () => { - const cpu = new CPU(new Uint16Array(1024)); - const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - cpu.writeData(0xb8, 0x03); // TWBR <- 0x03 - cpu.writeData(0xb9, 0x01); // TWSR <- 1 (prescaler: 4) - expect(twi.sclFrequency).toEqual(400000); - }); - - it('should trigger data an interrupt if TWINT is set', () => { - const cpu = new CPU(new Uint16Array(1024)); - const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - cpu.writeData(0xbc, 0x81); // TWCR <- TWINT | TWIE - cpu.data[95] = 0x80; // SREG: I------- - twi.tick(); - expect(cpu.pc).toEqual(0x30); // 2-wire Serial Interface Vector - expect(cpu.cycles).toEqual(2); - expect(cpu.data[0xbc] & 0x80).toEqual(0); // UCSR0A should clear TWINT - }); - - describe('Master mode', () => { - it('should call the startEvent handler when TWSTA bit is written 1', () => { - const cpu = new CPU(new Uint16Array(1024)); - const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - jest.spyOn(twi.eventHandler, 'start'); - cpu.writeData(0xbc, (1 << TWINT) | (1 << TWSTA) | (1 << TWEN)); - twi.tick(); - expect(twi.eventHandler.start).toHaveBeenCalledWith(false); - }); - - it('should successfully transmit a byte to a slave', () => { - // based on the example in page 225 of the datasheet: - // https://ww1.microchip.com/downloads/en/DeviceDoc/ATmega48A-PA-88A-PA-168A-PA-328-P-DS-DS40002061A.pdf - const { program } = asmProgram(` - ; register addresses - _REPLACE TWSR, 0xb9 - _REPLACE TWDR, 0xbb - _REPLACE TWCR, 0xbc - - ; TWCR bits - _REPLACE TWEN, 0x04 - _REPLACE TWSTO, 0x10 - _REPLACE TWSTA, 0x20 - _REPLACE TWINT, 0x80 - - ; TWSR states - _REPLACE START, 0x8 ; TWI start - _REPLACE MT_SLA_ACK, 0x18 ; Slave Adresss ACK has been received - _REPLACE MT_DATA_ACK, 0x28 ; Data ACK has been received - - ; Send start condition - ldi r16, TWEN - sbr r16, TWSTA - sbr r16, TWINT - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the START condition has been transmitted - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from START go to ERROR - lds r16, TWSR - andi r16, 0xf8 - cpi r16, START - brne error - - ; Load SLA_W into TWDR Register. Clear TWINT bit in TWCR to start transmission of address - ; 0x44 = Address 0x22, write mode (R/W bit clear) - _REPLACE SLA_W, 0x44 - ldi r16, SLA_W - sts TWDR, r16 - ldi r16, TWINT - sbr r16, TWEN - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the SLA+W has been transmitted, and ACK/NACK has been received. - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_SLA_ACK go to ERROR - lds r16, TWSR - andi r16, 0xf8 - cpi r16, MT_SLA_ACK - brne error - - ; Load DATA into TWDR Register. Clear TWINT bit in TWCR to start transmission of data - _replace DATA, 0x55 - ldi r16, DATA - sts TWDR, r16 - ldi r16, TWINT - sbr r16, TWEN - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the DATA has been transmitted, and ACK/NACK has been received - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_ACK go to ERROR - lds r16, TWSR - andi r16, 0xf8 - cpi r16, MT_DATA_ACK - brne error - - ; Transmit STOP condition - ldi r16, TWINT - sbr r16, TWEN - sbr r16, TWSTO - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the STOP condition has been sent - call wait_for_twint - - ; Check value of TWI Status Register. The masked value should be 0xf8 once done - lds r16, TWSR - andi r16, 0xf8 - cpi r16, 0xf8 - brne error - - ; Indicate success by loading 0x42 into r17 - ldi r17, 0x42 - - loop: - jmp loop - - ; Busy-waits for the TWINT flag to be set - wait_for_twint: - lds r16, TWCR - andi r16, TWINT - breq wait_for_twint - ret - - ; In case of an error, toggle a breakpoint - error: - break - `); - const cpu = new CPU(program); - const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - twi.eventHandler = { - start: jest.fn(), - stop: jest.fn(), - connectToSlave: jest.fn(), - writeByte: jest.fn(), - readByte: jest.fn() - }; - - // Step 1: wait for start condition - runInstructions(cpu, twi, 4); - expect(twi.eventHandler.start).toHaveBeenCalledWith(false); - - runInstructions(cpu, twi, 16); - twi.completeStart(); - - // Step 2: wait for slave connect in write mode - runInstructions(cpu, twi, 16); - expect(twi.eventHandler.connectToSlave).toHaveBeenCalledWith(0x22, true); - - runInstructions(cpu, twi, 16); - twi.completeConnect(true); - - // Step 3: wait for first data byte - runInstructions(cpu, twi, 16); - expect(twi.eventHandler.writeByte).toHaveBeenCalledWith(0x55); - - runInstructions(cpu, twi, 16); - twi.completeWrite(true); - - // Step 4: wait for stop condition - runInstructions(cpu, twi, 16); - expect(twi.eventHandler.stop).toHaveBeenCalled(); - - runInstructions(cpu, twi, 16); - twi.completeStop(); - - // Step 5: wait for the assembly code to indicate success by settings r17 to 0x42 - runInstructions(cpu, twi, 16); - expect(cpu.data[17]).toEqual(0x42); - }); - - it('should successfully receive a byte from a slave', () => { - const { program } = asmProgram(` - ; register addresses - _REPLACE TWSR, 0xb9 - _REPLACE TWDR, 0xbb - _REPLACE TWCR, 0xbc - - ; TWCR bits - _REPLACE TWEN, 0x04 - _REPLACE TWSTO, 0x10 - _REPLACE TWSTA, 0x20 - _REPLACE TWEA, 0x40 - _REPLACE TWINT, 0x80 - - ; TWSR states - _REPLACE START, 0x8 ; TWI start - _REPLACE MT_SLAR_ACK, 0x40 ; Slave Adresss ACK has been received - _REPLACE MT_DATA_RECV, 0x50 ; Data has been received - _REPLACE MT_DATA_RECV_NACK, 0x58 ; Data has been received, NACK has been returned - - ; Send start condition - ldi r16, TWEN - sbr r16, TWSTA - sbr r16, TWINT - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the START condition has been transmitted - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from START go to ERROR - lds r16, TWSR - andi r16, 0xf8 - ldi r18, START - cpse r16, r18 - jmp error ; only jump if r16 != r18 (START) - - ; Load SLA_R into TWDR Register. Clear TWINT bit in TWCR to start transmission of address - ; 0xa1 = Address 0x50, read mode (R/W bit set) - _REPLACE SLA_R, 0xa1 - ldi r16, SLA_R - sts TWDR, r16 - ldi r16, TWINT - sbr r16, TWEN - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the SLA+W has been transmitted, and ACK/NACK has been received. - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_SLA_ACK go to ERROR - lds r16, TWSR - andi r16, 0xf8 - cpi r16, MT_SLAR_ACK - brne error - - ; Clear TWINT bit in TWCR to receive the next byte, set TWEA to send ACK - ldi r16, TWINT - sbr r16, TWEA - sbr r16, TWEN - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the DATA has been received, and ACK has been transmitted - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_RECV go to ERROR - lds r16, TWSR - andi r16, 0xf8 - cpi r16, MT_DATA_RECV - brne error - - ; Validate that we recieved the desired data - first byte should be 0x66 - lds r16, TWDR - cpi r16, 0x66 - brne error - - ; Clear TWINT bit in TWCR to receive the next byte, this time we don't ACK - ldi r16, TWINT - sbr r16, TWEN - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the DATA has been received, and NACK has been transmitted - call wait_for_twint - - ; Check value of TWI Status Register. Mask prescaler bits. If status different from MT_DATA_RECV_NACK go to ERROR - lds r16, TWSR - andi r16, 0xf8 - cpi r16, MT_DATA_RECV_NACK - brne error - - ; Validate that we recieved the desired data - second byte should be 0x77 - lds r16, TWDR - cpi r16, 0x77 - brne error - - ; Transmit STOP condition - ldi r16, TWINT - sbr r16, TWEN - sbr r16, TWSTO - sts TWCR, r16 - - ; Wait for TWINT Flag set. This indicates that the STOP condition has been sent - call wait_for_twint - - ; Check value of TWI Status Register. The masked value should be 0xf8 once done - lds r16, TWSR - andi r16, 0xf8 - cpi r16, 0xf8 - brne error - - ; Indicate success by loading 0x42 into r17 - ldi r17, 0x42 - - loop: - jmp loop - - ; Busy-waits for the TWINT flag to be set - wait_for_twint: - lds r16, TWCR - andi r16, TWINT - breq wait_for_twint - ret - - ; In case of an error, toggle a breakpoint - error: - break - `); - const cpu = new CPU(program); - const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - twi.eventHandler = { - start: jest.fn(), - stop: jest.fn(), - connectToSlave: jest.fn(), - writeByte: jest.fn(), - readByte: jest.fn() - }; - - // Step 1: wait for start condition - runInstructions(cpu, twi, 4); - expect(twi.eventHandler.start).toHaveBeenCalledWith(false); - - runInstructions(cpu, twi, 16); - twi.completeStart(); - - // Step 2: wait for slave connect in read mode - runInstructions(cpu, twi, 16); - expect(twi.eventHandler.connectToSlave).toHaveBeenCalledWith(0x50, false); - - runInstructions(cpu, twi, 16); - twi.completeConnect(true); - - // Step 3: send the first byte to the master, expect ack - runInstructions(cpu, twi, 16); - expect(twi.eventHandler.readByte).toHaveBeenCalledWith(true); - - runInstructions(cpu, twi, 16); - twi.completeRead(0x66); - - // Step 4: send the first byte to the master, expect nack - runInstructions(cpu, twi, 16); - expect(twi.eventHandler.readByte).toHaveBeenCalledWith(false); - - runInstructions(cpu, twi, 16); - twi.completeRead(0x77); - - // Step 5: wait for stop condition - runInstructions(cpu, twi, 24); - expect(twi.eventHandler.stop).toHaveBeenCalled(); - - runInstructions(cpu, twi, 16); - twi.completeStop(); - - // Step 6: wait for the assembly code to indicate success by settings r17 to 0x42 - runInstructions(cpu, twi, 16); - expect(cpu.data[17]).toEqual(0x42); - }); - }); -}); diff --git a/src/twi.ts b/src/twi.ts deleted file mode 100644 index fd0645b..0000000 --- a/src/twi.ts +++ /dev/null @@ -1,200 +0,0 @@ -import { CPU } from './cpu'; -import { avrInterrupt } from './interrupt'; -import { u8 } from './types'; - -export interface TWIEventHandler { - start(repeated: boolean): void; - - stop(): void; - - connectToSlave(addr: u8, write: boolean): void; - - writeByte(value: u8): void; - - readByte(ack: boolean): void; -} - -export interface TWIConfig { - twiInterrupt: u8; - - TWBR: u8; - TWCR: u8; - TWSR: u8; - TWDR: u8; - TWAR: u8; - TWAMR: u8; -} - -/* eslint-disable @typescript-eslint/no-unused-vars */ -// Register bits: -const TWCR_TWINT = 0x80; // TWI Interrupt Flag -const TWCR_TWEA = 0x40; // TWI Enable Acknowledge Bit -const TWCR_TWSTA = 0x20; // TWI START Condition Bit -const TWCR_TWSTO = 0x10; // TWI STOP Condition Bit -const TWCR_TWWC = 0x8; //TWI Write Collision Flag -const TWCR_TWEN = 0x4; // TWI Enable Bit -const TWCR_TWIE = 0x1; // TWI Interrupt Enable -const TWSR_TWS_MASK = 0xf8; // TWI Status -const TWSR_TWPS1 = 0x2; // TWI Prescaler Bits -const TWSR_TWPS0 = 0x1; // TWI Prescaler Bits -const TWSR_TWPS_MASK = TWSR_TWPS1 | TWSR_TWPS0; // TWI Prescaler mask -const TWAR_TWA_MASK = 0xfe; // TWI (Slave) Address Register -const TWAR_TWGCE = 0x1; // TWI General Call Recognition Enable Bit - -const STATUS_BUS_ERROR = 0x0; -const STATUS_TWI_IDLE = 0xf8; -// Master states -const STATUS_START = 0x08; -const STATUS_REPEATED_START = 0x10; -const STATUS_SLAW_ACK = 0x18; -const STATUS_SLAW_NACK = 0x20; -const STATUS_DATA_SENT_ACK = 0x28; -const STATUS_DATA_SENT_NACK = 0x30; -const STATUS_DATA_LOST_ARBITRATION = 0x38; -const STATUS_SLAR_ACK = 0x40; -const STATUS_SLAR_NACK = 0x48; -const STATUS_DATA_RECEIVED_ACK = 0x50; -const STATUS_DATA_RECEIVED_NACK = 0x58; -// TODO: add slave states -/* eslint-enable @typescript-eslint/no-unused-vars */ - -export const twiConfig: TWIConfig = { - twiInterrupt: 0x30, - TWBR: 0xb8, - TWSR: 0xb9, - TWAR: 0xba, - TWDR: 0xbb, - TWCR: 0xbc, - TWAMR: 0xbd -}; - -// A simple TWI Event Handler that sends a NACK for all events -export class NoopTWIEventHandler implements TWIEventHandler { - constructor(protected twi: AVRTWI) {} - - start() { - this.twi.completeStart(); - } - - stop() { - this.twi.completeStop(); - } - - connectToSlave() { - this.twi.completeConnect(false); - } - - writeByte() { - this.twi.completeWrite(false); - } - - readByte() { - this.twi.completeRead(0xff); - } -} - -export class AVRTWI { - public eventHandler: TWIEventHandler = new NoopTWIEventHandler(this); - - private nextTick: (() => void) | null = null; - - constructor(private cpu: CPU, private config: TWIConfig, private freqMHz: number) { - this.updateStatus(STATUS_TWI_IDLE); - this.cpu.writeHooks[config.TWCR] = (value) => { - const clearInt = value & TWCR_TWINT; - if (clearInt) { - value &= ~TWCR_TWINT; - } - const { status } = this; - if (clearInt && value & TWCR_TWEN) { - const twdrValue = this.cpu.data[this.config.TWDR]; - this.nextTick = () => { - if (value & TWCR_TWSTA) { - this.eventHandler.start(status !== STATUS_TWI_IDLE); - } else if (value & TWCR_TWSTO) { - this.eventHandler.stop(); - } else if (status === STATUS_START) { - this.eventHandler.connectToSlave(twdrValue >> 1, twdrValue & 0x1 ? false : true); - } else if (status === STATUS_SLAW_ACK || status === STATUS_DATA_SENT_ACK) { - this.eventHandler.writeByte(twdrValue); - } else if (status === STATUS_SLAR_ACK || status === STATUS_DATA_RECEIVED_ACK) { - const ack = !!(value & TWCR_TWEA); - this.eventHandler.readByte(ack); - } - }; - this.cpu.data[config.TWCR] = value; - return true; - } - }; - } - - tick() { - if (this.nextTick) { - this.nextTick(); - this.nextTick = null; - } - if (this.cpu.interruptsEnabled) { - const { TWCR, twiInterrupt } = this.config; - if (this.cpu.data[TWCR] & TWCR_TWIE && this.cpu.data[TWCR] & TWCR_TWINT) { - avrInterrupt(this.cpu, twiInterrupt); - this.cpu.data[TWCR] &= ~TWCR_TWINT; - } - } - } - - get prescaler() { - switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) { - case 0: - return 1; - case 1: - return 4; - case 2: - return 16; - case 3: - return 64; - } - // We should never get here: - throw new Error('Invalid prescaler value!'); - } - - get sclFrequency() { - return this.freqMHz / (16 + 2 * this.cpu.data[this.config.TWBR] * this.prescaler); - } - - completeStart() { - this.updateStatus(this.status === STATUS_TWI_IDLE ? STATUS_START : STATUS_REPEATED_START); - } - - completeStop() { - this.cpu.data[this.config.TWCR] &= ~TWCR_TWSTO; - this.updateStatus(STATUS_TWI_IDLE); - } - - completeConnect(ack: boolean) { - if (this.cpu.data[this.config.TWDR] & 0x1) { - this.updateStatus(ack ? STATUS_SLAR_ACK : STATUS_SLAR_NACK); - } else { - this.updateStatus(ack ? STATUS_SLAW_ACK : STATUS_SLAW_NACK); - } - } - - completeWrite(ack: boolean) { - this.updateStatus(ack ? STATUS_DATA_SENT_ACK : STATUS_DATA_SENT_NACK); - } - - completeRead(value: u8) { - const ack = !!(this.cpu.data[this.config.TWCR] & TWCR_TWEA); - this.cpu.data[this.config.TWDR] = value; - this.updateStatus(ack ? STATUS_DATA_RECEIVED_ACK : STATUS_DATA_RECEIVED_NACK); - } - - private get status() { - return this.cpu.data[this.config.TWSR] & TWSR_TWS_MASK; - } - - private updateStatus(value: u8) { - const { TWCR, TWSR } = this.config; - this.cpu.data[TWSR] = (this.cpu.data[TWSR] & ~TWSR_TWS_MASK) | value; - this.cpu.data[TWCR] |= TWCR_TWINT; - } -} diff --git a/src/usart.spec.ts b/src/usart.spec.ts deleted file mode 100644 index 222017c..0000000 --- a/src/usart.spec.ts +++ /dev/null @@ -1,159 +0,0 @@ -import { CPU } from './cpu'; -import { AVRUSART, usart0Config } from './usart'; - -const FREQ_16MHZ = 16e6; -const FREQ_11_0529MHZ = 11059200; - -describe('USART', () => { - it('should correctly calculate the baudRate from UBRR', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_11_0529MHZ); - cpu.writeData(0xc5, 0); // UBRR0H <- 0 - cpu.writeData(0xc4, 5); // UBRR0L <- 5 - expect(usart.baudRate).toEqual(115200); - }); - - it('should correctly calculate the baudRate from UBRR in double-speed mode', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc5, 3); // UBRR0H <- 3 - cpu.writeData(0xc4, 64); // UBRR0L <- 64 - cpu.writeData(0xc0, 2); // UCSR0A: U2X0 - expect(usart.baudRate).toEqual(2400); - }); - - it('should return 5-bits per byte when UCSZ = 0', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc0, 0); - expect(usart.bitsPerChar).toEqual(5); - }); - - it('should return 6-bits per byte when UCSZ = 1', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc0, 0x2); - expect(usart.bitsPerChar).toEqual(6); - }); - - it('should return 7-bits per byte when UCSZ = 2', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc0, 0x4); - expect(usart.bitsPerChar).toEqual(7); - }); - - it('should return 8-bits per byte when UCSZ = 3', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc0, 0x6); - expect(usart.bitsPerChar).toEqual(8); - }); - - it('should return 9-bits per byte when UCSZ = 7', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc0, 0x6); - cpu.writeData(0xc1, 0x4); - expect(usart.bitsPerChar).toEqual(9); - }); - - it('should invoke onByteTransmit when UDR0 is written to', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - usart.onByteTransmit = jest.fn(); - cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN - cpu.writeData(0xc6, 0x61); // UDR0 - expect(usart.onByteTransmit).toHaveBeenCalledWith(0x61); - }); - - it('should set UDRE and TXC flags after UDR0', () => { - const cpu = new CPU(new Uint16Array(1024)); - new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN - cpu.writeData(0xc0, 0); // UCSR0A <- 0 - cpu.writeData(0xc6, 0x61); // UDR0 - expect(cpu.data[0xc0]).toEqual(0x40 | 0x20); // UCSR0A: TXC | UDRE - }); - - describe('tick()', () => { - it('should trigger data register empty interrupt if UDRE is set', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc1, 0x28); // UCSR0B <- UDRIE | TXEN - cpu.writeData(0xc6, 0x61); // UDR0 - cpu.data[95] = 0x80; // SREG: I------- - usart.tick(); - expect(cpu.pc).toEqual(0x26); - expect(cpu.cycles).toEqual(2); - expect(cpu.data[0xc0] & 0x20).toEqual(0); // UCSR0A should clear UDRE - }); - - it('should trigger data tx complete interrupt if TXCIE is set', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc1, 0x48); // UCSR0B <- TXCIE | TXEN - cpu.writeData(0xc6, 0x61); // UDR0 - cpu.data[95] = 0x80; // SREG: I------- - usart.tick(); - expect(cpu.pc).toEqual(0x28); - expect(cpu.cycles).toEqual(2); - expect(cpu.data[0xc0] & 0x40).toEqual(0); // UCSR0A should clear TXC - }); - - it('should not trigger any interrupt if interrupts are disabled', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - cpu.writeData(0xc1, 0x28); // UCSR0B <- UDRIE | TXEN - cpu.writeData(0xc6, 0x61); // UDR0 - cpu.data[95] = 0; // SREG: 0 (disable interrupts) - usart.tick(); - expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(0); - expect(cpu.data[0xc0]).toEqual(0x40 | 0x20); // UCSR0A: TXC | UDRE - }); - }); - - describe('onLineTransmit', () => { - it('should call onLineTransmit with the current line buffer after every newline', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - usart.onLineTransmit = jest.fn(); - cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN - cpu.writeData(0xc6, 0x48); // 'H' - cpu.writeData(0xc6, 0x65); // 'e' - cpu.writeData(0xc6, 0x6c); // 'l' - cpu.writeData(0xc6, 0x6c); // 'l' - cpu.writeData(0xc6, 0x6f); // 'o' - cpu.writeData(0xc6, 0xa); // '\n' - expect(usart.onLineTransmit).toHaveBeenCalledWith('Hello'); - }); - - it('should not call onLineTransmit if no newline was received', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - usart.onLineTransmit = jest.fn(); - cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN - cpu.writeData(0xc6, 0x48); // 'H' - cpu.writeData(0xc6, 0x69); // 'i' - expect(usart.onLineTransmit).not.toHaveBeenCalled(); - }); - - it('should clear the line buffer after each call to onLineTransmit', () => { - const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); - usart.onLineTransmit = jest.fn(); - cpu.writeData(0xc1, 0x8); // UCSR0B <- TXEN - cpu.writeData(0xc6, 0x48); // 'H' - cpu.writeData(0xc6, 0x69); // 'i' - cpu.writeData(0xc6, 0xa); // '\n' - cpu.writeData(0xc6, 0x74); // 't' - cpu.writeData(0xc6, 0x68); // 'h' - cpu.writeData(0xc6, 0x65); // 'e' - cpu.writeData(0xc6, 0x72); // 'r' - cpu.writeData(0xc6, 0x65); // 'e' - cpu.writeData(0xc6, 0xa); // '\n' - expect(usart.onLineTransmit).toHaveBeenCalledWith('there'); - }); - }); -}); diff --git a/src/usart.ts b/src/usart.ts deleted file mode 100644 index 6838736..0000000 --- a/src/usart.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { CPU } from './cpu'; -import { avrInterrupt } from './interrupt'; -import { u8 } from './types'; - -export interface USARTConfig { - rxCompleteInterrupt: u8; - dataRegisterEmptyInterrupt: u8; - txCompleteInterrupt: u8; - - UCSRA: u8; - UCSRB: u8; - UCSRC: u8; - UBRRL: u8; - UBRRH: u8; - UDR: u8; -} - -export const usart0Config: USARTConfig = { - rxCompleteInterrupt: 0x24, - dataRegisterEmptyInterrupt: 0x26, - txCompleteInterrupt: 0x28, - UCSRA: 0xc0, - UCSRB: 0xc1, - UCSRC: 0xc2, - UBRRL: 0xc4, - UBRRH: 0xc5, - UDR: 0xc6 -}; - -export type USARTTransmitCallback = (value: u8) => void; -export type USARTLineTransmitCallback = (value: string) => void; - -/* eslint-disable @typescript-eslint/no-unused-vars */ -// Register bits: -const UCSRA_RXC = 0x80; // USART Receive Complete -const UCSRA_TXC = 0x40; // USART Transmit Complete -const UCSRA_UDRE = 0x20; // USART Data Register Empty -const UCSRA_FE = 0x10; // Frame Error -const UCSRA_DOR = 0x8; // Data OverRun -const UCSRA_UPE = 0x4; // USART Parity Error -const UCSRA_U2X = 0x2; // Double the USART Transmission Speed -const UCSRA_MPCM = 0x1; // Multi-processor Communication Mode -const UCSRB_RXCIE = 0x80; // RX Complete Interrupt Enable -const UCSRB_TXCIE = 0x40; // TX Complete Interrupt Enable -const UCSRB_UDRIE = 0x20; // USART Data Register Empty Interrupt Enable -const UCSRB_RXEN = 0x10; // Receiver Enable -const UCSRB_TXEN = 0x8; // Transmitter Enable -const UCSRB_UCSZ2 = 0x4; // Character Size 2 -const UCSRB_RXB8 = 0x2; // Receive Data Bit 8 -const UCSRB_TXB8 = 0x1; // Transmit Data Bit 8 -const UCSRC_UMSEL1 = 0x80; // USART Mode Select 1 -const UCSRC_UMSEL0 = 0x40; // USART Mode Select 0 -const UCSRC_UPM1 = 0x20; // Parity Mode 1 -const UCSRC_UPM0 = 0x10; // Parity Mode 0 -const UCSRC_USBS = 0x8; // Stop Bit Select -const UCSRC_UCSZ1 = 0x4; // Character Size 1 -const UCSRC_UCSZ0 = 0x2; // Character Size 0 -const UCSRC_UCPOL = 0x1; // Clock Polarity -/* eslint-enable @typescript-eslint/no-unused-vars */ - -export class AVRUSART { - public onByteTransmit: USARTTransmitCallback | null = null; - public onLineTransmit: USARTLineTransmitCallback | null = null; - - private lineBuffer = ''; - - constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) { - this.cpu.writeHooks[config.UCSRA] = (value) => { - this.cpu.data[config.UCSRA] = value | UCSRA_UDRE | UCSRA_TXC; - return true; - }; - this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => { - if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) { - // Enabling the transmission - mark UDR as empty - this.cpu.data[config.UCSRA] |= UCSRA_UDRE; - } - }; - this.cpu.writeHooks[config.UDR] = (value) => { - if (this.onByteTransmit) { - this.onByteTransmit(value); - } - if (this.onLineTransmit) { - const ch = String.fromCharCode(value); - if (ch === '\n') { - this.onLineTransmit(this.lineBuffer); - this.lineBuffer = ''; - } else { - this.lineBuffer += ch; - } - } - this.cpu.data[config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC; - }; - } - - tick() { - if (this.cpu.interruptsEnabled) { - const ucsra = this.cpu.data[this.config.UCSRA]; - const ucsrb = this.cpu.data[this.config.UCSRB]; - if (ucsra & UCSRA_UDRE && ucsrb & UCSRB_UDRIE) { - avrInterrupt(this.cpu, this.config.dataRegisterEmptyInterrupt); - this.cpu.data[this.config.UCSRA] &= ~UCSRA_UDRE; - } - if (ucsrb & UCSRA_TXC && ucsrb & UCSRB_TXCIE) { - avrInterrupt(this.cpu, this.config.txCompleteInterrupt); - this.cpu.data[this.config.UCSRA] &= ~UCSRA_TXC; - } - } - } - - get baudRate() { - const UBRR = (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL]; - const multiplier = this.cpu.data[this.config.UCSRA] & UCSRA_U2X ? 8 : 16; - return Math.floor(this.freqMHz / (multiplier * (1 + UBRR))); - } - - get bitsPerChar() { - const ucsz = - ((this.cpu.data[this.config.UCSRA] & (UCSRC_UCSZ1 | UCSRC_UCSZ0)) >> 1) | - (this.cpu.data[this.config.UCSRB] & UCSRB_UCSZ2); - switch (ucsz) { - case 0: - return 5; - case 1: - return 6; - case 2: - return 7; - case 3: - return 8; - default: // 4..6 are reserved - case 7: - return 9; - } - } -} -- cgit v1.2.3