From 36c4134a26063248a2ef47f5ac8defe50d9476b1 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Wed, 9 Dec 2020 00:51:13 +0200 Subject: refactor: central interrupt handling #38 --- src/peripherals/eeprom.spec.ts | 12 +++++++ src/peripherals/eeprom.ts | 26 +++++++++----- src/peripherals/spi.spec.ts | 5 +++ src/peripherals/spi.ts | 30 +++++++++------- src/peripherals/timer.spec.ts | 58 +++++++++++++++++++++++++++++++ src/peripherals/timer.ts | 78 +++++++++++++++++++++++------------------- src/peripherals/twi.spec.ts | 5 ++- src/peripherals/twi.ts | 30 ++++++++-------- src/peripherals/usart.spec.ts | 6 ++++ src/peripherals/usart.ts | 47 ++++++++++++++++--------- 10 files changed, 208 insertions(+), 89 deletions(-) (limited to 'src/peripherals') 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() { -- cgit v1.2.3 From 9c1288f18889ae3bd10869a9f6ebc53defa3024b Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Wed, 9 Dec 2020 15:46:53 +0200 Subject: perf!: centeral timekeeping This should improve performance, especially when running simulations with multiple peripherals. For instance, the demo project now runs at ~322%, up from ~185% in AVR8js 0.13.1. BREAKING CHANGE: `tick()` methods were removed from individual peripherals. You now need to call `cpu.tick()` instead. --- src/peripherals/eeprom.spec.ts | 34 +++---- src/peripherals/eeprom.ts | 22 ++--- src/peripherals/spi.spec.ts | 11 +-- src/peripherals/spi.ts | 22 +++-- src/peripherals/timer.spec.ts | 212 +++++++++++++++++++---------------------- src/peripherals/timer.ts | 30 +++++- src/peripherals/twi.spec.ts | 7 +- src/peripherals/twi.ts | 13 +-- src/peripherals/usart.spec.ts | 16 +--- src/peripherals/usart.ts | 17 +--- 10 files changed, 173 insertions(+), 211 deletions(-) (limited to 'src/peripherals') diff --git a/src/peripherals/eeprom.spec.ts b/src/peripherals/eeprom.spec.ts index d293bd9..8f08e47 100644 --- a/src/peripherals/eeprom.spec.ts +++ b/src/peripherals/eeprom.spec.ts @@ -23,11 +23,10 @@ describe('EEPROM', () => { describe('Reading the EEPROM', () => { it('should return 0xff when reading from an empty location', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const eeprom = new AVREEPROM(cpu, new EEPROMMemoryBackend(1024)); + new AVREEPROM(cpu, new EEPROMMemoryBackend(1024)); cpu.writeData(EEARL, 0); cpu.writeData(EEARH, 0); cpu.writeData(EECR, EERE); - eeprom.tick(); cpu.tick(); expect(cpu.cycles).toEqual(4); expect(cpu.data[EEDR]).toEqual(0xff); @@ -36,12 +35,11 @@ describe('EEPROM', () => { it('should return the value stored at the given EEPROM address', () => { const cpu = new CPU(new Uint16Array(0x1000)); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); eepromBackend.memory[0x250] = 0x42; cpu.writeData(EEARL, 0x50); cpu.writeData(EEARH, 0x2); cpu.writeData(EECR, EERE); - eeprom.tick(); cpu.tick(); expect(cpu.data[EEDR]).toEqual(0x42); }); @@ -51,13 +49,12 @@ describe('EEPROM', () => { it('should write a byte to the given EEPROM address', () => { const cpu = new CPU(new Uint16Array(0x1000)); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); cpu.writeData(EEDR, 0x55); cpu.writeData(EEARL, 15); cpu.writeData(EEARH, 0); cpu.writeData(EECR, EEMPE); cpu.writeData(EECR, EEPE); - eeprom.tick(); cpu.tick(); expect(cpu.cycles).toEqual(2); expect(eepromBackend.memory[15]).toEqual(0x55); @@ -84,10 +81,10 @@ describe('EEPROM', () => { const cpu = new CPU(program); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); eepromBackend.memory[9] = 0x0f; // high four bits are cleared - const runner = new TestProgramRunner(cpu, eeprom); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount); // EEPROM was 0x0f, and our program wrote 0x55. @@ -98,7 +95,7 @@ describe('EEPROM', () => { it('should clear the EEPE bit and fire an interrupt when write has been completed', () => { const cpu = new CPU(new Uint16Array(0x1000)); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); cpu.writeData(EEDR, 0x55); cpu.writeData(EEARL, 15); cpu.writeData(EEARH, 0); @@ -106,14 +103,12 @@ describe('EEPROM', () => { cpu.data[SREG] = 0x80; // SREG: I------- 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); @@ -123,16 +118,14 @@ describe('EEPROM', () => { it('should skip the write if EEMPE is clear', () => { const cpu = new CPU(new Uint16Array(0x1000)); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); cpu.writeData(EEDR, 0x55); cpu.writeData(EEARL, 15); cpu.writeData(EEARH, 0); 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); @@ -143,7 +136,7 @@ describe('EEPROM', () => { it('should skip the write if another write is already in progress', () => { const cpu = new CPU(new Uint16Array(0x1000)); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); // Write 0x55 to address 15 cpu.writeData(EEDR, 0x55); @@ -151,7 +144,6 @@ describe('EEPROM', () => { cpu.writeData(EEARH, 0); cpu.writeData(EECR, EEMPE); cpu.writeData(EECR, EEPE); - eeprom.tick(); cpu.tick(); expect(cpu.cycles).toEqual(2); @@ -161,7 +153,6 @@ describe('EEPROM', () => { cpu.writeData(EEARH, 0); cpu.writeData(EECR, EEMPE); cpu.writeData(EECR, EEPE); - eeprom.tick(); cpu.tick(); // Ensure that second write didn't happen @@ -173,7 +164,7 @@ describe('EEPROM', () => { it('should write two bytes sucessfully', () => { const cpu = new CPU(new Uint16Array(0x1000)); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); // Write 0x55 to address 15 cpu.writeData(EEDR, 0x55); @@ -181,13 +172,11 @@ describe('EEPROM', () => { cpu.writeData(EEARH, 0); 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 @@ -196,7 +185,6 @@ describe('EEPROM', () => { cpu.writeData(EEARH, 0); cpu.writeData(EECR, EEMPE); cpu.writeData(EECR, EEPE); - eeprom.tick(); cpu.tick(); // Ensure both writes took place @@ -226,10 +214,10 @@ describe('EEPROM', () => { const cpu = new CPU(program); const eepromBackend = new EEPROMMemoryBackend(1024); - const eeprom = new AVREEPROM(cpu, eepromBackend); + new AVREEPROM(cpu, eepromBackend); eepromBackend.memory[9] = 0x22; - const runner = new TestProgramRunner(cpu, eeprom); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount); expect(eepromBackend.memory[9]).toEqual(0xff); diff --git a/src/peripherals/eeprom.ts b/src/peripherals/eeprom.ts index bc386ca..d492aa7 100644 --- a/src/peripherals/eeprom.ts +++ b/src/peripherals/eeprom.ts @@ -95,7 +95,11 @@ export class AVREEPROM { } if (eecr & EEMPE) { - this.writeEnabledCycles = this.cpu.cycles + 4; + const eempeCycles = 4; + this.writeEnabledCycles = this.cpu.cycles + eempeCycles; + this.cpu.addClockEvent(() => { + this.cpu.data[EECR] &= ~EEMPE; + }, eempeCycles); } // Read @@ -134,6 +138,11 @@ export class AVREEPROM { } this.cpu.data[EECR] |= EEPE; + + this.cpu.addClockEvent(() => { + this.cpu.setInterruptFlag(this.EER); + }, this.writeCompleteCycles - this.cpu.cycles); + // When EEPE has been set, the CPU is halted for two cycles before the // next instruction is executed. this.cpu.cycles += 2; @@ -143,15 +152,4 @@ export class AVREEPROM { return false; }; } - - tick() { - 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.setInterruptFlag(this.EER); - } - } } diff --git a/src/peripherals/spi.spec.ts b/src/peripherals/spi.spec.ts index 8e11b94..635e657 100644 --- a/src/peripherals/spi.spec.ts +++ b/src/peripherals/spi.spec.ts @@ -160,7 +160,7 @@ describe('SPI', () => { return 0x5b; // we copy this byte to }; - const runner = new TestProgramRunner(cpu, spi); + const runner = new TestProgramRunner(cpu); runner.runToBreak(); // 16 cycles per clock * 8 bits = 128 @@ -172,11 +172,10 @@ describe('SPI', () => { it('should set the WCOL bit in SPSR if writing to SPDR while SPI is already transmitting', () => { const cpu = new CPU(new Uint16Array(1024)); - const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + new AVRSPI(cpu, spiConfig, FREQ_16MHZ); cpu.writeData(SPCR, SPE | MSTR); cpu.writeData(SPDR, 0x50); - spi.tick(); cpu.tick(); expect(cpu.readData(SPSR) & WCOL).toEqual(0); @@ -186,7 +185,7 @@ describe('SPI', () => { it('should clear the SPIF bit and fire an interrupt when SPI transfer completes', () => { const cpu = new CPU(new Uint16Array(1024)); - const spi = new AVRSPI(cpu, spiConfig, FREQ_16MHZ); + new AVRSPI(cpu, spiConfig, FREQ_16MHZ); cpu.writeData(SPCR, SPE | SPIE | MSTR); cpu.writeData(SPDR, 0x50); @@ -194,13 +193,11 @@ 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 @@ -215,12 +212,10 @@ describe('SPI', () => { cpu.writeData(SPDR, 0x8f); 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 f67143f..7dd5d43 100644 --- a/src/peripherals/spi.ts +++ b/src/peripherals/spi.ts @@ -38,7 +38,7 @@ const bitsPerByte = 8; export class AVRSPI { public onTransfer: SPITransferCallback | null = null; - private transmissionCompleteCycles = 0; + private transmissionActive = false; private receivedByte: u8 = 0; // Interrupts @@ -59,7 +59,7 @@ export class AVRSPI { } // Write collision - if (this.transmissionCompleteCycles > this.cpu.cycles) { + if (this.transmissionActive) { cpu.data[SPSR] |= SPSR_WCOL; return true; } @@ -69,7 +69,13 @@ export class AVRSPI { this.cpu.clearInterrupt(this.SPI); this.receivedByte = this.onTransfer?.(value) ?? 0; - this.transmissionCompleteCycles = this.cpu.cycles + this.clockDivider * bitsPerByte; + const cyclesToComplete = this.clockDivider * bitsPerByte; + this.transmissionActive = true; + this.cpu.addClockEvent(() => { + this.cpu.data[SPDR] = this.receivedByte; + this.cpu.setInterruptFlag(this.SPI); + this.transmissionActive = false; + }, cyclesToComplete); return true; }; cpu.writeHooks[SPSR] = (value: u8) => { @@ -78,13 +84,9 @@ export class AVRSPI { }; } - tick() { - if (this.transmissionCompleteCycles && this.cpu.cycles >= this.transmissionCompleteCycles) { - const { SPDR } = this.config; - this.cpu.data[SPDR] = this.receivedByte; - this.cpu.setInterruptFlag(this.SPI); - this.transmissionCompleteCycles = 0; - } + reset() { + this.transmissionActive = false; + this.receivedByte = 0; } get isMaster() { diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts index 6b6a9eb..40db1f6 100644 --- a/src/peripherals/timer.spec.ts +++ b/src/peripherals/timer.spec.ts @@ -25,6 +25,7 @@ const TCNT0 = 0x46; const OCR0A = 0x47; const OCR0B = 0x48; const TIMSK0 = 0x6e; +const TIMSK1 = 0x6f; // Timer 1 Registers const TIFR1 = 0x36; @@ -66,12 +67,11 @@ const nopOpCode = '0000'; describe('timer', () => { it('should update timer every tick when prescaler is 1', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 - timer.tick(); - cpu.tick(); cpu.cycles = 1; - timer.tick(); + cpu.tick(); + cpu.cycles = 2; cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(1); @@ -79,12 +79,11 @@ describe('timer', () => { it('should update timer every 64 ticks when prescaler is 3', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCCR0B, CS01 | CS00); // Set prescaler to 64 - timer.tick(); + cpu.cycles = 1; cpu.tick(); cpu.cycles = 64; - timer.tick(); cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(1); @@ -92,10 +91,11 @@ describe('timer', () => { it('should not update timer if it has been disabled', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCCR0B, 0); // No prescaler (timer disabled) + cpu.cycles = 1; + cpu.tick(); cpu.cycles = 100000; - timer.tick(); cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(0); // TCNT should stay 0 @@ -103,13 +103,12 @@ describe('timer', () => { it('should set TOV if timer overflows', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + 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(); + cpu.cycles = 2; cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(0); @@ -118,13 +117,12 @@ describe('timer', () => { 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); + 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(); + cpu.cycles = 2; cpu.tick(); expect(cpu.data[TIFR0]).toEqual(TOV0); cpu.writeData(TIFR0, TOV0); @@ -133,15 +131,14 @@ describe('timer', () => { it('should set TOV if timer overflows in FAST PWM mode', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 - timer.tick(); + cpu.cycles = 1; cpu.tick(); cpu.writeData(OCR0A, 0x7f); cpu.writeData(TCCR0A, WGM01 | WGM00); // WGM: Fast PWM - cpu.cycles = 1; - timer.tick(); + cpu.cycles = 2; cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(0); @@ -150,26 +147,25 @@ describe('timer', () => { it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 - timer.tick(); + cpu.cycles = 1; cpu.tick(); cpu.data[TIMSK0] = TOIE0; cpu.data[SREG] = 0x80; // SREG: I------- - cpu.cycles = 1; - timer.tick(); + cpu.cycles = 2; 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); expect(cpu.pc).toEqual(0x20); - expect(cpu.cycles).toEqual(3); + expect(cpu.cycles).toEqual(4); }); it('should support overriding TIFR/TOV and TIMSK/TOIE bits (issue #64)', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, { + new AVRTimer(cpu, { ...timer0Config, // The following values correspond ATtiny85 config: @@ -182,163 +178,154 @@ describe('timer', () => { }); cpu.writeData(TCNT0, 0xff); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 - timer.tick(); + cpu.cycles = 1; cpu.tick(); cpu.data[TIMSK0] = 2; cpu.data[SREG] = 0x80; // SREG: I------- - cpu.cycles = 1; - timer.tick(); + cpu.cycles = 2; 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); expect(cpu.pc).toEqual(0x20); - expect(cpu.cycles).toEqual(3); + expect(cpu.cycles).toEqual(4); }); it('should not generate an overflow interrupt when global interrupts disabled', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 - timer.tick(); + cpu.cycles = 1; cpu.tick(); cpu.data[TIMSK0] = TOIE0; cpu.data[SREG] = 0x0; // SREG: -------- - cpu.cycles = 1; - timer.tick(); + cpu.cycles = 2; cpu.tick(); expect(cpu.data[TIFR0]).toEqual(TOV0); expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should not generate an overflow interrupt when TOIE0 is clear', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0xff); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 - timer.tick(); + cpu.cycles = 1; cpu.tick(); cpu.data[TIMSK0] = 0; cpu.data[SREG] = 0x80; // SREG: I------- - cpu.cycles = 1; - timer.tick(); + cpu.cycles = 2; cpu.tick(); expect(cpu.data[TIFR0]).toEqual(TOV0); expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should set OCF0A flag when timer equals OCRA', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x10); cpu.writeData(OCR0A, 0x11); 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(); + cpu.cycles = 2; cpu.tick(); expect(cpu.data[TIFR0]).toEqual(OCF0A); expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should clear the timer in CTC mode if it equals to OCRA', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x10); cpu.writeData(OCR0A, 0x11); 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(); + cpu.cycles = 2; cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(0); expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should set OCF0B flag when timer equals OCRB', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x10); cpu.writeData(OCR0B, 0x11); 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(); + cpu.cycles = 2; cpu.tick(); expect(cpu.data[TIFR0]).toEqual(OCF0B); expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should generate Timer Compare A interrupt when TCNT0 == TCNTA', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x20); cpu.writeData(OCR0A, 0x21); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 cpu.writeData(TIMSK0, OCIE0A); cpu.writeData(95, 0x80); // SREG: I------- - timer.tick(); - cpu.tick(); cpu.cycles = 1; - timer.tick(); + cpu.tick(); + cpu.cycles = 2; 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); expect(cpu.pc).toEqual(0x1c); - expect(cpu.cycles).toEqual(3); + expect(cpu.cycles).toEqual(4); }); it('should not generate Timer Compare A interrupt when OCIEA is disabled', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x20); cpu.writeData(OCR0A, 0x21); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 cpu.writeData(TIMSK0, 0); cpu.writeData(95, 0x80); // SREG: I------- - timer.tick(); - cpu.tick(); cpu.cycles = 1; - timer.tick(); + cpu.tick(); + cpu.cycles = 2; cpu.tick(); const tcnt = cpu.readData(TCNT0); expect(tcnt).toEqual(0x21); expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should generate Timer Compare B interrupt when TCNT0 == TCNTB', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); cpu.writeData(TCNT0, 0x20); cpu.writeData(OCR0B, 0x21); cpu.writeData(TCCR0B, CS00); // Set prescaler to 1 cpu.writeData(TIMSK0, OCIE0B); cpu.writeData(95, 0x80); // SREG: I------- - timer.tick(); - cpu.tick(); cpu.cycles = 1; - timer.tick(); + cpu.tick(); + cpu.cycles = 2; 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); expect(cpu.pc).toEqual(0x1e); - expect(cpu.cycles).toEqual(3); + expect(cpu.cycles).toEqual(4); }); it('should not increment TCNT on the same cycle of TCNT write (issue #36)', () => { @@ -353,26 +340,24 @@ describe('timer', () => { IN r17, 0x26 ; r17 <- TCNT `); const cpu = new CPU(program); - const timer = new AVRTimer(cpu, timer0Config); - const runner = new TestProgramRunner(cpu, timer); + new AVRTimer(cpu, timer0Config); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount); expect(cpu.data[R17]).toEqual(0x31); }); it('timer2 should count every 256 ticks when prescaler is 6 (issue #5)', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer2Config); + new AVRTimer(cpu, timer2Config); cpu.writeData(TCCR2B, CS22 | CS21); // Set prescaler to 256 - timer.tick(); + cpu.cycles = 1; 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); }); @@ -387,8 +372,8 @@ describe('timer', () => { LDS r1, 0x46 ; r1 <- TCNT0 (2 cycles) `); const cpu = new CPU(program); - const timer = new AVRTimer(cpu, timer0Config); - const runner = new TestProgramRunner(cpu, timer); + new AVRTimer(cpu, timer0Config); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount); expect(cpu.data[R1]).toEqual(2); }); @@ -405,8 +390,8 @@ describe('timer', () => { LDS r17, 0xb2 ; TCNT should equal 2 at this point `); const cpu = new CPU(program); - const timer = new AVRTimer(cpu, timer2Config); - const runner = new TestProgramRunner(cpu, timer); + new AVRTimer(cpu, timer2Config); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount); expect(cpu.readData(R17)).toEqual(2); }); @@ -432,8 +417,8 @@ describe('timer', () => { IN r22, 0x26 ; TCNT0 will be 1 (end of test) `); const cpu = new CPU(program); - const timer = new AVRTimer(cpu, timer0Config); - const runner = new TestProgramRunner(cpu, timer); + new AVRTimer(cpu, timer0Config); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount); expect(cpu.readData(R17)).toEqual(2); @@ -463,14 +448,14 @@ describe('timer', () => { `); const cpu = new CPU(program); - const timer = new AVRTimer(cpu, timer0Config); + new AVRTimer(cpu, timer0Config); // Listen to Port D's internal callback const gpioCallback = jest.fn(); cpu.gpioTimerHooks[PORTD] = gpioCallback; const nopCount = lines.filter((line) => line.bytes == nopOpCode).length; - const runner = new TestProgramRunner(cpu, timer); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount - nopCount); expect(cpu.readData(TCNT0)).toEqual(0xfd); @@ -495,7 +480,7 @@ describe('timer', () => { describe('16 bit timers', () => { it('should increment 16-bit TCNT by 1', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer1Config); + new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x22); // TCNT1 <- 0x2233 cpu.writeData(TCNT1, 0x33); // ... const timerLow = cpu.readData(TCNT1); @@ -503,10 +488,9 @@ describe('timer', () => { expect((timerHigh << 8) | timerLow).toEqual(0x2233); 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.cycles = 2; cpu.tick(); cpu.readData(TCNT1); expect(cpu.dataView.getUint16(TCNT1, true)).toEqual(0x2234); // TCNT1 should increment @@ -514,69 +498,68 @@ describe('timer', () => { it('should set OCF0A flag when timer equals OCRA (16 bit mode)', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer1Config); + new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x10); // TCNT1 <- 0x10ee cpu.writeData(TCNT1, 0xee); // ... cpu.writeData(OCR1AH, 0x10); // OCR1 <- 0x10ef cpu.writeData(OCR1A, 0xef); // ... 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.cycles = 2; cpu.tick(); expect(cpu.data[TIFR1]).toEqual(OCF1A); // TIFR1 should have OCF1A bit on expect(cpu.pc).toEqual(0); - expect(cpu.cycles).toEqual(1); + expect(cpu.cycles).toEqual(2); }); it('should generate an overflow interrupt if timer overflows and interrupts enabled', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer1Config); - cpu.writeData(TCNT1H, 0x3); // TCNT1 <- 0x3ff - cpu.writeData(TCNT1, 0xff); // ... + new AVRTimer(cpu, timer1Config); cpu.writeData(TCCR1A, 0x3); // TCCR1A <- WGM10 | WGM11 (Fast PWM, 10-bit) cpu.writeData(TCCR1B, 0x9); // TCCR1B <- WGM12 | CS10 - cpu.data[0x6f] = 0x1; // TIMSK1: TOIE1 + cpu.writeData(TIMSK1, 0x1); // TIMSK1: TOIE1 cpu.data[SREG] = 0x80; // SREG: I------- - timer.tick(); - cpu.tick(); + cpu.writeData(TCNT1H, 0x3); // TCNT1 <- 0x3ff cpu.cycles = 1; - timer.tick(); cpu.tick(); + cpu.writeData(TCNT1, 0xff); // ... + cpu.cycles++; // This cycle shouldn't be counted + cpu.tick(); + cpu.cycles++; + cpu.tick(); // This is where we cause the overflow cpu.readData(TCNT1); // Refresh TCNT1 expect(cpu.dataView.getUint16(TCNT1, true)).toEqual(2); expect(cpu.data[TIFR1] & TOV1).toEqual(0); expect(cpu.pc).toEqual(0x1a); - expect(cpu.cycles).toEqual(3); + expect(cpu.cycles).toEqual(5); }); it('should reset the timer once it reaches ICR value in mode 12', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer1Config); + new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0x50); // TCNT1 <- 0x500f cpu.writeData(TCNT1, 0x0f); // ... cpu.writeData(ICR1H, 0x50); // ICR1 <- 0x5010 cpu.writeData(ICR1, 0x10); // ... cpu.writeData(TCCR1B, WGM13 | WGM12 | CS10); // Set prescaler to 1, WGM: CTC - timer.tick(); + cpu.cycles = 1; cpu.tick(); - cpu.cycles = 2; // 2 cycles should increment timer twice, beyond ICR1 - timer.tick(); + cpu.cycles = 3; // 2 cycles should increment timer twice, beyond ICR1 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); - expect(cpu.cycles).toEqual(2); + expect(cpu.cycles).toEqual(3); }); it('should not update the high byte of TCNT if written after the low byte (issue #37)', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer1Config); + new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1, 0x22); cpu.writeData(TCNT1H, 0x55); - timer.tick(); + cpu.cycles = 1; cpu.tick(); const timerLow = cpu.readData(TCNT1); const timerHigh = cpu.readData(TCNT1H); @@ -585,14 +568,13 @@ describe('timer', () => { it('reading from TCNT1H before TCNT1L should return old value (issue #37)', () => { const cpu = new CPU(new Uint16Array(0x1000)); - const timer = new AVRTimer(cpu, timer1Config); + new AVRTimer(cpu, timer1Config); cpu.writeData(TCNT1H, 0xff); 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(); + cpu.cycles = 2; cpu.tick(); // We read the high byte before the low byte, so the high byte should still have // the previous value: @@ -622,14 +604,14 @@ describe('timer', () => { `); const cpu = new CPU(program); - const timer = new AVRTimer(cpu, timer1Config); + new AVRTimer(cpu, timer1Config); // Listen to Port B's internal callback const gpioCallback = jest.fn(); cpu.gpioTimerHooks[PORTB] = gpioCallback; const nopCount = lines.filter((line) => line.bytes == nopOpCode).length; - const runner = new TestProgramRunner(cpu, timer); + const runner = new TestProgramRunner(cpu); runner.runInstructions(instructionCount - nopCount); expect(cpu.readData(TCNT1)).toEqual(0x49); diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts index 81c1e47..b8a6668 100644 --- a/src/peripherals/timer.ts +++ b/src/peripherals/timer.ts @@ -245,6 +245,7 @@ export class AVRTimer { private timerMode: TimerMode; private topValue: TimerTopValue; private tcnt: u16 = 0; + private tcntNext: u16 = 0; private compA: CompBitsValue; private compB: CompBitsValue; private tcntUpdated = false; @@ -280,7 +281,7 @@ export class AVRTimer { constructor(private cpu: CPU, private config: AVRTimerConfig) { this.updateWGMConfig(); this.cpu.readHooks[config.TCNT] = (addr: u8) => { - this.tick(); + this.count(false); if (this.config.bits === 16) { this.cpu.data[addr + 1] = this.tcnt >> 8; } @@ -288,9 +289,10 @@ export class AVRTimer { }; this.cpu.writeHooks[config.TCNT] = (value: u8) => { - this.tcnt = (this.highByteTemp << 8) | value; + this.tcntNext = (this.highByteTemp << 8) | value; this.countingUp = true; this.tcntUpdated = true; + this.cpu.updateClockEvent(this.count, 0); this.timerUpdated(); }; this.cpu.writeHooks[config.OCRA] = (value: u8) => { @@ -324,8 +326,10 @@ export class AVRTimer { }; cpu.writeHooks[config.TCCRB] = (value) => { this.cpu.data[config.TCCRB] = value; - this.tcntUpdated = true; this.divider = this.config.dividers[this.CS]; + this.reschedule(); + this.tcntUpdated = true; + this.cpu.updateClockEvent(this.count, 0); this.updateWGMConfig(); return true; }; @@ -348,6 +352,11 @@ export class AVRTimer { this.lastCycle = 0; this.ocrA = 0; this.ocrB = 0; + this.icr = 0; + this.tcnt = 0; + this.tcntNext = 0; + this.tcntUpdated = false; + this.countingUp = false; } get TCCRA() { @@ -389,7 +398,7 @@ export class AVRTimer { this.topValue = topValue; } - tick() { + count = (reschedule = true) => { const { divider, lastCycle } = this; const delta = this.cpu.cycles - lastCycle; if (divider && delta >= divider) { @@ -411,7 +420,18 @@ export class AVRTimer { this.cpu.setInterruptFlag(this.OVF); } } - this.tcntUpdated = false; + if (this.tcntUpdated) { + this.tcnt = this.tcntNext; + this.tcntUpdated = false; + } + if (reschedule) { + this.cpu.addClockEvent(this.count, this.lastCycle + divider - this.cpu.cycles); + } + }; + + private reschedule() { + this.cpu.clearClockEvent(this.count); + this.cpu.addClockEvent(this.count, this.lastCycle + this.divider - this.cpu.cycles); } private phasePwmCount(value: u16, delta: u8) { diff --git a/src/peripherals/twi.spec.ts b/src/peripherals/twi.spec.ts index e43ae38..2628b57 100644 --- a/src/peripherals/twi.spec.ts +++ b/src/peripherals/twi.spec.ts @@ -51,7 +51,6 @@ describe('TWI', () => { 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); @@ -64,7 +63,7 @@ describe('TWI', () => { const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); jest.spyOn(twi.eventHandler, 'start'); cpu.writeData(TWCR, TWINT | TWSTA | TWEN); - twi.tick(); + cpu.cycles++; cpu.tick(); expect(twi.eventHandler.start).toHaveBeenCalledWith(false); }); @@ -173,7 +172,7 @@ describe('TWI', () => { `); const cpu = new CPU(program); const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - const runner = new TestProgramRunner(cpu, twi, onTestBreak); + const runner = new TestProgramRunner(cpu, onTestBreak); twi.eventHandler = { start: jest.fn(), stop: jest.fn(), @@ -342,7 +341,7 @@ describe('TWI', () => { `); const cpu = new CPU(program); const twi = new AVRTWI(cpu, twiConfig, FREQ_16MHZ); - const runner = new TestProgramRunner(cpu, twi, onTestBreak); + const runner = new TestProgramRunner(cpu, onTestBreak); twi.eventHandler = { start: jest.fn(), stop: jest.fn(), diff --git a/src/peripherals/twi.ts b/src/peripherals/twi.ts index 914b67f..000bd2a 100644 --- a/src/peripherals/twi.ts +++ b/src/peripherals/twi.ts @@ -95,8 +95,6 @@ export class NoopTWIEventHandler implements TWIEventHandler { export class AVRTWI { public eventHandler: TWIEventHandler = new NoopTWIEventHandler(this); - private nextTick: (() => void) | null = null; - // Interrupts private TWI: AVRInterruptConfig = { address: this.config.twiInterrupt, @@ -116,7 +114,7 @@ export class AVRTWI { const { status } = this; if (clearInt && value & TWCR_TWEN) { const twdrValue = this.cpu.data[this.config.TWDR]; - this.nextTick = () => { + this.cpu.addClockEvent(() => { if (value & TWCR_TWSTA) { this.eventHandler.start(status !== STATUS_TWI_IDLE); } else if (value & TWCR_TWSTO) { @@ -129,19 +127,12 @@ export class AVRTWI { const ack = !!(value & TWCR_TWEA); this.eventHandler.readByte(ack); } - }; + }, 0); return true; } }; } - tick() { - if (this.nextTick) { - this.nextTick(); - this.nextTick = null; - } - } - get prescaler() { switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) { case 0: diff --git a/src/peripherals/usart.spec.ts b/src/peripherals/usart.spec.ts index c1e1570..fb56967 100644 --- a/src/peripherals/usart.spec.ts +++ b/src/peripherals/usart.spec.ts @@ -146,10 +146,9 @@ describe('USART', () => { 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); + new AVRUSART(cpu, usart0Config, FREQ_16MHZ); 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); @@ -158,12 +157,11 @@ describe('USART', () => { 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); + new AVRUSART(cpu, usart0Config, FREQ_16MHZ); cpu.writeData(UCSR0B, TXCIE | TXEN); cpu.writeData(UDR0, 0x61); 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); @@ -172,10 +170,9 @@ describe('USART', () => { it('should not trigger data TX Complete interrupt if UDR was not written to', () => { const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + new AVRUSART(cpu, usart0Config, FREQ_16MHZ); 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); @@ -183,12 +180,11 @@ describe('USART', () => { 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); + new AVRUSART(cpu, usart0Config, FREQ_16MHZ); cpu.writeData(UCSR0B, UDRIE | TXEN); cpu.writeData(UDR0, 0x61); 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); @@ -242,16 +238,14 @@ describe('USART', () => { describe('integration', () => { it('should set the TXC bit after ~1.04mS when baud rate set to 9600', () => { const cpu = new CPU(new Uint16Array(1024)); - const usart = new AVRUSART(cpu, usart0Config, FREQ_16MHZ); + new AVRUSART(cpu, usart0Config, FREQ_16MHZ); cpu.writeData(UCSR0B, TXEN); cpu.writeData(UBRR0L, 103); // baud: 9600 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 c1441ad..c7dcbd4 100644 --- a/src/peripherals/usart.ts +++ b/src/peripherals/usart.ts @@ -87,8 +87,6 @@ export class AVRUSART { enableMask: UCSRB_TXCIE, }; - private txCompleteCycles = 0; - constructor(private cpu: CPU, private config: USARTConfig, private freqMHz: number) { this.reset(); this.cpu.writeHooks[config.UCSRA] = (value) => { @@ -119,7 +117,11 @@ 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; + const cyclesToComplete = (this.UBRR * this.multiplier + 1) * symbolsPerChar; + this.cpu.addClockEvent(() => { + cpu.setInterruptFlag(this.UDRE); + cpu.setInterruptFlag(this.TXC); + }, cyclesToComplete); this.cpu.clearInterrupt(this.TXC); this.cpu.clearInterrupt(this.UDRE); }; @@ -131,15 +133,6 @@ export class AVRUSART { this.cpu.data[this.config.UCSRC] = UCSRC_UCSZ1 | UCSRC_UCSZ0; // default: 8 bits per byte } - tick() { - const { txCompleteCycles, cpu } = this; - if (txCompleteCycles && cpu.cycles >= txCompleteCycles) { - cpu.setInterruptFlag(this.UDRE); - cpu.setInterruptFlag(this.TXC); - this.txCompleteCycles = 0; - } - } - private get UBRR() { return (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL]; } -- cgit v1.2.3