Skip to content

Commit

Permalink
feat: new clock system
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
urish committed Feb 22, 2024
1 parent 9773d0f commit e669aaf
Show file tree
Hide file tree
Showing 23 changed files with 397 additions and 352 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci-micropython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,4 @@ jobs:
- name: Create SPI test filesystem
run: python test/mklittlefs.py littlefs-spi.img test/micropython/main-spi.py
- name: Test Micropython SPI
run: timeout 10 npm run test:micropython-spi -- "hello world" "h" "0123456789abcdef0123456789abcdef0123456789abcdef"
run: timeout 30 npm run test:micropython-spi -- "hello world" "h" "0123456789abcdef0123456789abcdef0123456789abcdef"
2 changes: 1 addition & 1 deletion .github/workflows/ci-pico-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ jobs:
- name: Install NPM packages
run: npm ci
- name: Test "Hello World" example
run: timeout 10 npm run start:micropython -- --image ~/pico/pico-examples/build/hello_world/usb/hello_usb.uf2 --expect-text "Hello, world!"
run: timeout 60 npm run start:micropython -- --image ~/pico/pico-examples/build/hello_world/usb/hello_usb.uf2 --expect-text "Hello, world!"
13 changes: 7 additions & 6 deletions demo/emulator-run.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import * as fs from 'fs';
import { RP2040 } from '../src/index.js';
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
import { Simulator } from '../src/simulator.js';
import { bootromB1 } from './bootrom.js';
import { loadHex } from './intelhex.js';
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';

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

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

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

mcu.core.PC = 0x10000000;
mcu.execute();
simulator.rp2040.core.PC = 0x10000000;
simulator.execute();
17 changes: 9 additions & 8 deletions demo/micropython-run.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { RP2040 } from '../src/index.js';
import fs from 'fs';
import minimist from 'minimist';
import { GDBTCPServer } from '../src/gdb/gdb-tcp-server.js';
import { Simulator } from '../src/simulator.js';
import { USBCDC } from '../src/usb/cdc.js';
import { ConsoleLogger, LogLevel } from '../src/utils/logging.js';
import { bootromB1 } from './bootrom.js';
import { loadUF2, loadMicropythonFlashImage, loadCircuitpythonFlashImage } from './load-flash.js';
import fs from 'fs';
import minimist from 'minimist';
import { loadCircuitpythonFlashImage, loadMicropythonFlashImage, loadUF2 } from './load-flash.js';

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

const mcu = new RP2040();
const simulator = new Simulator();
const mcu = simulator.rp2040;
mcu.loadBootrom(bootromB1);
mcu.logger = new ConsoleLogger(LogLevel.Error);

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

if (args.gdb) {
const gdbServer = new GDBTCPServer(mcu, 3333);
const gdbServer = new GDBTCPServer(simulator, 3333);
console.log(`RP2040 GDB Server ready! Listening on port ${gdbServer.port}`);
}

Expand Down Expand Up @@ -89,5 +90,5 @@ process.stdin.on('data', (chunk) => {
}
});

