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 'src/peripherals')
-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];
}
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.