-
Notifications
You must be signed in to change notification settings - Fork 40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to simulate signal for PIO? #106
Comments
Sorry for not replying earlier - still need help with this? |
It's still a problem. The solution I thought about is let CPU, DMA, PIO, ... not executed in single thread. But that's a big change. |
The way Wokwi solves this is by using a different implementation of the clock that does not follow real time (it does best-effort to sync with real time, though). Once you use a virtual clock, you get can all the peripherals accurately synchronized with this clock. In a nutshell:
This enables accurate synchronization of PIO, DMA, etc., when they are all running on the same thread. What approach have you thought using for synchronizing them over multiple threads? |
I have not traced detail about clock in this emulator yet.
Indeed, it is a problem, just thought this idea but not dive deeply yet. Maybe each thread always waiting for the clock signal to work, but checkout what each component can do within 1 clock may be a hard task. If I have time later, I will check how other emulator handle this, like QEMU. |
Where does the code check the timers? As timers are implemented by Lines 21 to 22 in fa0dc6c
Lines 350 to 360 in fa0dc6c
|
Nope, you don't miss anything - rp2040js currently implements timers in a way that does not support accurate timers, unfortunately. This is Wokwi's internal Clock implementation: export type ClockEventCallback = () => void;
/**
* Linked list of pending clock events. We use a linked in instead of an array for performance,
* similar to how AVR8js implements clock events:
* https://github.com/wokwi/avr8js/commit/968a6ee0c90498077888c7f09c58983598937570
*/
interface IClockEventEntry {
cycles: number;
callback: ClockEventCallback;
next: IClockEventEntry | null;
}
/* Dummy implementation, to be compatible with rp2040js interface */
class Timer {
constructor(readonly callback: ClockEventCallback) {}
pause() {}
resume() {}
}
export class RPClock {
private nextClockEvent: IClockEventEntry | null = null;
private readonly clockEventPool: IClockEventEntry[] = []; // helps avoid garbage collection
skippedCycles = 0;
cpu: { cycles: number } = { cycles: 0 };
constructor(readonly frequency = 125e6) {}
get micros() {
return this.nanos / 1000;
}
get nanos() {
return (this.cpu.cycles / this.frequency) * 1e9;
}
addEvent(nanos: number, callback: ClockEventCallback) {
const cycles = Math.round((nanos / 1e9) * this.frequency);
return this.addEventCycles(cycles, callback);
}
clearEvent(callback: ClockEventCallback) {
let { nextClockEvent: clockEvent } = this;
if (!clockEvent) {
return false;
}
const { clockEventPool } = this;
let lastItem: IClockEventEntry | null = null;
while (clockEvent) {
if (clockEvent.callback === callback) {
if (lastItem) {
lastItem.next = clockEvent.next;
} else {
this.nextClockEvent = clockEvent.next;
}
if (clockEventPool.length < 10) {
clockEventPool.push(clockEvent);
}
return true;
}
lastItem = clockEvent;
clockEvent = clockEvent.next;
}
return false;
}
addEventCycles(cycles: number, callback: ClockEventCallback) {
const { clockEventPool } = this;
cycles = this.cpu.cycles + Math.max(1, cycles);
const maybeEntry = clockEventPool.pop();
const entry: IClockEventEntry = maybeEntry ?? { cycles, callback, next: null };
entry.cycles = cycles;
entry.callback = callback;
let { nextClockEvent: clockEvent } = this;
let lastItem: IClockEventEntry | null = null;
while (clockEvent && clockEvent.cycles < cycles) {
lastItem = clockEvent;
clockEvent = clockEvent.next;
}
if (lastItem) {
lastItem.next = entry;
entry.next = clockEvent;
} else {
this.nextClockEvent = entry;
entry.next = clockEvent;
}
return callback;
}
/** @deprecated */
pause() {
/* Not really used; Kept for compatibility with rp2040js clock */
}
/** @deprecated */
resume() {
/* Not really used; Kept for compatibility with rp2040js clock */
}
tick() {
const { nextClockEvent } = this;
if (nextClockEvent && nextClockEvent.cycles <= this.cpu.cycles) {
this.nextClockEvent = nextClockEvent.next;
if (this.clockEventPool.length < 10) {
this.clockEventPool.push(nextClockEvent);
}
nextClockEvent.callback();
}
}
createTimer(deltaMicros: number, callback: () => void) {
const timer = new Timer(callback);
this.addEvent(deltaMicros * 1000, callback);
return timer;
}
deleteTimer(timer: any) {
if (timer instanceof Timer) {
this.clearEvent(timer.callback);
}
}
createEvent(callback: () => void) {
const clock = this;
return {
schedule(nanos: number) {
clock.clearEvent(callback);
clock.addEvent(nanos, callback);
},
unschedule() {
clock.clearEvent(callback);
},
};
}
skipToNextEvent() {
const { nextClockEvent } = this;
if (nextClockEvent) {
this.cpu.cycles = nextClockEvent.cycles;
this.nextClockEvent = nextClockEvent.next;
if (this.clockEventPool.length < 10) {
this.clockEventPool.push(nextClockEvent);
}
nextClockEvent.callback();
}
}
get nextClockEventCycles() {
return this.nextClockEvent?.cycles ?? 0;
}
} And as you observed, you also need to update I hope to introduce this change to rp2040js at some point, but right now, for most use cases, the |
#117 makes PIO run in sync with the CPU. There, PIO is tied to CPU ticks instead of some other separate clock. Not sure it's actually correct but certainly it's looking better now (previously, ClockDiv was ignored completely, etc.). |
@c1570, sadly this does not solve my problem, the main issue is the timing to simulate signal for PIO's |
The roughly flows for my PIO application are described as below
Since CPU, DMA, PIO and timers all execute in single thread, it's hard to simulate the signal with expected timing.
When PIO is running and waiting for data from CPU, but meanwhile CPU is not running (the only thread is occupied by PIO).
Similar situation for PIO waiting for DMA execution.
Any idea to this problem?
The text was updated successfully, but these errors were encountered: