aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorUri Shaked2020-12-09 15:46:53 +0200
committerUri Shaked2020-12-09 15:49:41 +0200
commit9c1288f18889ae3bd10869a9f6ebc53defa3024b (patch)
tree1857fe48d3e2d32a39cfe810a0dfdd7d96526b3a /src
parentrefactor: central interrupt handling #38 (diff)
downloadavr8js-9c1288f18889ae3bd10869a9f6ebc53defa3024b.tar.gz
avr8js-9c1288f18889ae3bd10869a9f6ebc53defa3024b.tar.bz2
avr8js-9c1288f18889ae3bd10869a9f6ebc53defa3024b.zip
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.
Diffstat (limited to '')
-rw-r--r--src/cpu/cpu.ts45
-rw-r--r--src/peripherals/eeprom.spec.ts34
-rw-r--r--src/peripherals/eeprom.ts22
-rw-r--r--src/peripherals/spi.spec.ts11
-rw-r--r--src/peripherals/spi.ts22
-rw-r--r--src/peripherals/timer.spec.ts212
-rw-r--r--src/peripherals/timer.ts30
-rw-r--r--src/peripherals/twi.spec.ts7
-rw-r--r--src/peripherals/twi.ts13
-rw-r--r--src/peripherals/usart.spec.ts16
-rw-r--r--src/peripherals/usart.ts17
-rw-r--r--src/utils/test-utils.ts14
12 files changed, 222 insertions, 221 deletions
diff --git a/src/cpu/cpu.ts b/src/cpu/cpu.ts
index 4b70efb..fb98786 100644
--- a/src/cpu/cpu.ts
+++ b/src/cpu/cpu.ts
@@ -55,6 +55,13 @@ export interface AVRInterruptConfig {
constant?: boolean;
}
+export type AVRClockEventCallback = () => void;
+
+interface AVRClockEventEntry {
+ cycles: number;
+ callback: AVRClockEventCallback;
+}
+
export class CPU implements ICPU {
readonly data: Uint8Array = new Uint8Array(this.sramBytes + registerSpace);
readonly data16 = new Uint16Array(this.data.buffer);
@@ -63,6 +70,7 @@ export class CPU implements ICPU {
readonly readHooks: CPUMemoryReadHooks = [];
readonly writeHooks: CPUMemoryHooks = [];
private readonly pendingInterrupts: AVRInterruptConfig[] = [];
+ private readonly clockEvents: AVRClockEventEntry[] = [];
readonly pc22Bits = this.progBytes.length > 0x20000;
// This lets the Timer Compare output override GPIO pins:
@@ -71,6 +79,7 @@ export class CPU implements ICPU {
pc: u32 = 0;
cycles: u32 = 0;
nextInterrupt: i16 = -1;
+ private nextClockEvent: u32 = 0;
constructor(public progMem: Uint16Array, private sramBytes = 8192) {
this.reset();
@@ -164,10 +173,44 @@ export class CPU implements ICPU {
}
}
+ private updateClockEvents() {
+ this.clockEvents.sort((a, b) => a.cycles - b.cycles);
+ this.nextClockEvent = this.clockEvents[0]?.cycles ?? 0;
+ }
+
+ addClockEvent(callback: AVRClockEventCallback, cycles: number) {
+ const entry = { cycles: this.cycles + Math.max(1, cycles), callback };
+ this.clockEvents.push(entry);
+ this.updateClockEvents();
+ return callback;
+ }
+
+ updateClockEvent(callback: AVRClockEventCallback, cycles: number) {
+ const entry = this.clockEvents.find((item) => (item.callback = callback));
+ if (entry) {
+ entry.cycles = this.cycles + Math.max(1, cycles);
+ this.updateClockEvents();
+ return true;
+ }
+ return false;
+ }
+
+ clearClockEvent(callback: AVRClockEventCallback) {
+ const index = this.clockEvents.findIndex((item) => (item.callback = callback));
+ if (index >= 0) {
+ this.clockEvents.splice(index, 1);
+ this.updateClockEvents();
+ }
+ }
+
tick() {
+ if (this.nextClockEvent && this.nextClockEvent <= this.cycles) {
+ const clockEvent = this.clockEvents.shift();
+ clockEvent?.callback();
+ this.nextClockEvent = this.clockEvents[0]?.cycles ?? 0;
+ }
if (this.interruptsEnabled && this.nextInterrupt >= 0) {
const interrupt = this.pendingInterrupts[this.nextInterrupt];
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
avrInterrupt(this, interrupt.address);
if (!interrupt.constant) {
this.clearInterrupt(interrupt);
diff --git a/src/peripherals/eeprom.spec.ts b/src/peripherals/eeprom.spec.ts
index 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];
}
diff --git a/src/utils/test-utils.ts b/src/utils/test-utils.ts
index 2bec178..4b2efff 100644
--- a/src/utils/test-utils.ts
+++ b/src/utils/test-utils.ts
@@ -13,33 +13,29 @@ export function asmProgram(source: string) {
}
export class TestProgramRunner {
- constructor(
- private readonly cpu: CPU,
- private readonly peripheral: { tick: () => void },
- private readonly onBreak?: (cpu: CPU) => void
- ) {}
+ constructor(private readonly cpu: CPU, private readonly onBreak?: (cpu: CPU) => void) {}
runInstructions(count: number) {
- const { cpu, peripheral, onBreak } = this;
+ const { cpu, onBreak } = this;
for (let i = 0; i < count; i++) {
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
onBreak?.(cpu);
throw new Error('BREAK instruction encountered');
}
avrInstruction(cpu);
- peripheral.tick();
+ cpu.tick();
}
}
runToBreak(maxIterations = 5000) {
- const { cpu, peripheral, onBreak } = this;
+ const { cpu, onBreak } = this;
for (let i = 0; i < maxIterations; i++) {
if (cpu.progMem[cpu.pc] === BREAK_OPCODE) {
onBreak?.(cpu);
return;
}
avrInstruction(cpu);
- peripheral.tick();
+ cpu.tick();
}
throw new Error('Program ran for too long without a BREAK instruction');
}