Skip to content

Commit 36c4134

Browse files
committed
refactor: central interrupt handling #38
1 parent c342246 commit 36c4134

File tree

13 files changed

+286
-92
lines changed

13 files changed

+286
-92
lines changed

demo/src/execute.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ export class AVRRunner {
5454
this.timer1.tick();
5555
this.timer2.tick();
5656
this.usart.tick();
57+
this.cpu.tick();
5758
}
5859

5960
callback(this.cpu);

src/cpu/cpu.ts

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
* Copyright (C) 2019, Uri Shaked
66
*/
77

8-
import { u32, u16, u8 } from '../types';
8+
import { u32, u16, u8, i16 } from '../types';
9+
import { avrInterrupt } from './interrupt';
910

1011
const registerSpace = 0x100;
1112

@@ -45,20 +46,31 @@ export interface CPUMemoryReadHooks {
4546
[key: number]: CPUMemoryReadHook;
4647
}
4748

49+
export interface AVRInterruptConfig {
50+
address: u8;
51+
enableRegister: u16;
52+
enableMask: u8;
53+
flagRegister: u16;
54+
flagMask: u8;
55+
constant?: boolean;
56+
}
57+
4858
export class CPU implements ICPU {
4959
readonly data: Uint8Array = new Uint8Array(this.sramBytes + registerSpace);
5060
readonly data16 = new Uint16Array(this.data.buffer);
5161
readonly dataView = new DataView(this.data.buffer);
5262
readonly progBytes = new Uint8Array(this.progMem.buffer);
5363
readonly readHooks: CPUMemoryReadHooks = [];
5464
readonly writeHooks: CPUMemoryHooks = [];
65+
private readonly pendingInterrupts: AVRInterruptConfig[] = [];
5566
readonly pc22Bits = this.progBytes.length > 0x20000;
5667

5768
// This lets the Timer Compare output override GPIO pins:
5869
readonly gpioTimerHooks: CPUMemoryHooks = [];
5970

60-
pc = 0;
61-
cycles = 0;
71+
pc: u32 = 0;
72+
cycles: u32 = 0;
73+
nextInterrupt: i16 = -1;
6274

6375
constructor(public progMem: Uint16Array, private sramBytes = 8192) {
6476
this.reset();
@@ -67,6 +79,8 @@ export class CPU implements ICPU {
6779
reset() {
6880
this.data.fill(0);
6981
this.SP = this.data.length - 1;
82+
this.pendingInterrupts.splice(0, this.pendingInterrupts.length);
83+
this.nextInterrupt = -1;
7084
}
7185

7286
readData(addr: number) {
@@ -101,4 +115,63 @@ export class CPU implements ICPU {
101115
get interruptsEnabled() {
102116
return this.SREG & 0x80 ? true : false;
103117
}
118+
119+
private updateNextInterrupt() {
120+
this.nextInterrupt = this.pendingInterrupts.findIndex((item) => !!item);
121+
}
122+
123+
setInterruptFlag(interrupt: AVRInterruptConfig) {
124+
const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
125+
if (interrupt.constant) {
126+
this.data[flagRegister] &= ~flagMask;
127+
} else {
128+
this.data[flagRegister] |= flagMask;
129+
}
130+
if (this.data[enableRegister] & enableMask) {
131+
this.queueInterrupt(interrupt);
132+
}
133+
}
134+
135+
updateInterruptEnable(interrupt: AVRInterruptConfig, registerValue: u8) {
136+
const { enableMask, flagRegister, flagMask } = interrupt;
137+
if (registerValue & enableMask) {
138+
if (this.data[flagRegister] & flagMask) {
139+
this.queueInterrupt(interrupt);
140+
}
141+
} else {
142+
this.clearInterrupt(interrupt, false);
143+
}
144+
}
145+
146+
queueInterrupt(interrupt: AVRInterruptConfig) {
147+
this.pendingInterrupts[interrupt.address] = interrupt;
148+
this.updateNextInterrupt();
149+
}
150+
151+
clearInterrupt({ address, flagRegister, flagMask }: AVRInterruptConfig, clearFlag = true) {
152+
delete this.pendingInterrupts[address];
153+
if (clearFlag) {
154+
this.data[flagRegister] &= ~flagMask;
155+
}
156+
this.updateNextInterrupt();
157+
}
158+
159+
clearInterruptByFlag(interrupt: AVRInterruptConfig, registerValue: number) {
160+
const { flagRegister, flagMask } = interrupt;
161+
if (registerValue & flagMask) {
162+
this.data[flagRegister] &= ~flagMask;
163+
this.clearInterrupt(interrupt);
164+
}
165+
}
166+
167+
tick() {
168+
if (this.interruptsEnabled && this.nextInterrupt >= 0) {
169+
const interrupt = this.pendingInterrupts[this.nextInterrupt];
170+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
171+
avrInterrupt(this, interrupt.address);
172+
if (!interrupt.constant) {
173+
this.clearInterrupt(interrupt);
174+
}
175+
}
176+
}
104177
}

src/peripherals/eeprom.spec.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ describe('EEPROM', () => {
2828
cpu.writeData(EEARH, 0);
2929
cpu.writeData(EECR, EERE);
3030
eeprom.tick();
31+
cpu.tick();
3132
expect(cpu.cycles).toEqual(4);
3233
expect(cpu.data[EEDR]).toEqual(0xff);
3334
});
@@ -41,6 +42,7 @@ describe('EEPROM', () => {
4142
cpu.writeData(EEARH, 0x2);
4243
cpu.writeData(EECR, EERE);
4344
eeprom.tick();
45+
cpu.tick();
4446
expect(cpu.data[EEDR]).toEqual(0x42);
4547
});
4648
});
@@ -56,6 +58,7 @@ describe('EEPROM', () => {
5658
cpu.writeData(EECR, EEMPE);
5759
cpu.writeData(EECR, EEPE);
5860
eeprom.tick();
61+
cpu.tick();
5962
expect(cpu.cycles).toEqual(2);
6063
expect(eepromBackend.memory[15]).toEqual(0x55);
6164
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
@@ -104,12 +107,14 @@ describe('EEPROM', () => {
104107
cpu.writeData(EECR, EEPE);
105108
cpu.cycles += 1000;
106109
eeprom.tick();
110+
cpu.tick();
107111
// At this point, write shouldn't be complete yet
108112
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
109113
expect(cpu.pc).toEqual(0);
110114
cpu.cycles += 10000000;
111115
// And now, 10 million cycles later, it should.
112116
eeprom.tick();
117+
cpu.tick();
113118
expect(eepromBackend.memory[15]).toEqual(0x55);
114119
expect(cpu.data[EECR] & EEPE).toEqual(0);
115120
expect(cpu.pc).toEqual(0x2c); // EEPROM Ready interrupt
@@ -125,8 +130,10 @@ describe('EEPROM', () => {
125130
cpu.writeData(EECR, EEMPE);
126131
cpu.cycles = 8; // waiting for more than 4 cycles should clear EEMPE
127132
eeprom.tick();
133+
cpu.tick();
128134
cpu.writeData(EECR, EEPE);
129135
eeprom.tick();
136+
cpu.tick();
130137
// Ensure that nothing was written, and EEPE bit is clear
131138
expect(cpu.cycles).toEqual(8);
132139
expect(eepromBackend.memory[15]).toEqual(0xff);
@@ -145,6 +152,7 @@ describe('EEPROM', () => {
145152
cpu.writeData(EECR, EEMPE);
146153
cpu.writeData(EECR, EEPE);
147154
eeprom.tick();
155+
cpu.tick();
148156
expect(cpu.cycles).toEqual(2);
149157

150158
// Write 0x66 to address 16 (first write is still in progress)
@@ -154,6 +162,7 @@ describe('EEPROM', () => {
154162
cpu.writeData(EECR, EEMPE);
155163
cpu.writeData(EECR, EEPE);
156164
eeprom.tick();
165+
cpu.tick();
157166

158167
// Ensure that second write didn't happen
159168
expect(cpu.cycles).toEqual(2);
@@ -173,11 +182,13 @@ describe('EEPROM', () => {
173182
cpu.writeData(EECR, EEMPE);
174183
cpu.writeData(EECR, EEPE);
175184
eeprom.tick();
185+
cpu.tick();
176186
expect(cpu.cycles).toEqual(2);
177187

178188
// wait long enough time for the first write to finish
179189
cpu.cycles += 10000000;
180190
eeprom.tick();
191+
cpu.tick();
181192

182193
// Write 0x66 to address 16
183194
cpu.writeData(EEDR, 0x66);
@@ -186,6 +197,7 @@ describe('EEPROM', () => {
186197
cpu.writeData(EECR, EEMPE);
187198
cpu.writeData(EECR, EEPE);
188199
eeprom.tick();
200+
cpu.tick();
189201

190202
// Ensure both writes took place
191203
expect(cpu.cycles).toEqual(10000004);

src/peripherals/eeprom.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { CPU } from '../cpu/cpu';
2-
import { avrInterrupt } from '../cpu/interrupt';
3-
import { u8, u16, u32 } from '../types';
1+
import { AVRInterruptConfig, CPU } from '../cpu/cpu';
2+
import { u16, u32, u8 } from '../types';
43

54
export interface EEPROMBackend {
65
readMemory(addr: u16): u8;
@@ -71,6 +70,16 @@ export class AVREEPROM {
7170

7271
private writeCompleteCycles = 0;
7372

73+
// Interrupts
74+
private EER: AVRInterruptConfig = {
75+
address: this.config.eepromReadyInterrupt,
76+
flagRegister: this.config.EECR,
77+
flagMask: EEPE,
78+
enableRegister: this.config.EECR,
79+
enableMask: EERIE,
80+
constant: true,
81+
};
82+
7483
constructor(
7584
private cpu: CPU,
7685
private backend: EEPROMBackend,
@@ -81,6 +90,10 @@ export class AVREEPROM {
8190

8291
const addr = (this.cpu.data[EEARH] << 8) | this.cpu.data[EEARL];
8392

93+
if (eecr & EERE) {
94+
this.cpu.clearInterrupt(this.EER);
95+
}
96+
8497
if (eecr & EEMPE) {
8598
this.writeEnabledCycles = this.cpu.cycles + 4;
8699
}
@@ -132,16 +145,13 @@ export class AVREEPROM {
132145
}
133146

134147
tick() {
135-
const { EECR, eepromReadyInterrupt } = this.config;
148+
const { EECR } = this.config;
136149

137150
if (this.writeEnabledCycles && this.cpu.cycles > this.writeEnabledCycles) {
138151
this.cpu.data[EECR] &= ~EEMPE;
139152
}
140153
if (this.writeCompleteCycles && this.cpu.cycles > this.writeCompleteCycles) {
141-
this.cpu.data[EECR] &= ~EEPE;
142-
if (this.cpu.interruptsEnabled && this.cpu.data[EECR] & EERIE) {
143-
avrInterrupt(this.cpu, eepromReadyInterrupt);
144-
}
154+
this.cpu.setInterruptFlag(this.EER);
145155
}
146156
}
147157
}

src/peripherals/spi.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ describe('SPI', () => {
177177
cpu.writeData(SPCR, SPE | MSTR);
178178
cpu.writeData(SPDR, 0x50);
179179
spi.tick();
180+
cpu.tick();
180181
expect(cpu.readData(SPSR) & WCOL).toEqual(0);
181182

182183
cpu.writeData(SPDR, 0x51);
@@ -194,11 +195,13 @@ describe('SPI', () => {
194195
// At this point, write shouldn't be complete yet
195196
cpu.cycles += 10;
196197
spi.tick();
198+
cpu.tick();
197199
expect(cpu.pc).toEqual(0);
198200

199201
// 100 cycles later, it should (8 bits * 8 cycles per bit = 64).
200202
cpu.cycles += 100;
201203
spi.tick();
204+
cpu.tick();
202205
expect(cpu.data[SPSR] & SPIF).toEqual(0);
203206
expect(cpu.pc).toEqual(0x22); // SPI Ready interrupt
204207
});
@@ -213,10 +216,12 @@ describe('SPI', () => {
213216

214217
cpu.cycles = 10;
215218
spi.tick();
219+
cpu.tick();
216220
expect(cpu.readData(SPDR)).toEqual(0);
217221

218222
cpu.cycles = 32; // 4 cycles per bit * 8 bits = 32
219223
spi.tick();
224+
cpu.tick();
220225
expect(cpu.readData(SPDR)).toEqual(0x88);
221226
});
222227
});

src/peripherals/spi.ts

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import { CPU } from '../cpu/cpu';
1+
import { AVRInterruptConfig, CPU } from '../cpu/cpu';
22
import { u8 } from '../types';
3-
import { avrInterrupt } from '../cpu/interrupt';
43

54
export interface SPIConfig {
65
spiInterrupt: u8;
@@ -42,6 +41,15 @@ export class AVRSPI {
4241
private transmissionCompleteCycles = 0;
4342
private receivedByte: u8 = 0;
4443

44+
// Interrupts
45+
private SPI: AVRInterruptConfig = {
46+
address: this.config.spiInterrupt,
47+
flagRegister: this.config.SPSR,
48+
flagMask: SPSR_SPIF,
49+
enableRegister: this.config.SPCR,
50+
enableMask: SPCR_SPIE,
51+
};
52+
4553
constructor(private cpu: CPU, private config: SPIConfig, private freqMHz: number) {
4654
const { SPCR, SPSR, SPDR } = config;
4755
cpu.writeHooks[SPDR] = (value: u8) => {
@@ -57,28 +65,26 @@ export class AVRSPI {
5765
}
5866

5967
// Clear write collision / interrupt flags
60-
cpu.data[SPSR] &= ~SPSR_WCOL & ~SPSR_SPIF;
68+
cpu.data[SPSR] &= ~SPSR_WCOL;
69+
this.cpu.clearInterrupt(this.SPI);
6170

6271
this.receivedByte = this.onTransfer?.(value) ?? 0;
6372
this.transmissionCompleteCycles = this.cpu.cycles + this.clockDivider * bitsPerByte;
6473
return true;
6574
};
75+
cpu.writeHooks[SPSR] = (value: u8) => {
76+
this.cpu.data[SPSR] = value;
77+
this.cpu.clearInterruptByFlag(this.SPI, value);
78+
};
6679
}
6780

6881
tick() {
6982
if (this.transmissionCompleteCycles && this.cpu.cycles >= this.transmissionCompleteCycles) {
70-
const { SPSR, SPDR } = this.config;
71-
this.cpu.data[SPSR] |= SPSR_SPIF;
83+
const { SPDR } = this.config;
7284
this.cpu.data[SPDR] = this.receivedByte;
85+
this.cpu.setInterruptFlag(this.SPI);
7386
this.transmissionCompleteCycles = 0;
7487
}
75-
if (this.cpu.interruptsEnabled) {
76-
const { SPSR, SPCR, spiInterrupt } = this.config;
77-
if (this.cpu.data[SPCR] & SPCR_SPIE && this.cpu.data[SPSR] & SPSR_SPIF) {
78-
avrInterrupt(this.cpu, spiInterrupt);
79-
this.cpu.data[SPSR] &= ~SPSR_SPIF;
80-
}
81-
}
8288
}
8389

8490
get isMaster() {

0 commit comments

Comments
 (0)