Skip to content

Commit 02bc892

Browse files
authored
feat: new clock system (#137)
The simulator clock was previously tied to the host system time. This causes inconsistency across simulation sessions, and also meant that the internal timer accuracy was low, as the timer resolution was limited by the host OS scheduling. This changes the simulator clock to run independently from the system clock, and increases the internal clock resolution from microseconds to nanosecond. It also introduces a new `Simulator` class, which manages the execution of the simulated core (previously, this functionality has been part of the `RP2040` class).
1 parent 9773d0f commit 02bc892

21 files changed

+405
-359
lines changed

demo/emulator-run.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import * as fs from 'fs';
2-
import { RP2040 } from '../src/index.js';
2+
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
3+
import { Simulator } from '../src/simulator.js';
34
import { bootromB1 } from './bootrom.js';
45
import { loadHex } from './intelhex.js';
5-
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
66

77
// Create an array with the compiled code of blink
88
// Execute the instructions from this array, one by one.
99
const hex = fs.readFileSync('hello_uart.hex', 'utf-8');
10-
const mcu = new RP2040();
10+
const simulator = new Simulator();
11+
const mcu = simulator.rp2040;
1112
mcu.loadBootrom(bootromB1);
1213
loadHex(hex, mcu.flash, 0x10000000);
1314

14-
const gdbServer = new GDBTCPServer(mcu, 3333);
15+
const gdbServer = new GDBTCPServer(simulator, 3333);
1516
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
1617

1718
mcu.uart[0].onByte = (value) => {
1819
process.stdout.write(new Uint8Array([value]));
1920
};
2021

21-
mcu.core.PC = 0x10000000;
22-
mcu.execute();
22+
simulator.rp2040.core.PC = 0x10000000;
23+
simulator.execute();

demo/micropython-run.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { RP2040 } from '../src/index.js';
1+
import fs from 'fs';
2+
import minimist from 'minimist';
23
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
4+
import { Simulator } from '../src/simulator.js';
35
import { USBCDC } from '../src/usb/cdc.js';
46
import { ConsoleLogger, LogLevel } from '../src/utils/logging.js';
57
import { bootromB1 } from './bootrom.js';
6-
import { loadUF2, loadMicropythonFlashImage, loadCircuitpythonFlashImage } from './load-flash.js';
7-
import fs from 'fs';
8-
import minimist from 'minimist';
8+
import { loadCircuitpythonFlashImage, loadMicropythonFlashImage, loadUF2 } from './load-flash.js';
99

1010
const args = minimist(process.argv.slice(2), {
1111
string: [
@@ -19,7 +19,8 @@ const args = minimist(process.argv.slice(2), {
1919
});
2020
const expectText = args['expect-text'];
2121

22-
const mcu = new RP2040();
22+
const simulator = new Simulator();
23+
const mcu = simulator.rp2040;
2324
mcu.loadBootrom(bootromB1);
2425
mcu.logger = new ConsoleLogger(LogLevel.Error);
2526

@@ -42,7 +43,7 @@ if (fs.existsSync('littlefs.img') && !args.circuitpython) {
4243
}
4344

4445
if (args.gdb) {
45-
const gdbServer = new GDBTCPServer(mcu, 3333);
46+
const gdbServer = new GDBTCPServer(simulator, 3333);
4647
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
4748
}
4849

@@ -89,5 +90,5 @@ process.stdin.on('data', (chunk) => {
8990
}
9091
});
9192

92-
mcu.core.PC = 0x10000000;
93-
mcu.execute();
93+
simulator.rp2040.core.PC = 0x10000000;
94+
simulator.execute();

src/clock/clock.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
export interface IClockTimer {
2-
pause(currentMicros: number): void;
3-
resume(currentMicros: number): void;
1+
export type AlarmCallback = () => void;
2+
3+
export interface IAlarm {
4+
schedule(deltaNanos: number): void;
5+
cancel(): void;
46
}
57

68
export interface IClock {
7-
readonly micros: number;
8-
9-
pause(): void;
10-
11-
resume(): void;
12-
13-
createTimer(deltaMicros: number, callback: () => void): IClockTimer;
9+
readonly nanos: number;
1410

15-
deleteTimer(timer: IClockTimer): void;
11+
createAlarm(callback: AlarmCallback): IAlarm;
1612
}

src/clock/mock-clock.ts

Lines changed: 3 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,7 @@
1-
import { IClock, IClockTimer } from './clock.js';
2-
3-
export class MockClockTimer implements IClockTimer {
4-
constructor(
5-
readonly micros: number,
6-
readonly callback: () => void,
7-
) {}
8-
9-
pause() {
10-
/* intentionally empty */
11-
}
12-
13-
resume() {
14-
/* intentionally empty */
15-
}
16-
}
17-
18-
export class MockClock implements IClock {
19-
micros: number = 0;
20-
21-
readonly timers: MockClockTimer[] = [];
22-
23-
pause() {
24-
/* intentionally empty */
25-
}
26-
27-
resume() {
28-
/* intentionally empty */
29-
}
1+
import { SimulationClock } from './simulation-clock.js';
302

3+
export class MockClock extends SimulationClock {
314
advance(deltaMicros: number) {
32-
const { timers } = this;
33-
const targetTime = this.micros + Math.max(deltaMicros, 0.01);
34-
while (timers[0] && timers[0].micros <= targetTime) {
35-
const timer = timers.shift();
36-
if (timer) {
37-
this.micros = timer.micros;
38-
timer.callback();
39-
}
40-
}
41-
}
42-
43-
createTimer(deltaMicros: number, callback: () => void) {
44-
const timer = new MockClockTimer(this.micros + deltaMicros, callback);
45-
this.timers.push(timer);
46-
this.timers.sort((a, b) => a.micros - b.micros);
47-
return timer;
48-
}
49-
50-
deleteTimer(timer: IClockTimer) {
51-
const timerIndex = this.timers.indexOf(timer as MockClockTimer);
52-
if (timerIndex >= 0) {
53-
this.timers.splice(timerIndex, 1);
54-
}
5+
this.tick(this.nanos + deltaMicros * 1000);
556
}
567
}

src/clock/realtime-clock.ts

Lines changed: 0 additions & 79 deletions
This file was deleted.

src/clock/simulation-clock.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { AlarmCallback, IAlarm, IClock } from './clock.js';
2+
3+
type ClockEventCallback = () => void;
4+
5+
export class ClockAlarm implements IAlarm {
6+
next: ClockAlarm | null = null;
7+
nanos: number = 0;
8+
scheduled = false;
9+
10+
constructor(
11+
private readonly clock: SimulationClock,
12+
readonly callback: AlarmCallback,
13+
) {}
14+
15+
schedule(deltaNanos: number): void {
16+
if (this.scheduled) {
17+
this.cancel();
18+
}
19+
this.clock.linkAlarm(deltaNanos, this);
20+
}
21+
22+
cancel(): void {
23+
this.clock.unlinkAlarm(this);
24+
this.scheduled = false;
25+
}
26+
}
27+
28+
export class SimulationClock implements IClock {
29+
private nextAlarm: ClockAlarm | null = null;
30+
31+
private nanosCounter = 0;
32+
33+
constructor(readonly frequency = 125e6) {}
34+
35+
get nanos() {
36+
return this.nanosCounter;
37+
}
38+
39+
get micros() {
40+
return this.nanos / 1000;
41+
}
42+
43+
createAlarm(callback: ClockEventCallback) {
44+
return new ClockAlarm(this, callback);
45+
}
46+
47+
linkAlarm(nanos: number, alarm: ClockAlarm) {
48+
alarm.nanos = this.nanos + nanos;
49+
let alarmListItem = this.nextAlarm;
50+
let lastItem = null;
51+
while (alarmListItem && alarmListItem.nanos < alarm.nanos) {
52+
lastItem = alarmListItem;
53+
alarmListItem = alarmListItem.next;
54+
}
55+
if (lastItem) {
56+
lastItem.next = alarm;
57+
alarm.next = alarmListItem;
58+
} else {
59+
this.nextAlarm = alarm;
60+
alarm.next = alarmListItem;
61+
}
62+
alarm.scheduled = true;
63+
return alarm;
64+
}
65+
66+
unlinkAlarm(alarm: ClockAlarm) {
67+
let alarmListItem = this.nextAlarm;
68+
if (!alarmListItem) {
69+
return false;
70+
}
71+
let lastItem = null;
72+
while (alarmListItem) {
73+
if (alarmListItem === alarm) {
74+
if (lastItem) {
75+
lastItem.next = alarmListItem.next;
76+
} else {
77+
this.nextAlarm = alarmListItem.next;
78+
}
79+
return true;
80+
}
81+
lastItem = alarmListItem;
82+
alarmListItem = alarmListItem.next;
83+
}
84+
return false;
85+
}
86+
87+
tick(deltaNanos: number) {
88+
const targetNanos = this.nanosCounter + deltaNanos;
89+
let alarm = this.nextAlarm;
90+
while (alarm && alarm.nanos <= targetNanos) {
91+
this.nextAlarm = alarm.next;
92+
this.nanosCounter = alarm.nanos;
93+
alarm.callback();
94+
alarm = this.nextAlarm;
95+
}
96+
this.nanosCounter = targetNanos;
97+
}
98+
99+
get nanosToNextAlarm() {
100+
if (this.nextAlarm) {
101+
return this.nextAlarm.nanos - this.nanos;
102+
}
103+
return 0;
104+
}
105+
}

0 commit comments

Comments
 (0)