Skip to content

Commit c029ed1

Browse files
committed
perf(cpu): speed up interrupts
code which makes heavy use of interrupts considerably slows down the simulator. E.g. that transmit programs large amount of data over SPI. See wokwi/wokwi-features#280 for an example.
1 parent 8c1f76f commit c029ed1

File tree

1 file changed

+28
-11
lines changed

1 file changed

+28
-11
lines changed

src/cpu/cpu.ts

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { u32, u16, u8, i16 } from '../types';
1010
import { avrInterrupt } from './interrupt';
1111

1212
const registerSpace = 0x100;
13+
const MAX_INTERRUPTS = 128; // Enough for ATMega2560
1314

1415
export type CPUMemoryHook = (value: u8, oldValue: u8, addr: u16, mask: u8) => boolean | void;
1516
export interface CPUMemoryHooks {
@@ -46,7 +47,7 @@ export class CPU {
4647
readonly progBytes = new Uint8Array(this.progMem.buffer);
4748
readonly readHooks: CPUMemoryReadHooks = [];
4849
readonly writeHooks: CPUMemoryHooks = [];
49-
private readonly pendingInterrupts: AVRInterruptConfig[] = [];
50+
private readonly pendingInterrupts: (AVRInterruptConfig | null)[] = new Array(MAX_INTERRUPTS);
5051
private nextClockEvent: AVRClockEventEntry | null = null;
5152
private readonly clockEventPool: AVRClockEventEntry[] = []; // helps avoid garbage collection
5253

@@ -77,6 +78,7 @@ export class CPU {
7778
cycles = 0;
7879

7980
nextInterrupt: i16 = -1;
81+
maxInterrupt: i16 = 0;
8082

8183
constructor(public progMem: Uint16Array, private sramBytes = 8192) {
8284
this.reset();
@@ -86,7 +88,7 @@ export class CPU {
8688
this.data.fill(0);
8789
this.SP = this.data.length - 1;
8890
this.pc = 0;
89-
this.pendingInterrupts.splice(0, this.pendingInterrupts.length);
91+
this.pendingInterrupts.fill(null);
9092
this.nextInterrupt = -1;
9193
this.nextClockEvent = null;
9294
}
@@ -124,10 +126,6 @@ export class CPU {
124126
return this.SREG & 0x80 ? true : false;
125127
}
126128

127-
private updateNextInterrupt() {
128-
this.nextInterrupt = this.pendingInterrupts.findIndex((item) => !!item);
129-
}
130-
131129
setInterruptFlag(interrupt: AVRInterruptConfig) {
132130
const { flagRegister, flagMask, enableRegister, enableMask } = interrupt;
133131
if (interrupt.inverseFlag) {
@@ -153,16 +151,34 @@ export class CPU {
153151
}
154152

155153
queueInterrupt(interrupt: AVRInterruptConfig) {
156-
this.pendingInterrupts[interrupt.address] = interrupt;
157-
this.updateNextInterrupt();
154+
const { address } = interrupt;
155+
this.pendingInterrupts[address] = interrupt;
156+
if (this.nextInterrupt === -1 || this.nextInterrupt > address) {
157+
this.nextInterrupt = address;
158+
}
159+
if (address > this.maxInterrupt) {
160+
this.maxInterrupt = address;
161+
}
158162
}
159163

160164
clearInterrupt({ address, flagRegister, flagMask }: AVRInterruptConfig, clearFlag = true) {
161-
delete this.pendingInterrupts[address];
162165
if (clearFlag) {
163166
this.data[flagRegister] &= ~flagMask;
164167
}
165-
this.updateNextInterrupt();
168+
const { pendingInterrupts, maxInterrupt } = this;
169+
if (!pendingInterrupts[address]) {
170+
return;
171+
}
172+
pendingInterrupts[address] = null;
173+
if (this.nextInterrupt === address) {
174+
this.nextInterrupt = -1;
175+
for (let i = address + 1; i <= maxInterrupt; i++) {
176+
if (pendingInterrupts[i]) {
177+
this.nextInterrupt = i;
178+
break;
179+
}
180+
}
181+
}
166182
}
167183

168184
clearInterruptByFlag(interrupt: AVRInterruptConfig, registerValue: number) {
@@ -241,7 +257,8 @@ export class CPU {
241257

242258
const { nextInterrupt } = this;
243259
if (this.interruptsEnabled && nextInterrupt >= 0) {
244-
const interrupt = this.pendingInterrupts[nextInterrupt];
260+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
261+
const interrupt = this.pendingInterrupts[nextInterrupt]!;
245262
avrInterrupt(this, interrupt.address);
246263
if (!interrupt.constant) {
247264
this.clearInterrupt(interrupt);

0 commit comments

Comments
 (0)