diff --git a/.github/workflows/ci-micropython.yml b/.github/workflows/ci-micropython.yml index 5edd41d..fcf2b10 100644 --- a/.github/workflows/ci-micropython.yml +++ b/.github/workflows/ci-micropython.yml @@ -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" diff --git a/.github/workflows/ci-pico-sdk.yml b/.github/workflows/ci-pico-sdk.yml index 0dfeb0c..38471af 100644 --- a/.github/workflows/ci-pico-sdk.yml +++ b/.github/workflows/ci-pico-sdk.yml @@ -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!" 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();