mcu.core.PC = 0x10000000;
mcu.execute();
simulator.rp2040.core.PC = 0x10000000;
simulator.execute();
18 changes: 7 additions & 11 deletions src/clock/clock.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
export interface IClockTimer {
pause(currentMicros: number): void;
resume(currentMicros: number): void;
export type AlarmCallback = () => void;

export interface IAlarm {
schedule(deltaNanos: number): void;
cancel(): void;
}

export interface IClock {
readonly micros: number;

pause(): void;

resume(): void;

createTimer(deltaMicros: number, callback: () => void): IClockTimer;
readonly nanos: number;

deleteTimer(timer: IClockTimer): void;
createAlarm(callback: AlarmCallback): IAlarm;
}
55 changes: 3 additions & 52 deletions src/clock/mock-clock.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,7 @@
import { IClock, IClockTimer } from './clock.js';

export class MockClockTimer implements IClockTimer {
constructor(
readonly micros: number,
readonly callback: () => void,
) {}

pause() {
/* intentionally empty */
}

resume() {
/* intentionally empty */
}
}

export class MockClock implements IClock {
micros: number = 0;

readonly timers: MockClockTimer[] = [];

pause() {
/* intentionally empty */
}

resume() {
/* intentionally empty */
}
import { SimulationClock } from './simulation-clock.js';

export class MockClock extends SimulationClock {
advance(deltaMicros: number) {
const { timers } = this;
const targetTime = this.micros + Math.max(deltaMicros, 0.01);
while (timers[0] && timers[0].micros <= targetTime) {
const timer = timers.shift();
if (timer) {
this.micros = timer.micros;
timer.callback();
}
}
}

createTimer(deltaMicros: number, callback: () => void) {
const timer = new MockClockTimer(this.micros + deltaMicros, callback);
this.timers.push(timer);
this.timers.sort((a, b) => a.micros - b.micros);
return timer;
}

deleteTimer(timer: IClockTimer) {
const timerIndex = this.timers.indexOf(timer as MockClockTimer);
if (timerIndex >= 0) {
this.timers.splice(timerIndex, 1);
}
this.tick(this.nanos + deltaMicros * 1000);
}
}
79 changes: 0 additions & 79 deletions src/clock/realtime-clock.ts

This file was deleted.

105 changes: 105 additions & 0 deletions src/clock/simulation-clock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { AlarmCallback, IAlarm, IClock } from './clock.js';

type ClockEventCallback = () => void;

export class ClockAlarm implements IAlarm {
next: ClockAlarm | null = null;
nanos: number = 0;
scheduled = false;

constructor(
private readonly clock: SimulationClock,
readonly callback: AlarmCallback,
) {}

schedule(deltaNanos: number): void {
if (this.scheduled) {
this.cancel();
}
this.clock.linkAlarm(deltaNanos, this);
}

cancel(): void {
this.clock.unlinkAlarm(this);
this.scheduled = false;
}
}

export class SimulationClock implements IClock {
private nextAlarm: ClockAlarm | null = null;

private nanosCounter = 0;

constructor(readonly frequency = 125e6) {}

get nanos() {
return this.nanosCounter;
}

get micros() {
return this.nanos / 1000;
}

createAlarm(callback: ClockEventCallback) {
return new ClockAlarm(this, callback);
}

linkAlarm(nanos: number, alarm: ClockAlarm) {
alarm.nanos = this.nanos + nanos;
let alarmListItem = this.nextAlarm;
let lastItem = null;
while (alarmListItem && alarmListItem.nanos < nanos) {
lastItem = alarmListItem;
alarmListItem = alarmListItem.next;
}
if (lastItem) {
lastItem.next = alarm;
alarm.next = alarmListItem;
} else {
this.nextAlarm = alarm;
alarm.next = alarmListItem;
}
alarm.scheduled = true;
return alarm;
}

unlinkAlarm(alarm: ClockAlarm) {
let alarmListItem = this.nextAlarm;
if (!alarmListItem) {
return false;
}
let lastItem = null;
while (alarmListItem) {
if (alarmListItem === alarm) {
if (lastItem) {
lastItem.next = alarmListItem.next;
} else {
this.nextAlarm = alarmListItem.next;
}
return true;
}
lastItem = alarmListItem;
alarmListItem = alarmListItem.next;
}
return false;
}

tick(deltaNanos: number) {
const targetNanos = this.nanosCounter + deltaNanos;
let alarm = this.nextAlarm;
while (alarm && alarm.nanos <= targetNanos) {
this.nextAlarm = alarm.next;
this.nanosCounter = alarm.nanos;
alarm.callback();
alarm = this.nextAlarm;
}
this.nanosCounter = targetNanos;
}

get nanosToNextAlarm() {
if (this.nextAlarm) {
return this.nextAlarm.nanos - this.nanos;
}
return 0;
}
}
Loading

0 comments on commit e669aaf

Please sign in to comment.