Skip to content

Commit b2280ef

Browse files
authored
Merge pull request #71 from wokwi/interrupt-refactor
refactor: central interrupt handling #38
2 parents c342246 + 9c1288f commit b2280ef

File tree

14 files changed

+482
-291
lines changed

14 files changed

+482
-291
lines changed

demo/src/execute.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,7 @@ export class AVRRunner {
5050
const cyclesToRun = this.cpu.cycles + this.workUnitCycles;
5151
while (this.cpu.cycles < cyclesToRun) {
5252
avrInstruction(this.cpu);
53-
this.timer0.tick();
54-
this.timer1.tick();
55-
this.timer2.tick();
56-
this.usart.tick();
53+
this.cpu.tick();
5754
}
5855

5956
callback(this.cpu);

src/cpu/cpu.ts

Lines changed: 119 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,40 @@ 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+
58+
export type AVRClockEventCallback = () => void;
59+
60+
interface AVRClockEventEntry {
61+
cycles: number;
62+
callback: AVRClockEventCallback;
63+
}
64+
4865
export class CPU implements ICPU {
4966
readonly data: Uint8Array = new Uint8Array(this.sramBytes + registerSpace);
5067
readonly data16 = new Uint16Array(this.data.buffer);
5168
readonly dataView = new DataView(this.data.buffer);
5269
readonly progBytes = new Uint8Array(this.progMem.buffer);
5370
readonly readHooks: CPUMemoryReadHooks = [];
5471
readonly writeHooks: CPUMemoryHooks = [];
72+
private readonly pendingInterrupts: AVRInterruptConfig[] = [];
73+
private readonly clockEvents: AVRClockEventEntry[] = [];
5574
readonly pc22Bits = this.progBytes.length > 0x20000;
5675

5776
// This lets the Timer Compare output override GPIO pins:
5877
readonly gpioTimerHooks: CPUMemoryHooks = [];
5978

60-
pc = 0;
61-
cycles = 0;
79+
pc: u32 = 0;
80+
cycles: u32 = 0;
81+
nextInterrupt: i16 = -1;
82+
private nextClockEvent: u32 = 0;
6283

6384
constructor(public progMem: Uint16Array, private sramBytes = 8192) {
6485
this.reset();
@@ -67,6 +88,8 @@ export class CPU implements ICPU {
6788
reset() {
6889
this.data.fill(0);
6990
this.SP = this.data.length - 1;
91+
this.pendingInterrupts.splice(0, this.pendingInterrupts.length);
92+
this.nextInterrupt = -1;
7093
}
7194

7295
readData(addr: number) {
@@ -101,4 +124,97 @@ export class CPU implements ICPU {
101124
get interruptsEnabled() {
102125
return this.SREG & 0x80 ? true : false;
103126
}
127+
128+
private updateNextInterrupt() {
129+
this.nextInterrupt = this.pendingInterrupts.findIndex((item) => !!item);
130+
}
131+
132+
setInterruptFlag(interrupt: AVRInterruptConfig) {
133+
const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
134+
if (interrupt.constant) {
135+
this.data[flagRegister] &= ~flagMask;
136+
} else {
137+
this.data[flagRegister] |= flagMask;
138+
}
139+
if (this.data[enableRegister] & enableMask) {
140+
this.queueInterrupt(interrupt);
141+
}
142+
}
143+
144+
updateInterruptEnable(interrupt: AVRInterruptConfig, registerValue: u8) {
145+
const { enableMask, flagRegister, flagMask } = interrupt;
146+
if (registerValue & enableMask) {
147+
if (this.data[flagRegister] & flagMask) {
148+
this.queueInterrupt(interrupt);
149+
}
150+
} else {
151+
this.clearInterrupt(interrupt, false);
152+
}
153+
}
154+
155+
queueInterrupt(interrupt: AVRInterruptConfig) {
156+
this.pendingInterrupts[interrupt.address] = interrupt;
157+
this.updateNextInterrupt();
158+
}
159+
160+
clearInterrupt({ address, flagRegister, flagMask }: AVRInterruptConfig, clearFlag = true) {
161+
delete this.pendingInterrupts[address];
162+
if (clearFlag) {
163+
this.data[flagRegister] &= ~flagMask;
164+
}
165+
this.updateNextInterrupt();
166+
}
167+
168+
clearInterruptByFlag(interrupt: AVRInterruptConfig, registerValue: number) {
169+
const { flagRegister, flagMask } = interrupt;
170+
if (registerValue & flagMask) {
171+
this.data[flagRegister] &= ~flagMask;
172+
this.clearInterrupt(interrupt);
173+
}
174+
}
175+
176+
private updateClockEvents() {
177+
this.clockEvents.sort((a, b) => a.cycles - b.cycles);
178+
this.nextClockEvent = this.clockEvents[0]?.cycles ?? 0;
179+
}
180+
181+
addClockEvent(callback: AVRClockEventCallback, cycles: number) {
182+
const entry = { cycles: this.cycles + Math.max(1, cycles), callback };
183+
this.clockEvents.push(entry);
184+
this.updateClockEvents();
185+
return callback;
186+
}
187+
188+
updateClockEvent(callback: AVRClockEventCallback, cycles: number) {
189+
const entry = this.clockEvents.find((item) => (item.callback = callback));
190+
if (entry) {
191+
entry.cycles = this.cycles + Math.max(1, cycles);
192+
this.updateClockEvents();
193+
return true;
194+
}
195+
return false;
196+
}
197+
198+
clearClockEvent(callback: AVRClockEventCallback) {
199+
const index = this.clockEvents.findIndex((item) => (item.callback = callback));
200+
if (index >= 0) {
201+
this.clockEvents.splice(index, 1);
202+
this.updateClockEvents();
203+
}
204+
}
205+
206+
tick() {
207+
if (this.nextClockEvent && this.nextClockEvent <= this.cycles) {
208+
const clockEvent = this.clockEvents.shift();
209+
clockEvent?.callback();
210+
this.nextClockEvent = this.clockEvents[0]?.cycles ?? 0;
211+
}
212+
if (this.interruptsEnabled && this.nextInterrupt >= 0) {
213+
const interrupt = this.pendingInterrupts[this.nextInterrupt];
214+
avrInterrupt(this, interrupt.address);
215+
if (!interrupt.constant) {
216+
this.clearInterrupt(interrupt);
217+
}
218+
}
219+
}
104220
}

src/peripherals/eeprom.spec.ts

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,24 @@ describe('EEPROM', () => {
2323
describe('Reading the EEPROM', () => {
2424
it('should return 0xff when reading from an empty location', () => {
2525
const cpu = new CPU(new Uint16Array(0x1000));
26-
const eeprom = new AVREEPROM(cpu, new EEPROMMemoryBackend(1024));
26+
new AVREEPROM(cpu, new EEPROMMemoryBackend(1024));
2727
cpu.writeData(EEARL, 0);
2828
cpu.writeData(EEARH, 0);
2929
cpu.writeData(EECR, EERE);
30-
eeprom.tick();
30+
cpu.tick();
3131
expect(cpu.cycles).toEqual(4);
3232
expect(cpu.data[EEDR]).toEqual(0xff);
3333
});
3434

3535
it('should return the value stored at the given EEPROM address', () => {
3636
const cpu = new CPU(new Uint16Array(0x1000));
3737
const eepromBackend = new EEPROMMemoryBackend(1024);
38-
const eeprom = new AVREEPROM(cpu, eepromBackend);
38+
new AVREEPROM(cpu, eepromBackend);
3939
eepromBackend.memory[0x250] = 0x42;
4040
cpu.writeData(EEARL, 0x50);
4141
cpu.writeData(EEARH, 0x2);
4242
cpu.writeData(EECR, EERE);
43-
eeprom.tick();
43+
cpu.tick();
4444
expect(cpu.data[EEDR]).toEqual(0x42);
4545
});
4646
});
@@ -49,13 +49,13 @@ describe('EEPROM', () => {
4949
it('should write a byte to the given EEPROM address', () => {
5050
const cpu = new CPU(new Uint16Array(0x1000));
5151
const eepromBackend = new EEPROMMemoryBackend(1024);
52-
const eeprom = new AVREEPROM(cpu, eepromBackend);
52+
new AVREEPROM(cpu, eepromBackend);
5353
cpu.writeData(EEDR, 0x55);
5454
cpu.writeData(EEARL, 15);
5555
cpu.writeData(EEARH, 0);
5656
cpu.writeData(EECR, EEMPE);
5757
cpu.writeData(EECR, EEPE);
58-
eeprom.tick();
58+
cpu.tick();
5959
expect(cpu.cycles).toEqual(2);
6060
expect(eepromBackend.memory[15]).toEqual(0x55);
6161
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
@@ -81,10 +81,10 @@ describe('EEPROM', () => {
8181

8282
const cpu = new CPU(program);
8383
const eepromBackend = new EEPROMMemoryBackend(1024);
84-
const eeprom = new AVREEPROM(cpu, eepromBackend);
84+
new AVREEPROM(cpu, eepromBackend);
8585
eepromBackend.memory[9] = 0x0f; // high four bits are cleared
8686

87-
const runner = new TestProgramRunner(cpu, eeprom);
87+
const runner = new TestProgramRunner(cpu);
8888
runner.runInstructions(instructionCount);
8989

9090
// EEPROM was 0x0f, and our program wrote 0x55.
@@ -95,21 +95,21 @@ describe('EEPROM', () => {
9595
it('should clear the EEPE bit and fire an interrupt when write has been completed', () => {
9696
const cpu = new CPU(new Uint16Array(0x1000));
9797
const eepromBackend = new EEPROMMemoryBackend(1024);
98-
const eeprom = new AVREEPROM(cpu, eepromBackend);
98+
new AVREEPROM(cpu, eepromBackend);
9999
cpu.writeData(EEDR, 0x55);
100100
cpu.writeData(EEARL, 15);
101101
cpu.writeData(EEARH, 0);
102102
cpu.writeData(EECR, EEMPE | EERIE);
103103
cpu.data[SREG] = 0x80; // SREG: I-------
104104
cpu.writeData(EECR, EEPE);
105105
cpu.cycles += 1000;
106-
eeprom.tick();
106+
cpu.tick();
107107
// At this point, write shouldn't be complete yet
108108
expect(cpu.data[EECR] & EEPE).toEqual(EEPE);
109109
expect(cpu.pc).toEqual(0);
110110
cpu.cycles += 10000000;
111111
// And now, 10 million cycles later, it should.
112-
eeprom.tick();
112+
cpu.tick();
113113
expect(eepromBackend.memory[15]).toEqual(0x55);
114114
expect(cpu.data[EECR] & EEPE).toEqual(0);
115115
expect(cpu.pc).toEqual(0x2c); // EEPROM Ready interrupt
@@ -118,15 +118,15 @@ describe('EEPROM', () => {
118118
it('should skip the write if EEMPE is clear', () => {
119119
const cpu = new CPU(new Uint16Array(0x1000));
120120
const eepromBackend = new EEPROMMemoryBackend(1024);
121-
const eeprom = new AVREEPROM(cpu, eepromBackend);
121+
new AVREEPROM(cpu, eepromBackend);
122122
cpu.writeData(EEDR, 0x55);
123123
cpu.writeData(EEARL, 15);
124124
cpu.writeData(EEARH, 0);
125125
cpu.writeData(EECR, EEMPE);
126126
cpu.cycles = 8; // waiting for more than 4 cycles should clear EEMPE
127-
eeprom.tick();
127+
cpu.tick();
128128
cpu.writeData(EECR, EEPE);
129-
eeprom.tick();
129+
cpu.tick();
130130
// Ensure that nothing was written, and EEPE bit is clear
131131
expect(cpu.cycles).toEqual(8);
132132
expect(eepromBackend.memory[15]).toEqual(0xff);
@@ -136,15 +136,15 @@ describe('EEPROM', () => {
136136
it('should skip the write if another write is already in progress', () => {
137137
const cpu = new CPU(new Uint16Array(0x1000));
138138
const eepromBackend = new EEPROMMemoryBackend(1024);
139-
const eeprom = new AVREEPROM(cpu, eepromBackend);
139+
new AVREEPROM(cpu, eepromBackend);
140140

141141
// Write 0x55 to address 15
142142
cpu.writeData(EEDR, 0x55);
143143
cpu.writeData(EEARL, 15);
144144
cpu.writeData(EEARH, 0);
145145
cpu.writeData(EECR, EEMPE);
146146
cpu.writeData(EECR, EEPE);
147-
eeprom.tick();
147+
cpu.tick();
148148
expect(cpu.cycles).toEqual(2);
149149

150150
// Write 0x66 to address 16 (first write is still in progress)
@@ -153,7 +153,7 @@ describe('EEPROM', () => {
153153
cpu.writeData(EEARH, 0);
154154
cpu.writeData(EECR, EEMPE);
155155
cpu.writeData(EECR, EEPE);
156-
eeprom.tick();
156+
cpu.tick();
157157

158158
// Ensure that second write didn't happen
159159
expect(cpu.cycles).toEqual(2);
@@ -164,28 +164,28 @@ describe('EEPROM', () => {
164164
it('should write two bytes sucessfully', () => {
165165
const cpu = new CPU(new Uint16Array(0x1000));
166166
const eepromBackend = new EEPROMMemoryBackend(1024);
167-
const eeprom = new AVREEPROM(cpu, eepromBackend);
167+
new AVREEPROM(cpu, eepromBackend);
168168

169169
// Write 0x55 to address 15
170170
cpu.writeData(EEDR, 0x55);
171171
cpu.writeData(EEARL, 15);
172172
cpu.writeData(EEARH, 0);
173173
cpu.writeData(EECR, EEMPE);
174174
cpu.writeData(EECR, EEPE);
175-
eeprom.tick();
175+
cpu.tick();
176176
expect(cpu.cycles).toEqual(2);
177177

178178
// wait long enough time for the first write to finish
179179
cpu.cycles += 10000000;
180-
eeprom.tick();
180+
cpu.tick();
181181

182182
// Write 0x66 to address 16
183183
cpu.writeData(EEDR, 0x66);
184184
cpu.writeData(EEARL, 16);
185185
cpu.writeData(EEARH, 0);
186186
cpu.writeData(EECR, EEMPE);
187187
cpu.writeData(EECR, EEPE);
188-
eeprom.tick();
188+
cpu.tick();
189189

190190
// Ensure both writes took place
191191
expect(cpu.cycles).toEqual(10000004);
@@ -214,10 +214,10 @@ describe('EEPROM', () => {
214214

215215
const cpu = new CPU(program);
216216
const eepromBackend = new EEPROMMemoryBackend(1024);
217-
const eeprom = new AVREEPROM(cpu, eepromBackend);
217+
new AVREEPROM(cpu, eepromBackend);
218218
eepromBackend.memory[9] = 0x22;
219219

220-
const runner = new TestProgramRunner(cpu, eeprom);
220+
const runner = new TestProgramRunner(cpu);
221221
runner.runInstructions(instructionCount);
222222

223223
expect(eepromBackend.memory[9]).toEqual(0xff);

0 commit comments

Comments
 (0)