esp32emu / js /worker.js
HelloSun's picture
ESP32 emulator update
d8afd6f verified
Raw History Blame Contribute Delete
4.75 kB
// Emulator worker: runs the ESP32 SoC model paced against wall-clock time.
import { ESP32, createESP32 } from './soc.js';
let soc = null, running = false, paused = false, turbo = false;
let wallStart = 0, virtStart = 0, speed = 1.0;
let uartBuf = [[], [], []];
let lastFrameWall = 0, lastStatus = 0, lastGpio = 0, framesInFlight = 0;
let instrWindow = 0, instrT0 = 0, mips = 0, ratio = 0, virtT0 = 0;
let roms = null, wasmMod = null;
function post(msg, tr) { self.postMessage(msg, tr || []); }
async function boot(cfg) {
if (!roms) { post({ type: 'error', text: 'ROMs not loaded' }); return; }
running = false; soc = null;
const opts = { cpi: cfg.cpi || 1, slice: cfg.slice || 256 };
soc = (wasmMod && !cfg.js) ? await createESP32({ ...opts, wasmModule: wasmMod }) : new ESP32(opts);
soc.loadRoms(roms.pro, roms.app);
soc.setFlash(new Uint8Array(cfg.flash));
soc.enablePsram(!!cfg.psram);
soc.onUart = (n, b) => { uartBuf[n].push(b); };
soc.reset('power');
wallStart = performance.now(); virtStart = soc.timeNs; running = true; paused = false;
instrT0 = wallStart; instrWindow = 0; virtT0 = soc.timeNs;
post({ type: 'booted', flashSize: soc.flashSize, psram: !!cfg.psram, engine: soc.engine });
if (!loopScheduled) { loopScheduled = true; setTimeout(loop, 0); }
}
let loopScheduled = false;
function flushUart() {
for (let n = 0; n < 3; n++) if (uartBuf[n].length) { post({ type: 'uart', n, data: new Uint8Array(uartBuf[n]) }); uartBuf[n] = []; }
}
function loop() {
const t = performance.now();
if (soc && running && !paused) {
// virtual-time target
const target = turbo ? Infinity : virtStart + (t - wallStart) * 1e6 * speed;
soc.idleLimit = turbo ? undefined : target;
soc.idleBlocked = false;
const budgetEnd = t + 12;
let instr = 0;
while (soc.timeNs < target && performance.now() < budgetEnd) {
instr += soc.run(50000);
if (soc.idleBlocked) break;
if (soc.idleForever) { break; }
}
instrWindow += instr;
// lag clamp: if far behind real time, resynchronize (don't try to catch up)
const now = performance.now();
if (!turbo && soc.timeNs < target - 250e6) { wallStart = now; virtStart = soc.timeNs; }
// outputs
const mon = soc.i2s[1].monitor;
if (mon.newFrame && now - lastFrameWall > 25 && framesInFlight < 2) {
const f = mon.takeFrame();
if (f) { lastFrameWall = now; framesInFlight++; post({ type: 'frame', w: f.w, h: f.h, data: f.data }, [f.data.buffer]); }
}
flushUart();
if (now - lastStatus > 500) {
const dt = (now - instrT0) / 1000;
mips = instrWindow / dt / 1e6; ratio = (soc.timeNs - virtT0) / 1e9 / dt;
instrWindow = 0; instrT0 = now; virtT0 = soc.timeNs; lastStatus = now;
post({ type: 'status', engine: soc.engine, virtMs: soc.timeNs / 1e6, mips, ratio, cpuMHz: soc.cpuHz / 1e6, pc0: soc.cpus[0].pc >>> 0, pc1: soc.cpus[1].pc >>> 0, frames: mon.frames, idle: !!(soc.cpus[0].halted && soc.cpus[1].halted || soc.cpus[1].stalled && soc.cpus[0].halted) });
}
if (now - lastGpio > 120) {
lastGpio = now;
const lv = new Uint8Array(40), drv = new Int8Array(40);
for (let i = 0; i < 40; i++) { lv[i] = soc.gpio.level(i); drv[i] = soc.gpio.driving(i); }
post({ type: 'gpio', lv, drv });
}
}
setTimeout(loop, (soc && running && !paused && !turbo && soc.idleBlocked) ? 4 : 0);
}
self.onmessage = (e) => {
const m = e.data;
switch (m.type) {
case 'roms':
roms = { pro: new Uint8Array(m.pro), app: new Uint8Array(m.app) };
(async () => { try { if (m.wasm) wasmMod = await WebAssembly.compile(m.wasm); } catch (err) { wasmMod = null; post({ type: 'error', text: 'wasm 不可用,改用 JS 核心: ' + err.message }); } post({ type: 'ready' }); })();
break;
case 'boot': framesInFlight = 0; boot(m); break;
case 'frameAck': if (framesInFlight > 0) framesInFlight--; break;
case 'key': if (soc) soc.ps2.kbd.key(m.code, m.down); break;
case 'mouse': if (soc) soc.ps2.mouse.mouse(m.dx, m.dy, m.buttons); break;
case 'uartRx': if (soc) soc.uart[m.n || 0].rxPush(m.data); break;
case 'pause': paused = !!m.value; if (!paused && soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break;
case 'turbo': turbo = !!m.value; if (soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break;
case 'speed': speed = m.value; if (soc) { wallStart = performance.now(); virtStart = soc.timeNs; } break;
case 'getFlash': if (soc) { const f = soc.getFlash().slice(); post({ type: 'flash', data: f.buffer }, [f.buffer]); } break;
case 'reset': if (soc) { soc.requestReset('power'); } break;
case 'gpioIn': if (soc) soc.gpio.setExt(m.pin, !m.level); break;
}
};