aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2020-12-09 00:51:13 +0200
committerUri Shaked2020-12-09 00:51:58 +0200
commit36c4134a26063248a2ef47f5ac8defe50d9476b1 (patch)
tree44433290b0d684768216b33550a7af9a6fb4235f /src
parenttest(cpu): improve test name (diff)
downloadavr8js-36c4134a26063248a2ef47f5ac8defe50d9476b1.tar.gz
avr8js-36c4134a26063248a2ef47f5ac8defe50d9476b1.tar.bz2
avr8js-36c4134a26063248a2ef47f5ac8defe50d9476b1.zip
refactor: central interrupt handling #38
Diffstat (limited to 'src')
-rw-r--r--src/cpu/cpu.ts79
-rw-r--r--src/peripherals/eeprom.spec.ts12
-rw-r--r--src/peripherals/eeprom.ts26
-rw-r--r--src/peripherals/spi.spec.ts5
-rw-r--r--src/peripherals/spi.ts30
-rw-r--r--src/peripherals/timer.spec.ts58
-rw-r--r--src/peripherals/timer.ts78
-rw-r--r--src/peripherals/twi.spec.ts5
-rw-r--r--src/peripherals/twi.ts30
-rw-r--r--src/peripherals/usart.spec.ts6
-rw-r--r--src/peripherals/usart.ts47
-rw-r--r--src/types.ts1
12 files changed, 285 insertions, 92 deletions
diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts
index ed88e71..4b70efb 100644
--- a/src/cpu/cpu.ts
+++ b/src/cpu/cpu.ts
@@ -5,7 +5,8 @@
* Copyright (C) 2019, Uri Shaked
*/
-import { u32, u16, u8 } from '../types';
+import { u32, u16, u8, i16 } from '../types';
+import { avrInterrupt } from './interrupt';
const registerSpace = 0x100;
@@ -45,6 +46,15 @@ export interface CPUMemoryReadHooks {
[key: number]: CPUMemoryReadHook;
}
+export interface AVRInterruptConfig {
+ address: u8;
+ enableRegister: u16;
+ enableMask: u8;
+ flagRegister: u16;
+ flagMask: u8;
+ constant?: boolean;
+}
+
export class CPU implements ICPU {
readonly data: Uint8Array = new Uint8Array(this.sramBytes + registerSpace);
readonly data16 = new Uint16Array(this.data.buffer);
@@ -52,13 +62,15 @@ export class CPU implements ICPU {
readonly progBytes = new Uint8Array(this.progMem.buffer);
readonly readHooks: CPUMemoryReadHooks = [];
readonly writeHooks: CPUMemoryHooks = [];
+ private readonly pendingInterrupts: AVRInterruptConfig[] = [];
readonly pc22Bits = this.progBytes.length > 0x20000;
// This lets the Timer Compare output override GPIO pins:
readonly gpioTimerHooks: CPUMemoryHooks = [];
- pc = 0;
- cycles = 0;
+ pc: u32 = 0;
+ cycles: u32 = 0;
+ nextInterrupt: i16 = -1;
constructor(public progMem: Uint16Array, private sramBytes = 8192) {
this.reset();
@@ -67,6 +79,8 @@ export class CPU implements ICPU {
reset() {
this.data.fill(0);
this.SP = this.data.length - 1;
+ this.pendingInterrupts.splice(0, this.pendingInterrupts.length);
+ this.nextInterrupt = -1;
}
readData(addr: number) {
@@ -101,4 +115,63 @@ export class CPU implements ICPU {
get interruptsEnabled() {
return this.SREG & 0x80 ? true : false;
}
+
+ private updateNextInterrupt() {
+ this.nextInterrupt = this.pendingInterrupts.findIndex((item) => !!item);
+ }
+
+ setInterruptFlag(interrupt: AVRInterruptConfig) {
+ const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
+ if (interrupt.constant) {
+ this.data[flagRegister] &= ~flagMask;
+ } else {
+ this.data[flagRegister] |= flagMask;
+ }
+ if (this.data[enableRegister] & enableMask) {
+ this.queueInterrupt(interrupt);
+ }
+ }
+
+ updateInterruptEnable(interrupt: AVRInterruptConfig, registerValue: u8) {
+ const { enableMask, flagRegister, flagMask } = interrupt;
+ if (registerValue & enableMask) {
+ if (this.data[flagRegister] & flagMask) {
+ this.queueInterrupt(interrupt);
+ }
+ } else {
+ this.clearInterrupt(interrupt, false);
+ }
+ }
+
+ queueInterrupt(interrupt: AVRInterruptConfig) {
+ this.pendingInterrupts[interrupt.address] = interrupt;
+ this.updateNextInterrupt();
+ }
+
+ clearInterrupt({ address, flagRegister, flagMask }: AVRInterruptConfig, clearFlag = true) {
+ delete this.pendingInterrupts[address];
+ if (clearFlag) {
+ this.data[flagRegister] &= ~flagMask;
+ }
+ this.updateNextInterrupt();
+ }
+
+ clearInterruptByFlag(interrupt: AVRInterruptConfig, registerValue: number) {
+ const { flagRegister, flagMask } = interrupt;
+ if (registerValue & flagMask) {
+ this.data[flagRegister] &= ~flagMask;
+ this.clearInterrupt(interrupt);
+ }
+ }
+
+ tick() {
+ if (this.interruptsEnabled && this.nextInterrupt >= 0) {
+ const interrupt = this.pendingInterrupts[this.nextInterrupt];
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ avrInterrupt(this, interrupt.address);
+ if (!interrupt.constant) {
+ this.clearInterrupt(interrupt);
+ }
+ }
+ }
}
diff --git a/src/peripherals/eeprom.spec.ts b/src/peripherals/eeprom.spec.ts
index e7cf62e..d293bd9 100644
--- a/src/peripherals/eeprom.spec.ts
+++ b/src/peripherals/eeprom.spec.ts
@@ -28,6 +28,7 @@ describe('EEPROM', () => {
cpu.writeData(EEARH, 0);
cpu.writeData(EECR, EERE);
eeprom.tick();
+ cpu.tick();
expect(cpu.cycles).toEqual(4);
expect(cpu.data[EEDR]).toEqual(0xff);
});
@@ -41,6 +42,7 @@ describe('EEPROM', () => {
cpu.writeData(EEARH, 0x2);
cpu.writeData(EECR, EERE);
eeprom.tick();
+ cpu.tick();
expect(cpu.data[EEDR]).toEqual(0x42);
});
});
@@ -56,6 +58,7 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEMPE);
cpu.writeData(EECR, EEPE);
eeprom.tick();
+ cpu.tick();
expect(cpu.cycles).toEqual(2);
expect(eepromBackend.memory[15]).toEqual(0x55);
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
@@ -104,12 +107,14 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEPE);
cpu.cycles += 1000;
eeprom.tick();
+ cpu.tick();
// At this point, write shouldn't be complete yet
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
expect(cpu.pc).toEqual(0);
cpu.cycles += 10000000;
// And now, 10 million cycles later, it should.
eeprom.tick();
+ cpu.tick();
expect(eepromBackend.memory[15]).toEqual(0x55);
expect(cpu.data[EECR] & EEPE).toEqual(0);
expect(cpu.pc).toEqual(0x2c); // EEPROM Ready interrupt
@@ -125,8 +130,10 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEMPE);
cpu.cycles = 8; // waiting for more than 4 cycles should clear EEMPE
eeprom.tick();
+ cpu.tick();
cpu.writeData(EECR, EEPE);
eeprom.tick();
+ cpu.tick();
// Ensure that nothing was written, and EEPE bit is clear
expect(cpu.cycles).toEqual(8);
expect(eepromBackend.memory[15]).toEqual(0xff);
@@ -145,6 +152,7 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEMPE);
cpu.writeData(EECR, EEPE);
eeprom.tick();
+ cpu.tick();
expect(cpu.cycles).toEqual(2);
// Write 0x66 to address 16 (first write is still in progress)
@@ -154,6 +162,7 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEMPE);
cpu.writeData(EECR, EEPE);
eeprom.tick();
+ cpu.tick();
// Ensure that second write didn't happen
expect(cpu.cycles).toEqual(2);
@@ -173,11 +182,13 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEMPE);
cpu.writeData(EECR, EEPE);
eeprom.tick();
+ cpu.tick();
expect(cpu.cycles).toEqual(2);
// wait long enough time for the first write to finish
cpu.cycles += 10000000;
eeprom.tick();
+ cpu.tick();
// Write 0x66 to address 16
cpu.writeData(EEDR, 0x66);
@@ -186,6 +197,7 @@ describe('EEPROM', () => {
cpu.writeData(EECR, EEMPE);
cpu.writeData(EECR, EEPE);
eeprom.tick();
+ cpu.tick();
// Ensure both writes took place
expect(cpu.cycles).toEqual(10000004);
diff --git a/src/peripherals/eeprom.ts b/src/peripherals/eeprom.ts
index 0301701..bc386ca 100644
--- a/src/peripherals/eeprom.ts
+++ b/src/peripherals/eeprom.ts
@@ -1,6 +1,5 @@
-import { CPU } from '../cpu/cpu';
-import { avrInterrupt } from '../cpu/interrupt';
-import { u8, u16, u32 } from '../types';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
+import { u16, u32, u8 } from '../types';
export interface EEPROMBackend {
readMemory(addr: u16): u8;
@@ -71,6 +70,16 @@ export class AVREEPROM {
private writeCompleteCycles = 0;
+ // Interrupts
+ private EER: AVRInterruptConfig = {
+ address: this.config.eepromReadyInterrupt,
+ flagRegister: this.config.EECR,
+ flagMask: EEPE,
+ enableRegister: this.config.EECR,
+ enableMask: EERIE,
+ constant: true,
+ };
+
constructor(
private cpu: CPU,
private backend: EEPROMBackend,
@@ -81,6 +90,10 @@ export class AVREEPROM {
const addr = (this.cpu.data[EEARH] << 8) | this.cpu.data[EEARL];
+ if (eecr & EERE) {
+ this.cpu.clearInterrupt(this.EER);
+ }
+
if (eecr & EEMPE) {
this.writeEnabledCycles = this.cpu.cycles + 4;
}
@@ -132,16 +145,13 @@ export class AVREEPROM {
}
tick() {
- const { EECR, eepromReadyInterrupt } = this.config;
+ const { EECR } = this.config;
if (this.writeEnabledCycles && this.cpu.cycles > this.writeEnabledCycles) {
this.cpu.data[EECR] &= ~EEMPE;
}
if (this.writeCompleteCycles && this.cpu.cycles > this.writeCompleteCycles) {
- this.cpu.data[EECR] &= ~EEPE;
- if (this.cpu.interruptsEnabled && this.cpu.data[EECR] & EERIE) {
- avrInterrupt(this.cpu, eepromReadyInterrupt);
- }
+ this.cpu.setInterruptFlag(this.EER);
}
}
}
diff --git a/src/peripherals/spi.spec.ts b/src/peripherals/spi.spec.ts
index 1bb099f..8e11b94 100644
--- a/src/peripherals/spi.spec.ts
+++ b/src/peripherals/spi.spec.ts
@@ -177,6 +177,7 @@ describe('SPI', () => {
cpu.writeData(SPCR, SPE | MSTR);
cpu.writeData(SPDR, 0x50);
spi.tick();
+ cpu.tick();
expect(cpu.readData(SPSR) & WCOL).toEqual(0);
cpu.writeData(SPDR, 0x51);
@@ -194,11 +195,13 @@ describe('SPI', () => {
// At this point, write shouldn't be complete yet
cpu.cycles += 10;
spi.tick();
+ cpu.tick();
expect(cpu.pc).toEqual(0);
// 100 cycles later, it should (8 bits * 8 cycles per bit = 64).
cpu.cycles += 100;
spi.tick();
+ cpu.tick();
expect(cpu.data[SPSR] & SPIF).toEqual(0);
expect(cpu.pc).toEqual(0x22); // SPI Ready interrupt
});
@@ -213,10 +216,12 @@ describe('SPI', () => {
cpu.cycles = 10;
spi.tick();
+ cpu.tick();
expect(cpu.readData(SPDR)).toEqual(0);
cpu.cycles = 32; // 4 cycles per bit * 8 bits = 32
spi.tick();
+ cpu.tick();
expect(cpu.readData(SPDR)).toEqual(0x88);
});
});
diff --git a/src/peripherals/spi.ts b/src/peripherals/spi.ts
index 0c03c3f..f67143f 100644
--- a/src/peripherals/spi.ts
+++ b/src/peripherals/spi.ts
@@ -1,6 +1,5 @@
-import { CPU } from '../cpu/cpu';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
import { u8 } from '../types';
-import { avrInterrupt } from '../cpu/interrupt';
export interface SPIConfig {
spiInterrupt: u8;
@@ -42,6 +41,15 @@ export class AVRSPI {
private transmissionCompleteCycles = 0;
private receivedByte: u8 = 0;
+ // Interrupts
+ private SPI: AVRInterruptConfig = {
+ address: this.config.spiInterrupt,
+ flagRegister: this.config.SPSR,
+ flagMask: SPSR_SPIF,
+ enableRegister: this.config.SPCR,
+ enableMask: SPCR_SPIE,
+ };
+
constructor(private cpu: CPU, private config: SPIConfig, private freqMHz: number) {
const { SPCR, SPSR, SPDR } = config;
cpu.writeHooks[SPDR] = (value: u8) => {
@@ -57,28 +65,26 @@ export class AVRSPI {
}
// Clear write collision / interrupt flags
- cpu.data[SPSR] &= ~SPSR_WCOL & ~SPSR_SPIF;
+ cpu.data[SPSR] &= ~SPSR_WCOL;
+ this.cpu.clearInterrupt(this.SPI);
this.receivedByte = this.onTransfer?.(value) ?? 0;
this.transmissionCompleteCycles = this.cpu.cycles + this.clockDivider * bitsPerByte;
return true;
};
+ cpu.writeHooks[SPSR] = (value: u8) => {
+ this.cpu.data[SPSR] = value;
+ this.cpu.clearInterruptByFlag(this.SPI, value);
+ };
}
tick() {
if (this.transmissionCompleteCycles && this.cpu.cycles >= this.transmissionCompleteCycles) {
- const { SPSR, SPDR } = this.config;
- this.cpu.data[SPSR] |= SPSR_SPIF;
+ const { SPDR } = this.config;
this.cpu.data[SPDR] = this.receivedByte;
+ this.cpu.setInterruptFlag(this.SPI);
this.transmissionCompleteCycles = 0;
}
- if (this.cpu.interruptsEnabled) {
- const { SPSR, SPCR, spiInterrupt } = this.config;
- if (this.cpu.data[SPCR] & SPCR_SPIE && this.cpu.data[SPSR] & SPSR_SPIF) {
- avrInterrupt(this.cpu, spiInterrupt);
- this.cpu.data[SPSR] &= ~SPSR_SPIF;
- }
- }
}
get isMaster() {
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index 2dc7d55..6b6a9eb 100644
--- a/src/peripherals/timer.spec.ts
+++ b/src/peripherals/timer.spec.ts
@@ -69,8 +69,10 @@ describe('timer', () => {
const timer = new AVRTimer(cpu, timer0Config);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(1);
});
@@ -80,8 +82,10 @@ describe('timer', () => {
const timer = new AVRTimer(cpu, timer0Config);
cpu.writeData(TCCR0B, CS01 | CS00); // Set prescaler to 64
timer.tick();
+ cpu.tick();
cpu.cycles = 64;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(1);
});
@@ -92,6 +96,7 @@ describe('timer', () => {
cpu.writeData(TCCR0B, 0); // No prescaler (timer disabled)
cpu.cycles = 100000;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0); // TCNT should stay 0
});
@@ -102,23 +107,42 @@ describe('timer', () => {
cpu.writeData(TCNT0, 0xff);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0);
expect(cpu.data[TIFR0]).toEqual(TOV0);
});
+ it('should clear the TOV flag when writing 1 to the TOV bit, and not trigger the interrupt', () => {
+ const cpu = new CPU(new Uint16Array(0x1000));
+ const timer = new AVRTimer(cpu, timer0Config);
+ cpu.writeData(TCNT0, 0xff);
+ cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
+ timer.tick();
+ cpu.tick();
+ cpu.cycles = 1;
+ timer.tick();
+ cpu.tick();
+ expect(cpu.data[TIFR0]).toEqual(TOV0);
+ cpu.writeData(TIFR0, TOV0);
+ expect(cpu.data[TIFR0]).toEqual(0);
+ });
+
it('should set TOV if timer overflows in FAST PWM mode', () => {
const cpu = new CPU(new Uint16Array(0x1000));
const timer = new AVRTimer(cpu, timer0Config);
cpu.writeData(TCNT0, 0xff);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.writeData(OCR0A, 0x7f);
cpu.writeData(TCCR0A, WGM01 | WGM00); // WGM: Fast PWM
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0);
expect(cpu.data[TIFR0]).toEqual(TOV0);
@@ -130,10 +154,12 @@ describe('timer', () => {
cpu.writeData(TCNT0, 0xff);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.data[TIMSK0] = TOIE0;
cpu.data[SREG] = 0x80; // SREG: I-------
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(2); // TCNT should be 2 (one tick above + 2 cycles for interrupt)
expect(cpu.data[TIFR0] & TOV0).toEqual(0);
@@ -157,10 +183,12 @@ describe('timer', () => {
cpu.writeData(TCNT0, 0xff);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.data[TIMSK0] = 2;
cpu.data[SREG] = 0x80; // SREG: I-------
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(2); // TCNT should be 2 (one tick above + 2 cycles for interrupt)
expect(cpu.data[TIFR0] & 2).toEqual(0);
@@ -174,10 +202,12 @@ describe('timer', () => {
cpu.writeData(TCNT0, 0xff);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.data[TIMSK0] = TOIE0;
cpu.data[SREG] = 0x0; // SREG: --------
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
expect(cpu.data[TIFR0]).toEqual(TOV0);
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
@@ -189,10 +219,12 @@ describe('timer', () => {
cpu.writeData(TCNT0, 0xff);
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.data[TIMSK0] = 0;
cpu.data[SREG] = 0x80; // SREG: I-------
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
expect(cpu.data[TIFR0]).toEqual(TOV0);
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
@@ -206,8 +238,10 @@ describe('timer', () => {
cpu.writeData(TCCR0A, 0x0); // WGM: Normal
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
expect(cpu.data[TIFR0]).toEqual(OCF0A);
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
@@ -221,8 +255,10 @@ describe('timer', () => {
cpu.writeData(TCCR0A, WGM01); // WGM: CTC
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0);
expect(cpu.pc).toEqual(0);
@@ -237,8 +273,10 @@ describe('timer', () => {
cpu.writeData(TCCR0A, 0x0); // WGM: (Normal)
cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
expect(cpu.data[TIFR0]).toEqual(OCF0B);
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
@@ -253,8 +291,10 @@ describe('timer', () => {
cpu.writeData(TIMSK0, OCIE0A);
cpu.writeData(95, 0x80); // SREG: I-------
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0x23); // TCNT should be 0x23 (one tick above + 2 cycles for interrupt)
expect(cpu.data[TIFR0] & OCF0A).toEqual(0);
@@ -271,8 +311,10 @@ describe('timer', () => {
cpu.writeData(TIMSK0, 0);
cpu.writeData(95, 0x80); // SREG: I-------
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0x21);
expect(cpu.pc).toEqual(0);
@@ -288,8 +330,10 @@ describe('timer', () => {
cpu.writeData(TIMSK0, OCIE0B);
cpu.writeData(95, 0x80); // SREG: I-------
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(0x23); // TCNT should be 0x23 (0x23 + 2 cycles for interrupt)
expect(cpu.data[TIFR0] & OCF0B).toEqual(0);
@@ -320,13 +364,16 @@ describe('timer', () => {
const timer = new AVRTimer(cpu, timer2Config);
cpu.writeData(TCCR2B, CS22 | CS21); // Set prescaler to 256
timer.tick();
+ cpu.tick();
cpu.cycles = 511;
timer.tick();
+ cpu.tick();
expect(cpu.readData(TCNT2)).toEqual(1);
cpu.cycles = 512;
timer.tick();
+ cpu.tick();
expect(cpu.readData(TCNT2)).toEqual(2);
});
@@ -457,8 +504,10 @@ describe('timer', () => {
cpu.writeData(TCCR1A, 0x0); // WGM: Normal
cpu.writeData(TCCR1B, CS10); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
cpu.readData(TCNT1);
expect(cpu.dataView.getUint16(TCNT1, true)).toEqual(0x2234); // TCNT1 should increment
});
@@ -473,8 +522,10 @@ describe('timer', () => {
cpu.writeData(TCCR1A, 0x0); // WGM: Normal
cpu.writeData(TCCR1B, CS10); // Set prescaler to 1
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
expect(cpu.data[TIFR1]).toEqual(OCF1A); // TIFR1 should have OCF1A bit on
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1);
@@ -490,8 +541,10 @@ describe('timer', () => {
cpu.data[0x6f] = 0x1; // TIMSK1: TOIE1
cpu.data[SREG] = 0x80; // SREG: I-------
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
cpu.readData(TCNT1); // Refresh TCNT1
expect(cpu.dataView.getUint16(TCNT1, true)).toEqual(2);
expect(cpu.data[TIFR1] & TOV1).toEqual(0);
@@ -508,8 +561,10 @@ describe('timer', () => {
cpu.writeData(ICR1, 0x10); // ...
cpu.writeData(TCCR1B, WGM13 | WGM12 | CS10); // Set prescaler to 1, WGM: CTC
timer.tick();
+ cpu.tick();
cpu.cycles = 2; // 2 cycles should increment timer twice, beyond ICR1
timer.tick();
+ cpu.tick();
cpu.readData(TCNT1); // Refresh TCNT1
expect(cpu.dataView.getUint16(TCNT1, true)).toEqual(0); // TCNT should be 0
expect(cpu.data[TIFR1] & TOV1).toEqual(0);
@@ -522,6 +577,7 @@ describe('timer', () => {
cpu.writeData(TCNT1, 0x22);
cpu.writeData(TCNT1H, 0x55);
timer.tick();
+ cpu.tick();
const timerLow = cpu.readData(TCNT1);
const timerHigh = cpu.readData(TCNT1H);
expect((timerHigh << 8) | timerLow).toEqual(0x22);
@@ -534,8 +590,10 @@ describe('timer', () => {
cpu.writeData(TCNT1, 0xff);
cpu.writeData(TCCR1B, WGM12 | CS10); // Set prescaler to 1, WGM: CTC
timer.tick();
+ cpu.tick();
cpu.cycles = 1;
timer.tick();
+ cpu.tick();
// We read the high byte before the low byte, so the high byte should still have
// the previous value:
const timerHigh = cpu.readData(TCNT1H);
diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts
index e347212..81c1e47 100644
--- a/src/peripherals/timer.ts
+++ b/src/peripherals/timer.ts
@@ -6,9 +6,8 @@
* Copyright (C) 2019, 2020, Uri Shaked
*/
-import { CPU } from '../cpu/cpu';
-import { avrInterrupt } from '../cpu/interrupt';
-import { portBConfig, portDConfig, PinOverrideMode } from './gpio';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
+import { PinOverrideMode, portBConfig, portDConfig } from './gpio';
const timer01Dividers = {
0: 0,
@@ -251,11 +250,33 @@ export class AVRTimer {
private tcntUpdated = false;
private countingUp = true;
private divider = 0;
- private pendingInterrupt = false;
// This is the temporary register used to access 16-bit registers (section 16.3 of the datasheet)
private highByteTemp: u8 = 0;
+ // Interrupts
+ private OVF: AVRInterruptConfig = {
+ address: this.config.ovfInterrupt,
+ flagRegister: this.config.TIFR,
+ flagMask: this.config.TOV,
+ enableRegister: this.config.TIMSK,
+ enableMask: this.config.TOIE,
+ };
+ private OCFA: AVRInterruptConfig = {
+ address: this.config.compAInterrupt,
+ flagRegister: this.config.TIFR,
+ flagMask: this.config.OCFA,
+ enableRegister: this.config.TIMSK,
+ enableMask: this.config.OCIEA,
+ };
+ private OCFB: AVRInterruptConfig = {
+ address: this.config.compBInterrupt,
+ flagRegister: this.config.TIFR,
+ flagMask: this.config.OCFB,
+ enableRegister: this.config.TIMSK,
+ enableMask: this.config.OCIEB,
+ };
+
constructor(private cpu: CPU, private config: AVRTimerConfig) {
this.updateWGMConfig();
this.cpu.readHooks[config.TCNT] = (addr: u8) => {
@@ -308,6 +329,18 @@ export class AVRTimer {
this.updateWGMConfig();
return true;
};
+ cpu.writeHooks[config.TIFR] = (value) => {
+ this.cpu.data[config.TIFR] = value;
+ this.cpu.clearInterruptByFlag(this.OVF, value);
+ this.cpu.clearInterruptByFlag(this.OCFA, value);
+ this.cpu.clearInterruptByFlag(this.OCFB, value);
+ return true;
+ };
+ cpu.writeHooks[config.TIMSK] = (value) => {
+ this.cpu.updateInterruptEnable(this.OVF, value);
+ this.cpu.updateInterruptEnable(this.OCFA, value);
+ this.cpu.updateInterruptEnable(this.OCFB, value);
+ };
}
reset() {
@@ -317,15 +350,6 @@ export class AVRTimer {
this.ocrB = 0;
}
- get TIFR() {
- return this.cpu.data[this.config.TIFR];
- }
-
- set TIFR(value: u8) {
- this.pendingInterrupt = value > 0;
- this.cpu.data[this.config.TIFR] = value;
- }
-
get TCCRA() {
return this.cpu.data[this.config.TCCRA];
}
@@ -384,27 +408,10 @@ export class AVRTimer {
this.timerUpdated();
}
if ((timerMode === TimerMode.Normal || timerMode === TimerMode.FastPWM) && val > newVal) {
- this.TIFR |= this.config.TOV;
+ this.cpu.setInterruptFlag(this.OVF);
}
}
this.tcntUpdated = false;
- if (this.cpu.interruptsEnabled && this.pendingInterrupt) {
- const { TIFR, TIMSK } = this;
- const { TOV, OCFA, OCFB, TOIE, OCIEA, OCIEB } = this.config;
- if (TIFR & TOV && TIMSK & TOIE) {
- avrInterrupt(this.cpu, this.config.ovfInterrupt);
- this.TIFR &= ~TOV;
- }
- if (TIFR & OCFA && TIMSK & OCIEA) {
- avrInterrupt(this.cpu, this.config.compAInterrupt);
- this.TIFR &= ~OCFA;
- }
- if (TIFR & OCFB && TIMSK & OCIEB) {
- avrInterrupt(this.cpu, this.config.compBInterrupt);
- this.TIFR &= ~OCFB;
- }
- this.pendingInterrupt = false;
- }
}
private phasePwmCount(value: u16, delta: u8) {
@@ -418,7 +425,7 @@ export class AVRTimer {
value--;
if (!value && !this.tcntUpdated) {
this.countingUp = true;
- this.TIFR |= this.config.TOV;
+ this.cpu.setInterruptFlag(this.OVF);
}
}
delta--;
@@ -430,19 +437,18 @@ export class AVRTimer {
const value = this.tcnt;
if (this.ocrA && value === this.ocrA) {
- const { TOV, OCFA } = this.config;
- this.TIFR |= OCFA;
+ this.cpu.setInterruptFlag(this.OCFA);
if (this.timerMode === TimerMode.CTC) {
// Clear Timer on Compare Match (CTC) Mode
this.tcnt = 0;
- this.TIFR |= TOV;
+ this.cpu.setInterruptFlag(this.OVF);
}
if (this.compA) {
this.updateCompPin(this.compA, 'A');
}
}
if (this.ocrB && value === this.ocrB) {
- this.TIFR |= this.config.OCFB;
+ this.cpu.setInterruptFlag(this.OCFB);
if (this.compB) {
this.updateCompPin(this.compB, 'B');
}
diff --git a/src/peripherals/twi.spec.ts b/src/peripherals/twi.spec.ts
index 1a2b2ae..e43ae38 100644
--- a/src/peripherals/twi.spec.ts
+++ b/src/peripherals/twi.spec.ts
@@ -48,9 +48,11 @@ describe('TWI', () => {
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(TWCR, TWINT | TWIE);
+ cpu.writeData(TWCR, TWIE);
cpu.data[SREG] = 0x80; // SREG: I-------
+ twi.completeStart(); // This will set the TWINT flag
twi.tick();
+ cpu.tick();
expect(cpu.pc).toEqual(0x30); // 2-wire Serial Interface Vector
expect(cpu.cycles).toEqual(2);
expect(cpu.data[TWCR] & TWINT).toEqual(0);
@@ -63,6 +65,7 @@ describe('TWI', () => {
jest.spyOn(twi.eventHandler, 'start');
cpu.writeData(TWCR, TWINT | TWSTA | TWEN);
twi.tick();
+ cpu.tick();
expect(twi.eventHandler.start).toHaveBeenCalledWith(false);
});
diff --git a/src/peripherals/twi.ts b/src/peripherals/twi.ts
index 52258a6..914b67f 100644
--- a/src/peripherals/twi.ts
+++ b/src/peripherals/twi.ts
@@ -1,5 +1,4 @@
-import { CPU } from '../cpu/cpu';
-import { avrInterrupt } from '../cpu/interrupt';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
import { u8 } from '../types';
export interface TWIEventHandler {
@@ -98,13 +97,22 @@ export class AVRTWI {
private nextTick: (() => void) | null = null;
+ // Interrupts
+ private TWI: AVRInterruptConfig = {
+ address: this.config.twiInterrupt,
+ flagRegister: this.config.TWCR,
+ flagMask: TWCR_TWINT,
+ enableRegister: this.config.TWCR,
+ enableMask: TWCR_TWIE,
+ };
+
constructor(private cpu: CPU, private config: TWIConfig, private freqMHz: number) {
this.updateStatus(STATUS_TWI_IDLE);
this.cpu.writeHooks[config.TWCR] = (value) => {
+ this.cpu.data[config.TWCR] = value;
const clearInt = value & TWCR_TWINT;
- if (clearInt) {
- value &= ~TWCR_TWINT;
- }
+ this.cpu.clearInterruptByFlag(this.TWI, value);
+ this.cpu.updateInterruptEnable(this.TWI, value);
const { status } = this;
if (clearInt && value & TWCR_TWEN) {
const twdrValue = this.cpu.data[this.config.TWDR];
@@ -122,7 +130,6 @@ export class AVRTWI {
this.eventHandler.readByte(ack);
}
};
- this.cpu.data[config.TWCR] = value;
return true;
}
};
@@ -133,13 +140,6 @@ export class AVRTWI {
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() {
@@ -193,8 +193,8 @@ export class AVRTWI {
}
private updateStatus(value: u8) {
- const { TWCR, TWSR } = this.config;
+ const { TWSR } = this.config;
this.cpu.data[TWSR] = (this.cpu.data[TWSR] & ~TWSR_TWS_MASK) | value;
- this.cpu.data[TWCR] |= TWCR_TWINT;
+ this.cpu.setInterruptFlag(this.TWI);
}
}
diff --git a/src/peripherals/usart.spec.ts b/src/peripherals/usart.spec.ts
index 2728f9c..c1e1570 100644
--- a/src/peripherals/usart.spec.ts
+++ b/src/peripherals/usart.spec.ts
@@ -150,6 +150,7 @@ describe('USART', () => {
cpu.writeData(UCSR0B, UDRIE | TXEN);
cpu.data[SREG] = 0x80; // SREG: I-------
usart.tick();
+ cpu.tick();
expect(cpu.pc).toEqual(PC_INT_UDRE);
expect(cpu.cycles).toEqual(2);
expect(cpu.data[UCSR0A] & UDRE).toEqual(0);
@@ -163,6 +164,7 @@ describe('USART', () => {
cpu.data[SREG] = 0x80; // SREG: I-------
cpu.cycles = 1e6;
usart.tick();
+ cpu.tick();
expect(cpu.pc).toEqual(PC_INT_TXC);
expect(cpu.cycles).toEqual(1e6 + 2);
expect(cpu.data[UCSR0A] & TXC).toEqual(0);
@@ -174,6 +176,7 @@ describe('USART', () => {
cpu.writeData(UCSR0B, TXCIE | TXEN);
cpu.data[SREG] = 0x80; // SREG: I-------
usart.tick();
+ cpu.tick();
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(0);
});
@@ -186,6 +189,7 @@ describe('USART', () => {
cpu.data[SREG] = 0; // SREG: 0 (disable interrupts)
cpu.cycles = 1e6;
usart.tick();
+ cpu.tick();
expect(cpu.pc).toEqual(0);
expect(cpu.cycles).toEqual(1e6);
expect(cpu.data[UCSR0A]).toEqual(TXC | UDRE);
@@ -244,9 +248,11 @@ describe('USART', () => {
cpu.writeData(UDR0, 0x48); // 'H'
cpu.cycles += 16000; // 1ms
usart.tick();
+ cpu.tick();
expect(cpu.data[UCSR0A] & TXC).toEqual(0);
cpu.cycles += 800; // 0.05ms
usart.tick();
+ cpu.tick();
expect(cpu.data[UCSR0A] & TXC).toEqual(TXC);
});
});
diff --git a/src/peripherals/usart.ts b/src/peripherals/usart.ts
index a2037a9..c1441ad 100644
--- a/src/peripherals/usart.ts
+++ b/src/peripherals/usart.ts
@@ -6,8 +6,7 @@
* Copyright (C) 2019, 2020, Uri Shaked
*/
-import { CPU } from '../cpu/cpu';
-import { avrInterrupt } from '../cpu/interrupt';
+import { AVRInterruptConfig, CPU } from '../cpu/cpu';
import { u8 } from '../types';
export interface USARTConfig {
@@ -72,14 +71,38 @@ export class AVRUSART {
private lineBuffer = '';
+ // Interrupts
+ private UDRE: AVRInterruptConfig = {
+ address: this.config.dataRegisterEmptyInterrupt,
+ flagRegister: this.config.UCSRA,
+ flagMask: UCSRA_UDRE,
+ enableRegister: this.config.UCSRB,
+ enableMask: UCSRB_UDRIE,
+ };
+ private TXC: AVRInterruptConfig = {
+ address: this.config.txCompleteInterrupt,
+ flagRegister: this.config.UCSRA,
+ flagMask: UCSRA_TXC,
+ enableRegister: this.config.UCSRB,
+ enableMask: UCSRB_TXCIE,
+ };
+
private txCompleteCycles = 0;
constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) {
this.reset();
+ this.cpu.writeHooks[config.UCSRA] = (value) => {
+ cpu.data[config.UCSRA] = value;
+ cpu.clearInterruptByFlag(this.UDRE, value);
+ cpu.clearInterruptByFlag(this.TXC, value);
+ return true;
+ };
this.cpu.writeHooks[config.UCSRB] = (value, oldValue) => {
+ cpu.updateInterruptEnable(this.UDRE, value);
+ cpu.updateInterruptEnable(this.TXC, value);
if (value & UCSRB_TXEN && !(oldValue & UCSRB_TXEN)) {
// Enabling the transmission - mark UDR as empty
- this.cpu.data[config.UCSRA] |= UCSRA_UDRE;
+ cpu.setInterruptFlag(this.UDRE);
}
};
this.cpu.writeHooks[config.UDR] = (value) => {
@@ -97,7 +120,8 @@ export class AVRUSART {
}
const symbolsPerChar = 1 + this.bitsPerChar + this.stopBits + (this.parityEnabled ? 1 : 0);
this.txCompleteCycles = this.cpu.cycles + (this.UBRR * this.multiplier + 1) * symbolsPerChar;
- this.cpu.data[config.UCSRA] &= ~(UCSRA_TXC | UCSRA_UDRE);
+ this.cpu.clearInterrupt(this.TXC);
+ this.cpu.clearInterrupt(this.UDRE);
};
}
@@ -110,21 +134,10 @@ export class AVRUSART {
tick() {
const { txCompleteCycles, cpu } = this;
if (txCompleteCycles && cpu.cycles >= txCompleteCycles) {
- this.cpu.data[this.config.UCSRA] |= UCSRA_UDRE | UCSRA_TXC;
+ cpu.setInterruptFlag(this.UDRE);
+ cpu.setInterruptFlag(this.TXC);
this.txCompleteCycles = 0;
}
- if (cpu.interruptsEnabled) {
- const ucsra = cpu.data[this.config.UCSRA];
- const ucsrb = cpu.data[this.config.UCSRB];
- if (ucsra & UCSRA_UDRE && ucsrb & UCSRB_UDRIE) {
- avrInterrupt(cpu, this.config.dataRegisterEmptyInterrupt);
- cpu.data[this.config.UCSRA] &= ~UCSRA_UDRE;
- }
- if (ucsra & UCSRA_TXC && ucsrb & UCSRB_TXCIE) {
- avrInterrupt(cpu, this.config.txCompleteInterrupt);
- cpu.data[this.config.UCSRA] &= ~UCSRA_TXC;
- }
- }
}
private get UBRR() {
diff --git a/src/types.ts b/src/types.ts
index f87f298..850cd6b 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -1,3 +1,4 @@
export type u8 = number;
export type u16 = number;
+export type i16 = number;
export type u32 = number;
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.