From c6456a2a5ecc9a8ecacd60beb3acc68168ee2844 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Sat, 16 Dec 2023 22:02:09 +0200 Subject: [PATCH] feat: new clock system 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). --- demo/emulator-run.ts | 13 +++-- demo/micropython-run.ts | 17 +++--- src/clock/clock.ts | 18 +++--- src/clock/mock-clock.ts | 55 +----------------- src/clock/realtime-clock.ts | 79 ------------------------- src/clock/simulation-clock.ts | 105 ++++++++++++++++++++++++++++++++++ src/cortex-m0-core.ts | 79 +++++++++++++------------ src/gdb/gdb-connection.ts | 4 +- src/gdb/gdb-server.ts | 25 ++++---- src/gdb/gdb-target.ts | 9 +++ src/gdb/gdb-tcp-server.ts | 6 +- src/index.ts | 7 ++- src/peripherals/adc.ts | 30 ++++++---- src/peripherals/dma.ts | 23 +++----- src/peripherals/rtc.ts | 6 +- src/peripherals/timer.ts | 42 ++++++++------ src/peripherals/usb.ts | 97 ++++++++++++++++++++----------- src/rp2040.ts | 34 +---------- src/simulator.ts | 47 +++++++++++++++ src/utils/timer32.ts | 34 ++++++----- test/micropython-spi-test.ts | 13 +++-- 21 files changed, 394 insertions(+), 349 deletions(-) delete mode 100644 src/clock/realtime-clock.ts create mode 100644 src/clock/simulation-clock.ts create mode 100644 src/gdb/gdb-target.ts create mode 100644 src/simulator.ts diff --git a/demo/emulator-run.ts b/demo/emulator-run.ts index 55fe8e7..e936f4c 100644 --- a/demo/emulator-run.ts +++ b/demo/emulator-run.ts @@ -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(); diff --git a/demo/micropython-run.ts b/demo/micropython-run.ts index 3334da5..2eed868 100644 --- a/demo/micropython-run.ts +++ b/demo/micropython-run.ts @@ -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: [ @@ -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); @@ -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}`); } @@ -89,5 +90,5 @@ process.stdin.on('data', (chunk) => { } }); -mcu.core.PC = 0x10000000; -mcu.execute(); +simulator.rp2040.core.PC = 0x10000000; +simulator.execute(); diff --git a/src/clock/clock.ts b/src/clock/clock.ts index f94ac17..b5969ee 100644 --- a/src/clock/clock.ts +++ b/src/clock/clock.ts @@ -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; } diff --git a/src/clock/mock-clock.ts b/src/clock/mock-clock.ts index bd9e286..e07e285 100644 --- a/src/clock/mock-clock.ts +++ b/src/clock/mock-clock.ts @@ -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); } } diff --git a/src/clock/realtime-clock.ts b/src/clock/realtime-clock.ts deleted file mode 100644 index 1102280..0000000 --- a/src/clock/realtime-clock.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { getCurrentMicroseconds } from '../utils/time.js'; -import { IClock, IClockTimer } from './clock.js'; - -export class ClockTimer implements IClockTimer { - private jsTimer: NodeJS.Timeout | null = null; - private timeLeft: number = this.micros; - - constructor( - private micros: number, - private callback: () => void, - ) {} - - schedule(currentMicros: number) { - this.jsTimer = setTimeout(this.callback, (this.micros - currentMicros) / 1000); - } - - unschedule() { - if (this.jsTimer) { - clearTimeout(this.jsTimer); - this.jsTimer = null; - } - } - - pause(currentMicros: number) { - this.timeLeft = this.micros - currentMicros; - this.unschedule(); - } - - resume(currentMicros: number) { - this.micros = currentMicros + this.timeLeft; - this.schedule(currentMicros); - } -} - -export class RealtimeClock implements IClock { - baseTime: number = 0; - pauseTime: number = 0; - paused = true; - timers = new Set(); - - pause() { - if (!this.paused) { - for (const timer of this.timers) { - timer.pause(this.micros); - } - this.pauseTime = this.micros; - this.paused = true; - } - } - - resume() { - if (this.paused) { - this.baseTime = getCurrentMicroseconds() - this.pauseTime; - this.paused = false; - for (const timer of this.timers) { - timer.resume(this.micros); - } - } - } - - createTimer(deltaMicros: number, callback: () => void) { - const timer = new ClockTimer(this.micros + deltaMicros, () => { - this.timers.delete(timer); - callback(); - }); - timer.schedule(this.micros); - this.timers.add(timer); - return timer; - } - - deleteTimer(timer: ClockTimer) { - timer.unschedule(); - this.timers.delete(timer); - } - - get micros() { - return getCurrentMicroseconds() - this.baseTime; - } -} diff --git a/src/clock/simulation-clock.ts b/src/clock/simulation-clock.ts new file mode 100644 index 0000000..0c57613 --- /dev/null +++ b/src/clock/simulation-clock.ts @@ -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; + } +} diff --git a/src/cortex-m0-core.ts b/src/cortex-m0-core.ts index 96e3d8a..24014c9 100644 --- a/src/cortex-m0-core.ts +++ b/src/cortex-m0-core.ts @@ -1,5 +1,5 @@ import { MAX_HARDWARE_IRQ } from './irq.js'; -import { RP2040, APB_START_ADDRESS, SIO_START_ADDRESS } from './rp2040.js'; +import { APB_START_ADDRESS, RP2040, SIO_START_ADDRESS } from './rp2040.js'; /* eslint-disable @typescript-eslint/no-unused-vars */ const EXC_RESET = 1; @@ -597,7 +597,7 @@ export class CortexM0Core { const wideInstruction = opcode >> 12 === 0b1111 || opcode >> 11 === 0b11101; const opcode2 = wideInstruction ? this.readUint16(opcodePC + 2) : 0; this.PC += 2; - this.cycles++; + let deltaCycles = 1; // ADCS if (opcode >> 6 === 0b0100000101) { const Rm = (opcode >> 3) & 0x7; @@ -649,7 +649,7 @@ export class CortexM0Core { this.registers[Rdn] = result; } else if (Rdn === pcRegister) { this.registers[Rdn] = result & ~0x1; - this.cycles++; + deltaCycles++; } else if (Rdn === spRegister) { this.registers[Rdn] = result & ~0x3; } @@ -703,7 +703,7 @@ export class CortexM0Core { } if (this.checkCondition(cond)) { this.PC += imm8 + 2; - this.cycles++; + deltaCycles++; } } // B @@ -713,7 +713,7 @@ export class CortexM0Core { imm11 = (imm11 & 0x7ff) - 0x800; } this.PC += imm11 + 2; - this.cycles++; + deltaCycles++; } // BICS else if (opcode >> 6 === 0b0100001110) { @@ -742,7 +742,7 @@ export class CortexM0Core { ((S ? 0b11111111 : 0) << 24) | ((I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1)); this.LR = (this.PC + 2) | 0x1; this.PC += 2 + imm32; - this.cycles += 2; + deltaCycles += 2; this.blTaken(this, false); } // BLX @@ -750,14 +750,14 @@ export class CortexM0Core { const Rm = (opcode >> 3) & 0xf; this.LR = this.PC | 0x1; this.PC = this.registers[Rm] & ~1; - this.cycles++; + deltaCycles++; this.blTaken(this, true); } // BX else if (opcode >> 7 === 0b010001110 && (opcode & 0x7) === 0) { const Rm = (opcode >> 3) & 0xf; this.BXWritePC(this.registers[Rm]); - this.cycles++; + deltaCycles++; } // CMN (register) else if (opcode >> 6 === 0b0100001011) { @@ -795,12 +795,12 @@ export class CortexM0Core { // DMB SY else if (opcode === 0xf3bf && (opcode2 & 0xfff0) === 0x8f50) { this.PC += 2; - this.cycles += 2; + deltaCycles += 2; } // DSB SY else if (opcode === 0xf3bf && (opcode2 & 0xfff0) === 0x8f40) { this.PC += 2; - this.cycles += 2; + deltaCycles += 2; } // EORS else if (opcode >> 6 === 0b0100000001) { @@ -814,7 +814,7 @@ export class CortexM0Core { // ISB SY else if (opcode === 0xf3bf && (opcode2 & 0xfff0) === 0x8f60) { this.PC += 2; - this.cycles += 2; + deltaCycles += 2; } // LDMIA else if (opcode >> 11 === 0b11001) { @@ -825,7 +825,7 @@ export class CortexM0Core { if (registers & (1 << i)) { this.registers[i] = this.readUint32(address); address += 4; - this.cycles++; + deltaCycles++; } } // Write back @@ -839,7 +839,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rn] + imm5; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint32(addr); } // LDR (sp + immediate) @@ -847,7 +847,7 @@ export class CortexM0Core { const Rt = (opcode >> 8) & 0x7; const imm8 = opcode & 0xff; const addr = this.SP + (imm8 << 2); - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint32(addr); } // LDR (literal) @@ -856,7 +856,7 @@ export class CortexM0Core { const Rt = (opcode >> 8) & 7; const nextPC = this.PC + 2; const addr = (nextPC & 0xfffffffc) + imm8; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint32(addr); } // LDR (register) @@ -865,7 +865,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint32(addr); } // LDRB (immediate) @@ -874,7 +874,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rn] + imm5; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint8(addr); } // LDRB (register) @@ -883,7 +883,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint8(addr); } // LDRH (immediate) @@ -892,7 +892,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rn] + (imm5 << 1); - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint16(addr); } // LDRH (register) @@ -901,7 +901,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = this.readUint16(addr); } // LDRSB @@ -910,7 +910,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = signExtend8(this.readUint8(addr)); } // LDRSH @@ -919,7 +919,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const addr = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(addr); + deltaCycles += this.cyclesIO(addr); this.registers[Rt] = signExtend16(this.readUint16(addr)); } // LSLS (immediate) @@ -976,7 +976,7 @@ export class CortexM0Core { const Rd = ((opcode >> 4) & 0x8) | (opcode & 0x7); let value = Rm === pcRegister ? this.PC + 2 : this.registers[Rm]; if (Rd === pcRegister) { - this.cycles++; + deltaCycles++; value &= ~1; } else if (Rd === spRegister) { value &= ~3; @@ -997,7 +997,7 @@ export class CortexM0Core { const Rd = (opcode2 >> 8) & 0xf; this.registers[Rd] = this.readSpecialRegister(SYSm); this.PC += 2; - this.cycles += 2; + deltaCycles += 2; } // MSR else if (opcode >> 4 === 0b111100111000 && opcode2 >> 8 == 0b10001000) { @@ -1005,7 +1005,7 @@ export class CortexM0Core { const Rn = opcode & 0xf; this.writeSpecialRegister(SYSm, this.registers[Rn]); this.PC += 2; - this.cycles += 2; + deltaCycles += 2; } // MULS else if (opcode >> 6 === 0b0100001101) { @@ -1042,13 +1042,13 @@ export class CortexM0Core { if (opcode & (1 << i)) { this.registers[i] = this.readUint32(address); address += 4; - this.cycles++; + deltaCycles++; } } if (P) { this.SP = address + 4; this.BXWritePC(this.readUint32(address)); - this.cycles += 2; + deltaCycles += 2; } else { this.SP = address; } @@ -1065,7 +1065,7 @@ export class CortexM0Core { for (let i = 0; i <= 7; i++) { if (opcode & (1 << i)) { this.writeUint32(address, this.registers[i]); - this.cycles++; + deltaCycles++; address += 4; } } @@ -1147,7 +1147,7 @@ export class CortexM0Core { if (registers & (1 << i)) { this.writeUint32(address, this.registers[i]); address += 4; - this.cycles++; + deltaCycles++; } } // Write back @@ -1161,7 +1161,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const address = this.registers[Rn] + imm5; - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint32(address, this.registers[Rt]); } // STR (sp + immediate) @@ -1169,7 +1169,7 @@ export class CortexM0Core { const Rt = (opcode >> 8) & 0x7; const imm8 = opcode & 0xff; const address = this.SP + (imm8 << 2); - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint32(address, this.registers[Rt]); } // STR (register) @@ -1178,7 +1178,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const address = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint32(address, this.registers[Rt]); } // STRB (immediate) @@ -1187,7 +1187,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const address = this.registers[Rn] + imm5; - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint8(address, this.registers[Rt]); } // STRB (register) @@ -1196,7 +1196,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const address = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint8(address, this.registers[Rt]); } // STRH (immediate) @@ -1205,7 +1205,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const address = this.registers[Rn] + imm5; - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint16(address, this.registers[Rt]); } // STRH (register) @@ -1214,7 +1214,7 @@ export class CortexM0Core { const Rn = (opcode >> 3) & 0x7; const Rt = opcode & 0x7; const address = this.registers[Rm] + this.registers[Rn]; - this.cycles += this.cyclesIO(address, true); + deltaCycles += this.cyclesIO(address, true); this.writeUint16(address, this.registers[Rt]); } // SUB (SP minus immediate) @@ -1295,7 +1295,7 @@ export class CortexM0Core { } // WFE else if (opcode === 0b1011111100100000) { - this.cycles++; + deltaCycles++; if (this.eventRegistered) { this.eventRegistered = false; } else { @@ -1304,7 +1304,7 @@ export class CortexM0Core { } // WFI else if (opcode === 0b1011111100110000) { - this.cycles++; + deltaCycles++; this.waiting = true; } // YIELD @@ -1318,5 +1318,8 @@ export class CortexM0Core { ); this.logger.warn(LOG_NAME, `Opcode: 0x${opcode.toString(16)} (0x${opcode2.toString(16)})`); } + + this.cycles += deltaCycles; + return deltaCycles; } } diff --git a/src/gdb/gdb-connection.ts b/src/gdb/gdb-connection.ts index 7518458..b092d92 100644 --- a/src/gdb/gdb-connection.ts +++ b/src/gdb/gdb-connection.ts @@ -4,7 +4,7 @@ import { gdbChecksum, gdbMessage } from './gdb-utils.js'; export type GDBResponseHandler = (value: string) => void; export class GDBConnection { - readonly rp2040 = this.server.rp2040; + readonly target = this.server.target; private buf = ''; constructor( @@ -19,7 +19,7 @@ export class GDBConnection { const { onResponse } = this; if (data.charCodeAt(0) === 3) { this.server.info('BREAK'); - this.rp2040.stop(); + this.target.stop(); onResponse(gdbMessage(STOP_REPLY_SIGINT)); data = data.slice(1); } diff --git a/src/gdb/gdb-server.ts b/src/gdb/gdb-server.ts index 353ebda..472e89a 100644 --- a/src/gdb/gdb-server.ts +++ b/src/gdb/gdb-server.ts @@ -5,9 +5,9 @@ */ import { SYSM_CONTROL, SYSM_MSP, SYSM_PRIMASK, SYSM_PSP } from '../cortex-m0-core.js'; -import { RP2040 } from '../rp2040.js'; -import { ConsoleLogger, Logger, LogLevel } from '../utils/logging.js'; +import { ConsoleLogger, LogLevel, Logger } from '../utils/logging.js'; import { GDBConnection } from './gdb-connection.js'; +import { IGDBTarget } from './gdb-target.js'; import { decodeHexBuf, encodeHexBuf, @@ -82,10 +82,10 @@ export class GDBServer { private readonly connections = new Set(); - constructor(readonly rp2040: RP2040) {} + constructor(readonly target: IGDBTarget) {} processGDBMessage(cmd: string) { - const { rp2040 } = this; + const { rp2040 } = this.target; const { core } = rp2040; if (cmd === 'Hg0') { return gdbMessage('OK'); @@ -128,8 +128,8 @@ export class GDBServer { return gdbMessage('vCont;c;C;s;S'); } if (cmd.startsWith('vCont;c')) { - if (!rp2040.executing) { - rp2040.execute(); + if (!this.target.executing) { + this.target.execute(); } return; } @@ -145,8 +145,8 @@ export class GDBServer { break; case 'c': - if (!rp2040.executing) { - rp2040.execute(); + if (!this.target.executing) { + this.target.execute(); } return gdbMessage('OK'); @@ -189,7 +189,7 @@ export class GDBServer { case 'P': { // Write register - const params = cmd.substr(1).split('='); + const params = cmd.substring(1).split('='); const registerIndex = parseInt(params[0], 16); const registerValue = params[1].trim(); const registerBytes = registerIndex > 0x12 ? 1 : 4; @@ -259,10 +259,11 @@ export class GDBServer { } addConnection(connection: GDBConnection) { + const { rp2040 } = this.target; this.connections.add(connection); - this.rp2040.onBreak = () => { - this.rp2040.stop(); - this.rp2040.core.PC -= this.rp2040.core.breakRewind; + rp2040.onBreak = () => { + this.target.stop(); + rp2040.core.PC -= rp2040.core.breakRewind; for (const connection of this.connections) { connection.onBreakpoint(); } diff --git a/src/gdb/gdb-target.ts b/src/gdb/gdb-target.ts new file mode 100644 index 0000000..5f149cb --- /dev/null +++ b/src/gdb/gdb-target.ts @@ -0,0 +1,9 @@ +import { RP2040 } from '../rp2040.js'; + +export interface IGDBTarget { + readonly executing: boolean; + rp2040: RP2040; + + execute(): void; + stop(): void; +} diff --git a/src/gdb/gdb-tcp-server.ts b/src/gdb/gdb-tcp-server.ts index 72f92f4..43542ea 100644 --- a/src/gdb/gdb-tcp-server.ts +++ b/src/gdb/gdb-tcp-server.ts @@ -1,16 +1,16 @@ import { createServer, Socket } from 'net'; import { GDBConnection } from './gdb-connection.js'; import { GDBServer } from './gdb-server.js'; -import { RP2040 } from '../rp2040.js'; +import { IGDBTarget } from './gdb-target.js'; export class GDBTCPServer extends GDBServer { private socketServer = createServer(); constructor( - rp2040: RP2040, + target: IGDBTarget, readonly port: number = 3333, ) { - super(rp2040); + super(target); this.socketServer.listen(port); this.socketServer.on('connection', (socket) => this.handleConnection(socket)); } diff --git a/src/index.ts b/src/index.ts index e4caf15..e46a0f2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,18 +1,19 @@ export { GDBConnection } from './gdb/gdb-connection.js'; export { GDBServer } from './gdb/gdb-server.js'; export { GPIOPin, GPIOPinState } from './gpio-pin.js'; +export { I2CMode, I2CSpeed, RPI2C } from './peripherals/i2c.js'; export { BasePeripheral, type Peripheral } from './peripherals/peripheral.js'; -export { RPI2C, I2CSpeed, I2CMode } from './peripherals/i2c.js'; export { RPUSBController } from './peripherals/usb.js'; export { RP2040 } from './rp2040.js'; +export { Simulator } from './simulator.js'; export { USBCDC } from './usb/cdc.js'; export { DataDirection, DescriptorType, - type ISetupPacketParams, SetupRecipient, SetupRequest, SetupType, + type ISetupPacketParams, } from './usb/interfaces.js'; export { createSetupPacket, @@ -20,4 +21,4 @@ export { setDeviceAddressPacket, setDeviceConfigurationPacket, } from './usb/setup.js'; -export { ConsoleLogger, type Logger, LogLevel } from './utils/logging.js'; +export { ConsoleLogger, LogLevel, type Logger } from './utils/logging.js'; diff --git a/src/peripherals/adc.ts b/src/peripherals/adc.ts index 0f335b7..a6aa487 100644 --- a/src/peripherals/adc.ts +++ b/src/peripherals/adc.ts @@ -84,16 +84,15 @@ export class RPADC extends BasePeripheral implements Peripheral { * Invoked whenever the emulated code performs an ADC read. * * The default implementation reads the result from the `channelValues` array, and then calls - * completeADCRead() after `sampleTime` milliseconds. + * completeADCRead() after `sampleTime` microseconds. * * If you override the default implementation, make sure to call `completeADCRead()` after - * `sampleTime` milliseconds (or else the ADC read will never complete). + * `sampleTime` microseconds (or else the ADC read will never complete). */ onADCRead: (channel: number) => void = (channel) => { // Default implementation - this.rp2040.clock.createTimer(this.sampleTime, () => - this.completeADCRead(this.channelValues[channel], false), - ); + this.currentChannel = channel; + this.sampleAlarm.schedule(this.sampleTime * 1000); }; readonly fifo = new FIFO(4); @@ -111,6 +110,13 @@ export class RPADC extends BasePeripheral implements Peripheral { busy = false; err = false; + currentChannel = 0; + /** Used to simulate ADC sample time */ + sampleAlarm; + + /** For scheduling multi-shot ADC capture */ + multiShotAlarm; + get temperatueEnable() { return this.cs & CS_TS_EN; } @@ -147,6 +153,14 @@ export class RPADC extends BasePeripheral implements Peripheral { constructor(rp2040: RP2040, name: string) { super(rp2040, name); + this.sampleAlarm = this.rp2040.clock.createAlarm(() => + this.completeADCRead(this.channelValues[this.currentChannel], false), + ); + this.multiShotAlarm = this.rp2040.clock.createAlarm(() => { + if (this.cs & CS_START_MANY) { + this.startADCRead(); + } + }); } checkInterrupts() { @@ -213,11 +227,7 @@ export class RPADC extends BasePeripheral implements Peripheral { if (this.divider > sampleTicks) { // clock runs at 48MHz, subtract 2uS const micros = (this.divider - sampleTicks) / clockMHZ; - this.rp2040.clock.createTimer(micros, () => { - if (this.cs & CS_START_MANY) { - this.startADCRead(); - } - }); + this.multiShotAlarm.schedule(micros * 1000); } else { this.startADCRead(); } diff --git a/src/peripherals/dma.ts b/src/peripherals/dma.ts index 1769412..ad91002 100644 --- a/src/peripherals/dma.ts +++ b/src/peripherals/dma.ts @@ -1,4 +1,3 @@ -import { IClockTimer } from '../clock/clock.js'; import { IRQ } from '../irq.js'; import { RP2040 } from '../rp2040.js'; import { BasePeripheral, Peripheral } from './peripheral.js'; @@ -132,13 +131,14 @@ export class RPDMAChannel { private chainTo = 0; private ringMask = 0; private transferFn: () => void = () => 0; - private transferTimer: IClockTimer | null = null; + private transferAlarm; constructor( readonly dma: RPDMA, readonly rp2040: RP2040, readonly index: number, ) { + this.transferAlarm = rp2040.clock.createAlarm(this.transfer); this.reset(); } @@ -196,7 +196,6 @@ export class RPDMAChannel { transfer = () => { const { ctrl, dataSize, ringMask } = this; - this.transferTimer = null; this.transferFn(); if (ctrl & INCR_READ) { if (ringMask && !(ctrl & RING_SEL)) { @@ -228,26 +227,19 @@ export class RPDMAChannel { }; scheduleTransfer() { - if (this.transferTimer) { - // Already scheduled; do nothing. - return; - } if (this.dma.dreq[this.treqValue] || this.treqValue === TREQ.Permanent) { - this.transferTimer = this.rp2040.clock.createTimer(0, this.transfer); + this.transferAlarm.schedule(0); } else { const delay = this.dma.getTimer(this.treqValue); if (delay) { - this.transferTimer = this.rp2040.clock.createTimer(delay, this.transfer); + this.transferAlarm.schedule(delay * 1000); } } } abort() { this.ctrl &= ~BUSY; - if (this.transferTimer) { - this.rp2040.clock.deleteTimer(this.transferTimer); - this.transferTimer = null; - } + this.transferAlarm.cancel(); } readUint32(offset: number) { @@ -336,9 +328,8 @@ export class RPDMAChannel { if (this.ctrl & EN && this.ctrl & BUSY) { this.scheduleTransfer(); } - if (!(this.ctrl & EN) && this.transferTimer) { - this.rp2040.clock.deleteTimer(this.transferTimer); - this.transferTimer = null; + if (!(this.ctrl & EN)) { + this.transferAlarm.cancel(); } break; } diff --git a/src/peripherals/rtc.ts b/src/peripherals/rtc.ts index c703442..2c60aa9 100644 --- a/src/peripherals/rtc.ts +++ b/src/peripherals/rtc.ts @@ -48,11 +48,11 @@ export class RP2040RTC extends BasePeripheral implements Peripheral { setup1 = 0; ctrl = 0; baseline = new Date(2021, 0, 1); - baselineMicros = 0; + baselineNanos = 0; readUint32(offset: number) { const date = new Date( - this.baseline.getTime() + (this.rp2040.clock.micros - this.baselineMicros) / 1000, + this.baseline.getTime() + (this.rp2040.clock.nanos - this.baselineNanos) / 1_000_000, ); switch (offset) { case RTC_SETUP0: @@ -108,7 +108,7 @@ export class RP2040RTC extends BasePeripheral implements Peripheral { const min = (this.setup1 >> SETUP_1_MIN_SHIFT) & SETUP_1_MIN_MASK; const sec = (this.setup1 >> SETUP_1_SEC_SHIFT) & SETUP_1_SEC_MASK; this.baseline = new Date(year, month - 1, day, hour, min, sec); - this.baselineMicros = this.rp2040.clock.micros; + this.baselineNanos = this.rp2040.clock.nanos; this.ctrl &= ~RTC_LOAD_BITS; } } else { diff --git a/src/peripherals/timer.ts b/src/peripherals/timer.ts index ecea546..ed12bc5 100644 --- a/src/peripherals/timer.ts +++ b/src/peripherals/timer.ts @@ -1,4 +1,4 @@ -import { IClock, IClockTimer } from '../clock/clock.js'; +import { IClock, IAlarm } from '../clock/clock.js'; import { IRQ } from '../irq.js'; import { RP2040 } from '../rp2040.js'; import { BasePeripheral, Peripheral } from './peripheral.js'; @@ -28,23 +28,17 @@ const timerInterrupts = [IRQ.TIMER_0, IRQ.TIMER_1, IRQ.TIMER_2, IRQ.TIMER_3]; class RPTimerAlarm { armed = false; targetMicros = 0; - timer: IClockTimer | null = null; constructor( - readonly name: string, readonly bitValue: number, + readonly clockAlarm: IAlarm, ) {} } export class RPTimer extends BasePeripheral implements Peripheral { private readonly clock: IClock; private latchedTimeHigh = 0; - private readonly alarms = [ - new RPTimerAlarm('Alarm 0', ALARM_0), - new RPTimerAlarm('Alarm 1', ALARM_1), - new RPTimerAlarm('Alarm 2', ALARM_2), - new RPTimerAlarm('Alarm 3', ALARM_3), - ]; + private readonly alarms; private intRaw = 0; private intEnable = 0; private intForce = 0; @@ -53,6 +47,24 @@ export class RPTimer extends BasePeripheral implements Peripheral { constructor(rp2040: RP2040, name: string) { super(rp2040, name); this.clock = rp2040.clock; + this.alarms = [ + new RPTimerAlarm( + ALARM_0, + this.clock.createAlarm(() => this.fireAlarm(0)), + ), + new RPTimerAlarm( + ALARM_1, + this.clock.createAlarm(() => this.fireAlarm(1)), + ), + new RPTimerAlarm( + ALARM_2, + this.clock.createAlarm(() => this.fireAlarm(2)), + ), + new RPTimerAlarm( + ALARM_3, + this.clock.createAlarm(() => this.fireAlarm(3)), + ), + ]; } get intStatus() { @@ -60,7 +72,7 @@ export class RPTimer extends BasePeripheral implements Peripheral { } readUint32(offset: number) { - const time = this.clock.micros; + const time = this.clock.nanos / 1000; switch (offset) { case TIMEHR: @@ -116,11 +128,10 @@ export class RPTimer extends BasePeripheral implements Peripheral { case ALARM3: { const alarmIndex = (offset - ALARM0) / 4; const alarm = this.alarms[alarmIndex]; - const delta = (value - this.clock.micros) >>> 0; - this.disarmAlarm(alarm); + const deltaMicros = (value - this.clock.nanos / 1000) >>> 0; alarm.armed = true; alarm.targetMicros = value; - alarm.timer = this.clock.createTimer(delta, () => this.fireAlarm(alarmIndex)); + alarm.clockAlarm.schedule(deltaMicros * 1000); break; } case ARMED: @@ -169,10 +180,7 @@ export class RPTimer extends BasePeripheral implements Peripheral { } private disarmAlarm(alarm: RPTimerAlarm) { - if (alarm.timer) { - this.clock.deleteTimer(alarm.timer); - alarm.timer = null; - } + alarm.clockAlarm.cancel(); alarm.armed = false; } } diff --git a/src/peripherals/usb.ts b/src/peripherals/usb.ts index 26c013b..3031a24 100644 --- a/src/peripherals/usb.ts +++ b/src/peripherals/usb.ts @@ -1,6 +1,10 @@ +import { IAlarm } from '../clock/clock.js'; import { IRQ } from '../irq.js'; +import { RP2040 } from '../rp2040.js'; import { BasePeripheral } from './peripheral.js'; +const ENDPOINT_COUNT = 16; + // USB DPSRAM Registers const EP1_IN_CONTROL = 0x8; const EP0_IN_BUFFER_CONTROL = 0x80; @@ -88,6 +92,17 @@ const SIE_WRITECLEAR_MASK = SIE_SETUP_REC | SIE_RESUME; +class USBEndpointAlarm { + buffer = new Uint8Array(0); + + constructor(readonly alarm: IAlarm) {} + + schedule(buffer: Uint8Array, delayNanos: number) { + this.buffer = buffer; + this.alarm.schedule(delayNanos); + } +} + export class RPUSBController extends BasePeripheral { private mainCtrl = 0; private intRaw = 0; @@ -96,6 +111,9 @@ export class RPUSBController extends BasePeripheral { private sieStatus = 0; private buffStatus = 0; + private readonly endpointReadAlarms: USBEndpointAlarm[]; + private readonly endpointWriteAlarms: USBEndpointAlarm[]; + onUSBEnabled?: () => void; onResetReceived?: () => void; onEndpointWrite?: (endpoint: number, buffer: Uint8Array) => void; @@ -108,6 +126,25 @@ export class RPUSBController extends BasePeripheral { return (this.intRaw & this.intEnable) | this.intForce; } + constructor(rp2040: RP2040, name: string) { + super(rp2040, name); + const clock = rp2040.clock; + this.endpointReadAlarms = []; + this.endpointWriteAlarms = []; + for (let i = 0; i < ENDPOINT_COUNT; ++i) { + this.endpointReadAlarms.push( + new USBEndpointAlarm( + clock.createAlarm(() => this.finishRead(i, this.endpointReadAlarms[i].buffer)), + ), + ); + this.endpointWriteAlarms.push( + new USBEndpointAlarm( + clock.createAlarm(() => this.onEndpointWrite?.(i, this.endpointWriteAlarms[i].buffer)), + ), + ); + } + } + readUint32(offset: number) { switch (offset) { case MAIN_CTRL: @@ -199,33 +236,6 @@ export class RPUSBController extends BasePeripheral { interrupt = !!(control & USB_CTRL_INTERRUPT_PER_TRANSFER); } - const bufferLength = value & USB_BUF_CTRL_LEN_MASK; - const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut); - this.debug( - `Start USB transfer, endPoint=${endpoint}, direction=${ - bufferOut ? 'out' : 'in' - } buffer=${bufferOffset.toString(16)} length=${bufferLength}`, - ); - value &= ~USB_BUF_CTRL_AVAILABLE; - this.rp2040.usbDPRAMView.setUint32(offset, value, true); - if (bufferOut) { - this.onEndpointRead?.(endpoint, bufferLength); - } else { - value &= ~USB_BUF_CTRL_FULL; - this.rp2040.usbDPRAMView.setUint32(offset, value, true); - const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength); - if (interrupt || !doubleBuffer) { - this.indicateBufferReady(endpoint, false); - } - if (this.writeDelayMicroseconds) { - this.rp2040.clock.createTimer(this.writeDelayMicroseconds, () => { - this.onEndpointWrite?.(endpoint, buffer); - }); - } else { - this.onEndpointWrite?.(endpoint, buffer); - } - } - if (doubleBuffer && (value >> USB_BUF1_SHIFT) & USB_BUF_CTRL_AVAILABLE) { const bufferLength = (value >> USB_BUF1_SHIFT) & USB_BUF_CTRL_LEN_MASK; const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut) + USB_BUF1_OFFSET; @@ -244,22 +254,43 @@ export class RPUSBController extends BasePeripheral { const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength); this.indicateBufferReady(endpoint, false); if (this.writeDelayMicroseconds) { - this.rp2040.clock.createTimer(this.writeDelayMicroseconds, () => { - this.onEndpointWrite?.(endpoint, buffer); - }); + this.endpointWriteAlarms[endpoint].schedule(buffer, this.writeDelayMicroseconds * 1000); } else { this.onEndpointWrite?.(endpoint, buffer); } } } + + const bufferLength = value & USB_BUF_CTRL_LEN_MASK; + const bufferOffset = this.getEndpointBufferOffset(endpoint, bufferOut); + this.debug( + `Start USB transfer, endPoint=${endpoint}, direction=${ + bufferOut ? 'out' : 'in' + } buffer=${bufferOffset.toString(16)} length=${bufferLength}`, + ); + value &= ~USB_BUF_CTRL_AVAILABLE; + this.rp2040.usbDPRAMView.setUint32(offset, value, true); + if (bufferOut) { + this.onEndpointRead?.(endpoint, bufferLength); + } else { + value &= ~USB_BUF_CTRL_FULL; + this.rp2040.usbDPRAMView.setUint32(offset, value, true); + const buffer = this.rp2040.usbDPRAM.slice(bufferOffset, bufferOffset + bufferLength); + if (interrupt || !doubleBuffer) { + this.indicateBufferReady(endpoint, false); + } + if (this.writeDelayMicroseconds) { + this.endpointWriteAlarms[endpoint].schedule(buffer, this.writeDelayMicroseconds * 1000); + } else { + this.onEndpointWrite?.(endpoint, buffer); + } + } } } endpointReadDone(endpoint: number, buffer: Uint8Array, delay = this.readDelayMicroseconds) { if (delay) { - this.rp2040.clock.createTimer(delay, () => { - this.finishRead(endpoint, buffer); - }); + this.endpointReadAlarms[endpoint].schedule(buffer, delay * 1000); } else { this.finishRead(endpoint, buffer); } diff --git a/src/rp2040.ts b/src/rp2040.ts index ea9d796..7b45733 100644 --- a/src/rp2040.ts +++ b/src/rp2040.ts @@ -1,5 +1,5 @@ import { IClock } from './clock/clock.js'; -import { RealtimeClock } from './clock/realtime-clock.js'; +import { SimulationClock } from './clock/simulation-clock.js'; import { CortexM0Core } from './cortex-m0-core.js'; import { GPIOPin } from './gpio-pin.js'; import { IRQ } from './irq.js'; @@ -132,12 +132,8 @@ export class RP2040 { }), ]; - private stopped = true; - public logger: Logger = new ConsoleLogger(LogLevel.Debug, true); - private executeTimer: NodeJS.Timeout | null = null; - readonly peripherals: { [index: number]: Peripheral } = { 0x18000: new RPSSI(this, 'SSI'), 0x40000: new RP2040SysInfo(this, 'SYSINFO_BASE'), @@ -178,10 +174,9 @@ export class RP2040 { public onBreak = (code: number) => { // TODO: raise HardFault exception // console.error('Breakpoint!', code); - this.stopped = true; }; - constructor(readonly clock: IClock = new RealtimeClock()) { + constructor(readonly clock: IClock = new SimulationClock()) { this.reset(); } @@ -372,29 +367,4 @@ export class RP2040 { step() { this.core.executeInstruction(); } - - execute() { - this.clock.resume(); - this.executeTimer = null; - this.stopped = false; - for (let i = 0; i < 100000 && !this.stopped && !this.core.waiting; i++) { - this.core.executeInstruction(); - } - if (!this.stopped) { - this.executeTimer = setTimeout(() => this.execute(), 0); - } - } - - stop() { - this.stopped = true; - if (this.executeTimer != null) { - clearTimeout(this.executeTimer); - this.executeTimer = null; - } - this.clock.pause(); - } - - get executing() { - return !this.stopped; - } } diff --git a/src/simulator.ts b/src/simulator.ts new file mode 100644 index 0000000..da3da5b --- /dev/null +++ b/src/simulator.ts @@ -0,0 +1,47 @@ +import { SimulationClock } from './clock/simulation-clock.js'; +import { IGDBTarget } from './gdb/gdb-target.js'; +import { RP2040 } from './rp2040.js'; + +export class Simulator implements IGDBTarget { + executeTimer: ReturnType | null = null; + rp2040: RP2040; + stopped = true; + + constructor(readonly clock = new SimulationClock()) { + this.rp2040 = new RP2040(clock); + this.rp2040.onBreak = () => this.stop(); + } + + execute() { + const { rp2040, clock } = this; + + this.executeTimer = null; + this.stopped = false; + const cycleNanos = 1e9 / 125_000_000; // 125 MHz + for (let i = 0; i < 1000000 && !this.stopped; i++) { + if (rp2040.core.waiting) { + const { nanosToNextAlarm } = clock; + clock.tick(nanosToNextAlarm); + i += nanosToNextAlarm / cycleNanos; + } else { + const cycles = rp2040.core.executeInstruction(); + clock.tick(cycles * cycleNanos); + } + } + if (!this.stopped) { + this.executeTimer = setTimeout(() => this.execute(), 0); + } + } + + stop() { + this.stopped = true; + if (this.executeTimer != null) { + clearTimeout(this.executeTimer); + this.executeTimer = null; + } + } + + get executing() { + return !this.stopped; + } +} diff --git a/src/utils/timer32.ts b/src/utils/timer32.ts index ad4836a..335a0d1 100644 --- a/src/utils/timer32.ts +++ b/src/utils/timer32.ts @@ -1,4 +1,4 @@ -import { IClock, IClockTimer } from '../clock/clock.js'; +import { IClock } from '../clock/clock.js'; export enum TimerMode { Increment, @@ -8,7 +8,7 @@ export enum TimerMode { export class Timer32 { private baseValue = 0; - private baseMicros = 0; + private baseNanos = 0; private topValue = 0xffffffff; private prescalerValue = 1; private timerMode = TimerMode.Increment; @@ -21,14 +21,14 @@ export class Timer32 { ) {} reset() { - this.baseMicros = this.clock.micros; + this.baseNanos = this.clock.nanos; this.baseValue = 0; this.updated(); } set(value: number, zigZagDown = false) { this.baseValue = zigZagDown ? this.topValue * 2 - value : value; - this.baseMicros = this.clock.micros; + this.baseNanos = this.clock.nanos; this.updated(); } @@ -43,12 +43,12 @@ export class Timer32 { } get rawCounter() { - const { baseFreq, prescalerValue, baseMicros, baseValue, enabled, timerMode } = this; + const { baseFreq, prescalerValue, baseNanos, baseValue, enabled, timerMode } = this; if (!baseFreq || !prescalerValue || !enabled) { return this.baseValue; } const zigzag = timerMode == TimerMode.ZigZag; - const ticks = ((this.clock.micros - baseMicros) / 1e6) * (baseFreq / prescalerValue); + const ticks = ((this.clock.nanos - baseNanos) / 1e9) * (baseFreq / prescalerValue); const topModulo = zigzag ? this.topValue * 2 : this.topValue + 1; const delta = timerMode == TimerMode.Decrement ? topModulo - (ticks % topModulo) : ticks; let currentValue = Math.round(baseValue + delta); @@ -82,7 +82,7 @@ export class Timer32 { set frequency(value: number) { this.baseValue = this.counter; - this.baseMicros = this.clock.micros; + this.baseNanos = this.clock.nanos; this.baseFreq = value; this.updated(); } @@ -93,15 +93,15 @@ export class Timer32 { set prescaler(value: number) { this.baseValue = this.counter; - this.baseMicros = this.clock.micros; + this.baseNanos = this.clock.nanos; this.enabled = this.prescalerValue !== 0; this.prescalerValue = value; this.updated(); } - toMicros(cycles: number) { + toNanos(cycles: number) { const { baseFreq, prescalerValue } = this; - return (cycles * 1e6) / (baseFreq / prescalerValue); + return (cycles * 1e9) / (baseFreq / prescalerValue); } get enable() { @@ -111,7 +111,7 @@ export class Timer32 { set enable(value: boolean) { if (value !== this.enabled) { if (value) { - this.baseMicros = this.clock.micros; + this.baseNanos = this.clock.nanos; } else { this.baseValue = this.counter; } @@ -142,12 +142,13 @@ export class Timer32 { export class Timer32PeriodicAlarm { private targetValue = 0; private enabled = false; - private clockTimer?: IClockTimer; + private clockAlarm; constructor( readonly timer: Timer32, readonly callback: () => void, ) { + this.clockAlarm = this.timer.clock.createAlarm(this.handleAlarm); timer.listeners.push(this.update); } @@ -219,14 +220,11 @@ export class Timer32PeriodicAlarm { cycleDelta = top + 1 - cycleDelta; } const cyclesToAlarm = cycleDelta >>> 0; - const microsToAlarm = timer.toMicros(cyclesToAlarm); - this.clockTimer = this.timer.clock.createTimer(microsToAlarm, this.handleAlarm); + const nanosToAlarm = timer.toNanos(cyclesToAlarm); + this.clockAlarm.schedule(nanosToAlarm); } private cancel() { - if (this.clockTimer) { - this.timer.clock.deleteTimer(this.clockTimer); - this.clockTimer = undefined; - } + this.clockAlarm.cancel(); } } diff --git a/test/micropython-spi-test.ts b/test/micropython-spi-test.ts index ba94644..743172a 100644 --- a/test/micropython-spi-test.ts +++ b/test/micropython-spi-test.ts @@ -1,13 +1,14 @@ -import { GPIOPinState, RP2040 } from '../src/index.js'; -import { ConsoleLogger, LogLevel } from '../src/utils/logging.js'; -import { bootromB1 } from '../demo/bootrom.js'; -import { loadUF2, loadMicropythonFlashImage } from '../demo/load-flash.js'; import fs from 'fs'; import minimist from 'minimist'; +import { bootromB1 } from '../demo/bootrom.js'; +import { loadMicropythonFlashImage, loadUF2 } from '../demo/load-flash.js'; +import { GPIOPinState, Simulator } from '../src/index.js'; +import { ConsoleLogger, LogLevel } from '../src/utils/logging.js'; const args = minimist(process.argv.slice(2)); -const mcu = new RP2040(); +const simulator = new Simulator(); +const mcu = simulator.rp2040; mcu.loadBootrom(bootromB1); mcu.logger = new ConsoleLogger(LogLevel.Error); @@ -50,4 +51,4 @@ mcu.spi[0].onTransmit = (char) => { }; mcu.core.PC = 0x10000000; -mcu.execute(); +simulator.execute();