aboutsummaryrefslogtreecommitdiff
path: root/src/peripherals
diff options
context:
space:
mode:
authorUri Shaked2020-12-09 15:55:12 +0200
committerGitHub2020-12-09 15:55:12 +0200
commitb2280efa5457685db66d6ce6b156f37d5e678204 (patch)
tree1857fe48d3e2d32a39cfe810a0dfdd7d96526b3a /src/peripherals
parenttest(cpu): improve test name (diff)
parentperf!: centeral timekeeping (diff)
downloadavr8js-b2280efa5457685db66d6ce6b156f37d5e678204.tar.gz
avr8js-b2280efa5457685db66d6ce6b156f37d5e678204.tar.bz2
avr8js-b2280efa5457685db66d6ce6b156f37d5e678204.zip
Merge pull request #71 from wokwi/interrupt-refactor
refactor: central interrupt handling #38
Diffstat (limited to '')
-rw-r--r--src/peripherals/eeprom.spec.ts46
-rw-r--r--src/peripherals/eeprom.ts44
-rw-r--r--src/peripherals/spi.spec.ts16
-rw-r--r--src/peripherals/spi.ts48
-rw-r--r--src/peripherals/timer.spec.ts238
-rw-r--r--src/peripherals/timer.ts106
-rw-r--r--src/peripherals/twi.spec.ts12
-rw-r--r--src/peripherals/twi.ts41
-rw-r--r--src/peripherals/usart.spec.ts22
-rw-r--r--src/peripherals/usart.ts58
10 files changed, 356 insertions, 275 deletions
diff --git a/src/peripherals/eeprom.spec.ts b/src/peripherals/eeprom.spec.ts
index e7cf62e..8f08e47 100644
--- a/src/peripherals/eeprom.spec.ts
+++ b/src/peripherals/eeprom.spec.ts
@@ -23,11 +23,11 @@ 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);
});
@@ -35,12 +35,12 @@ 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);
});
});
@@ -49,13 +49,13 @@ 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);
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
@@ -81,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.
@@ -95,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);
@@ -103,13 +103,13 @@ 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);
expect(cpu.pc).toEqual(0x2c); // EEPROM Ready interrupt
@@ -118,15 +118,15 @@ 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);
expect(eepromBackend.memory[15]).toEqual(0xff);
@@ -136,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);
@@ -144,7 +144,7 @@ describe('EEPROM', () => {
cpu.writeData(EEARH, 0);
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)
@@ -153,7 +153,7 @@ 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
expect(cpu.cycles).toEqual(2);
@@ -164,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);
@@ -172,12 +172,12 @@ 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
cpu.writeData(EEDR, 0x66);
@@ -185,7 +185,7 @@ describe('EEPROM', () => {
cpu.writeData(EEARH, 0);
cpu.writeData(EECR, EEMPE);
cpu.writeData(EECR, EEPE);
- eeprom.tick();
+ cpu.tick();
// Ensure both writes took place
expect(cpu.cycles).toEqual(10000004);
@@ -214,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 0301701..d492aa7 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,8 +90,16 @@ 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;
+ const eempeCycles = 4;
+ this.writeEnabledCycles = this.cpu.cycles + eempeCycles;
+ this.cpu.addClockEvent(() => {
+ this.cpu.data[EECR] &= ~EEMPE;
+ }, eempeCycles);
}
// Read
@@ -121,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;
@@ -130,18 +152,4 @@ export class AVREEPROM {
return false;
};
}
-
- tick() {
- const { EECR, eepromReadyInterrupt } = 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);
- }
- }
- }
}
diff --git a/src/peripherals/spi.spec.ts b/src/peripherals/spi.spec.ts
index 1bb099f..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,11 @@ 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);
cpu.writeData(SPDR, 0x51);
@@ -185,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);
@@ -193,12 +193,12 @@ 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
});
@@ -212,11 +212,11 @@ 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 0c03c3f..7dd5d43 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;
@@ -39,9 +38,18 @@ const bitsPerByte = 8;
export class AVRSPI {
public onTransfer: SPITransferCallback | null = null;
- private transmissionCompleteCycles = 0;
+ private transmissionActive = false;
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) => {
@@ -51,34 +59,34 @@ export class AVRSPI {
}
// Write collision
- if (this.transmissionCompleteCycles > this.cpu.cycles) {
+ if (this.transmissionActive) {
cpu.data[SPSR] |= SPSR_WCOL;
return true;
}
// 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;
+ 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) => {
+ 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;
- this.cpu.data[SPDR] = this.receivedByte;
- 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;
- }
- }
+ reset() {
+ this.transmissionActive = false;
+ this.receivedByte = 0;
}
get isMaster() {
diff --git a/src/peripherals/timer.spec.ts b/src/peripherals/timer.spec.ts
index 2dc7d55..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,59 +67,79 @@ 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.cycles = 1;
- timer.tick();
+ cpu.tick();
+ cpu.cycles = 2;
+ cpu.tick();
const tcnt = cpu.readData(TCNT0);
expect(tcnt).toEqual(1);
});
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);
});
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
});
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.cycles = 1;
- timer.tick();
+ cpu.tick();
+ cpu.cycles = 2;
+ 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));
+ new AVRTimer(cpu, timer0Config);
+ cpu.writeData(TCNT0, 0xff);
+ cpu.writeData(TCCR0B, CS00); // Set prescaler to 1
+ cpu.cycles = 1;
+ cpu.tick();
+ cpu.cycles = 2;
+ 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);
+ 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);
expect(cpu.data[TIFR0]).toEqual(TOV0);
@@ -126,24 +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:
@@ -156,145 +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.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.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.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.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.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.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)', () => {
@@ -309,24 +340,25 @@ 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);
});
@@ -340,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);
});
@@ -358,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);
});
@@ -385,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);
@@ -416,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);
@@ -448,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);
@@ -456,72 +488,79 @@ 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.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
});
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.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.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 = 2; // 2 cycles should increment timer twice, beyond ICR1
- timer.tick();
+ cpu.cycles = 1;
+ cpu.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);
expect((timerHigh << 8) | timerLow).toEqual(0x22);
@@ -529,13 +568,14 @@ 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.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:
const timerHigh = cpu.readData(TCNT1H);
@@ -564,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 e347212..b8a6668 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,
@@ -246,20 +245,43 @@ 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;
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) => {
- this.tick();
+ this.count(false);
if (this.config.bits === 16) {
this.cpu.data[addr + 1] = this.tcnt >> 8;
}
@@ -267,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) => {
@@ -303,11 +326,25 @@ 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;
};
+ 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() {
@@ -315,15 +352,11 @@ export class AVRTimer {
this.lastCycle = 0;
this.ocrA = 0;
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;
+ this.icr = 0;
+ this.tcnt = 0;
+ this.tcntNext = 0;
+ this.tcntUpdated = false;
+ this.countingUp = false;
}
get TCCRA() {
@@ -365,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) {
@@ -384,27 +417,21 @@ 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;
+ 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) {
@@ -418,7 +445,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 +457,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..2628b57 100644
--- a/src/peripherals/twi.spec.ts
+++ b/src/peripherals/twi.spec.ts
@@ -48,9 +48,10 @@ 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.tick();
+ twi.completeStart(); // This will set the TWINT flag
+ cpu.tick();
expect(cpu.pc).toEqual(0x30); // 2-wire Serial Interface Vector
expect(cpu.cycles).toEqual(2);
expect(cpu.data[TWCR] & TWINT).toEqual(0);
@@ -62,7 +63,8 @@ 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);
});
@@ -170,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(),
@@ -339,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 52258a6..000bd2a 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 {
@@ -96,19 +95,26 @@ 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,
+ 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];
- this.nextTick = () => {
+ this.cpu.addClockEvent(() => {
if (value & TWCR_TWSTA) {
this.eventHandler.start(status !== STATUS_TWI_IDLE);
} else if (value & TWCR_TWSTO) {
@@ -121,27 +127,12 @@ export class AVRTWI {
const ack = !!(value & TWCR_TWEA);
this.eventHandler.readByte(ack);
}
- };
- this.cpu.data[config.TWCR] = value;
+ }, 0);
return true;
}
};
}
- tick() {
- if (this.nextTick) {
- this.nextTick();
- this.nextTick = null;
- }
- if (this.cpu.interruptsEnabled) {
- const { TWCR, twiInterrupt } = this.config;
- if (this.cpu.data[TWCR] & TWCR_TWIE && this.cpu.data[TWCR] & TWCR_TWINT) {
- avrInterrupt(this.cpu, twiInterrupt);
- this.cpu.data[TWCR] &= ~TWCR_TWINT;
- }
- }
- }
-
get prescaler() {
switch (this.cpu.data[this.config.TWSR] & TWSR_TWPS_MASK) {
case 0:
@@ -193,8 +184,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..fb56967 100644
--- a/src/peripherals/usart.spec.ts
+++ b/src/peripherals/usart.spec.ts
@@ -146,10 +146,10 @@ 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);
expect(cpu.data[UCSR0A] & UDRE).toEqual(0);
@@ -157,12 +157,12 @@ 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);
expect(cpu.data[UCSR0A] & TXC).toEqual(0);
@@ -170,22 +170,22 @@ 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);
});
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);
expect(cpu.data[UCSR0A]).toEqual(TXC | UDRE);
@@ -238,15 +238,15 @@ 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 a2037a9..c7dcbd4 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,36 @@ export class AVRUSART {
private lineBuffer = '';
- private txCompleteCycles = 0;
+ // 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,
+ };
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) => {
@@ -96,8 +117,13 @@ 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);
+ 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);
};
}
@@ -107,26 +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) {
- this.cpu.data[this.config.UCSRA] |= UCSRA_UDRE | UCSRA_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() {
return (this.cpu.data[this.config.UBRRH] << 8) | this.cpu.data[this.config.UBRRL];
}