feat: update ESP32-C3 wiring and enhance ROM function emulation with detailed logging

pull/47/head
David Montero Crespo 2026-03-17 23:06:36 -03:00
parent 0e7ef38104
commit 372e0f1d00
3 changed files with 251 additions and 23 deletions

View File

@ -2618,7 +2618,7 @@ void loop() {
wires: [ wires: [
{ id: 'c3w1', start: { componentId: 'esp32-c3', pinName: '8' }, end: { componentId: 'c3-led1', pinName: 'A' }, color: '#22cc22' }, { id: 'c3w1', start: { componentId: 'esp32-c3', pinName: '8' }, end: { componentId: 'c3-led1', pinName: 'A' }, color: '#22cc22' },
{ id: 'c3w2', start: { componentId: 'c3-led1', pinName: 'C' }, end: { componentId: 'c3-r1', pinName: '1' }, color: '#888888' }, { id: 'c3w2', start: { componentId: 'c3-led1', pinName: 'C' }, end: { componentId: 'c3-r1', pinName: '1' }, color: '#888888' },
{ id: 'c3w3', start: { componentId: 'c3-r1', pinName: '2' }, end: { componentId: 'esp32-c3', pinName: 'GND' }, color: '#000000' }, { id: 'c3w3', start: { componentId: 'c3-r1', pinName: '2' }, end: { componentId: 'esp32-c3', pinName: 'GND.9' }, color: '#000000' },
], ],
}, },
{ {
@ -2700,7 +2700,7 @@ void loop() {
{ id: 'c3-rw1', start: { componentId: 'esp32-c3', pinName: '6' }, end: { componentId: 'c3-rgb1', pinName: 'R' }, color: '#ff2222' }, { id: 'c3-rw1', start: { componentId: 'esp32-c3', pinName: '6' }, end: { componentId: 'c3-rgb1', pinName: 'R' }, color: '#ff2222' },
{ id: 'c3-rw2', start: { componentId: 'esp32-c3', pinName: '7' }, end: { componentId: 'c3-rgb1', pinName: 'G' }, color: '#22cc22' }, { id: 'c3-rw2', start: { componentId: 'esp32-c3', pinName: '7' }, end: { componentId: 'c3-rgb1', pinName: 'G' }, color: '#22cc22' },
{ id: 'c3-rw3', start: { componentId: 'esp32-c3', pinName: '8' }, end: { componentId: 'c3-rgb1', pinName: 'B' }, color: '#2244ff' }, { id: 'c3-rw3', start: { componentId: 'esp32-c3', pinName: '8' }, end: { componentId: 'c3-rgb1', pinName: 'B' }, color: '#2244ff' },
{ id: 'c3-rw4', start: { componentId: 'c3-rgb1', pinName: 'COM' }, end: { componentId: 'esp32-c3', pinName: 'GND' }, color: '#000000' }, { id: 'c3-rw4', start: { componentId: 'c3-rgb1', pinName: 'COM' }, end: { componentId: 'esp32-c3', pinName: 'GND.8' }, color: '#000000' },
], ],
}, },
{ {
@ -2738,8 +2738,8 @@ void loop() {
wires: [ wires: [
{ id: 'c3-bw1', start: { componentId: 'esp32-c3', pinName: '9' }, end: { componentId: 'c3-btn1', pinName: '1a' }, color: '#00aaff' }, { id: 'c3-bw1', start: { componentId: 'esp32-c3', pinName: '9' }, end: { componentId: 'c3-btn1', pinName: '1a' }, color: '#00aaff' },
{ id: 'c3-bw2', start: { componentId: 'esp32-c3', pinName: '8' }, end: { componentId: 'c3-led-btn', pinName: 'A' }, color: '#2244ff' }, { id: 'c3-bw2', start: { componentId: 'esp32-c3', pinName: '8' }, end: { componentId: 'c3-led-btn', pinName: 'A' }, color: '#2244ff' },
{ id: 'c3-bw3', start: { componentId: 'c3-led-btn', pinName: 'C' }, end: { componentId: 'esp32-c3', pinName: 'GND' }, color: '#000000' }, { id: 'c3-bw3', start: { componentId: 'c3-led-btn', pinName: 'C' }, end: { componentId: 'esp32-c3', pinName: 'GND.8' }, color: '#000000' },
{ id: 'c3-bw4', start: { componentId: 'c3-btn1', pinName: '1b' }, end: { componentId: 'esp32-c3', pinName: 'GND' }, color: '#000000' }, { id: 'c3-bw4', start: { componentId: 'c3-btn1', pinName: '1b' }, end: { componentId: 'esp32-c3', pinName: 'GND.9' }, color: '#000000' },
], ],
}, },
{ {

View File

@ -560,9 +560,11 @@ export class Esp32C3Simulator {
} }
/** /**
* ROM region (0x40000000-0x4005FFFF) serves the real ROM binary when * ROM region (0x40000000-0x4005FFFF) when the CPU fetches an instruction
* loaded, or falls back to C.RET (0x8082) with a0=0 if the binary is * from a known ROM function address, we emulate it natively in JavaScript
* unavailable. * (memset, memcpy, __udivdi3, ets_delay_us, etc.), set the return value
* in a0/a1, and serve C.RET (0x8082) so the CPU returns to the caller.
* Unknown functions still get a0=0 + C.RET.
*/ */
private _registerRomStub(): void { private _registerRomStub(): void {
const core = this.core; const core = this.core;
@ -573,15 +575,9 @@ export class Esp32C3Simulator {
const off = (addr >>> 0) - ROM_BASE; const off = (addr >>> 0) - ROM_BASE;
if (off < this._romData.length) return this._romData[off]; if (off < this._romData.length) return this._romData[off];
} }
// Fallback: detect instruction fetch and set a0=0 for C.RET stub // Detect instruction fetch and emulate known ROM functions
if ((addr & 1) === 0 && (addr >>> 0) === (core.pc >>> 0)) { if ((addr & 1) === 0 && (addr >>> 0) === (core.pc >>> 0)) {
core.regs[10] = 0; // a0 = 0 (ESP_OK) this._emulateRomFunction(addr >>> 0);
if (++this._romCallCount <= 50) {
console.log(
`[ROM] stub call #${this._romCallCount} → 0x${(addr >>> 0).toString(16)}` +
` ra=0x${(core.regs[1] >>> 0).toString(16)}`
);
}
} }
return (addr & 1) === 0 ? 0x82 : 0x80; return (addr & 1) === 0 ? 0x82 : 0x80;
}, },
@ -589,19 +585,13 @@ export class Esp32C3Simulator {
); );
} }
/** Second ROM region (0x40800000) — same stub with a0=0. */ /** Second ROM region (0x40800000) — same emulation with fallback a0=0. */
private _registerRomStub2(): void { private _registerRomStub2(): void {
const core = this.core; const core = this.core;
this.core.addMmio(ROM2_BASE, ROM2_SIZE, this.core.addMmio(ROM2_BASE, ROM2_SIZE,
(addr) => { (addr) => {
if ((addr & 1) === 0 && (addr >>> 0) === (core.pc >>> 0)) { if ((addr & 1) === 0 && (addr >>> 0) === (core.pc >>> 0)) {
core.regs[10] = 0; this._emulateRomFunction(addr >>> 0);
if (++this._romCallCount <= 50) {
console.log(
`[ROM2] call #${this._romCallCount} → 0x${(addr >>> 0).toString(16)}` +
` ra=0x${(core.regs[1] >>> 0).toString(16)}`
);
}
} }
return (addr & 1) === 0 ? 0x82 : 0x80; return (addr & 1) === 0 ? 0x82 : 0x80;
}, },
@ -609,6 +599,242 @@ export class Esp32C3Simulator {
); );
} }
// ── ROM function emulation ─────────────────────────────────────────────────
//
// The ESP32-C3 firmware links common C library functions (memset, memcpy,
// __udivdi3, …) and helper functions (ets_delay_us, rtc_get_reset_reason, …)
// to addresses in the ROM region (0x40000000+). Since we don't run the real
// ROM binary, we emulate these functions natively in JavaScript by reading
// arguments from registers a0a7, performing the operation on emulated
// memory, and writing the result back to a0(/a1). The caller then hits our
// C.RET (0x8082) stub and returns normally.
//
// Address mappings from esp32c3.rom.ld / esp32c3.rom.libgcc.ld:
private _emulateRomFunction(addr: number): void {
const r = this.core.regs;
const c = this.core;
if (++this._romCallCount <= 50) {
console.log(
`[ROM] #${this._romCallCount} 0x${addr.toString(16)}` +
` ra=0x${(r[1]>>>0).toString(16)}` +
` a0=0x${(r[10]>>>0).toString(16)} a1=0x${(r[11]>>>0).toString(16)}` +
` a2=0x${(r[12]>>>0).toString(16)} a3=0x${(r[13]>>>0).toString(16)}`
);
}
switch (addr) {
// ── C library functions ──────────────────────────────────────────
case 0x40000354: { // memset(dest, val, n) → dest
const dest = r[10] >>> 0;
const val = r[11] & 0xFF;
const n = r[12] >>> 0;
const safeN = Math.min(n, 0x100000); // cap at 1 MB safety
for (let i = 0; i < safeN; i++) c.writeByte(dest + i, val);
r[10] = dest | 0;
break;
}
case 0x40000358: { // memcpy(dest, src, n) → dest
const dest = r[10] >>> 0;
const src = r[11] >>> 0;
const n = r[12] >>> 0;
const safeN = Math.min(n, 0x100000);
// Copy forward (like real memcpy, undefined for overlap)
for (let i = 0; i < safeN; i++) c.writeByte(dest + i, c.readByte(src + i));
r[10] = dest | 0;
break;
}
case 0x4000035c: { // memmove(dest, src, n) → dest
const dest = r[10] >>> 0;
const src = r[11] >>> 0;
const n = r[12] >>> 0;
const safeN = Math.min(n, 0x100000);
if (dest < src) {
for (let i = 0; i < safeN; i++) c.writeByte(dest + i, c.readByte(src + i));
} else {
for (let i = safeN - 1; i >= 0; i--) c.writeByte(dest + i, c.readByte(src + i));
}
r[10] = dest | 0;
break;
}
case 0x40000360: { // memcmp(a, b, n) → int
const a = r[10] >>> 0;
const b = r[11] >>> 0;
const n = r[12] >>> 0;
let result = 0;
for (let i = 0; i < n; i++) {
const diff = c.readByte(a + i) - c.readByte(b + i);
if (diff !== 0) { result = diff > 0 ? 1 : -1; break; }
}
r[10] = result;
break;
}
case 0x40000364: { // strcpy(dest, src) → dest
const dest = r[10] >>> 0;
const src = r[11] >>> 0;
let i = 0;
while (i < 0x100000) {
const ch = c.readByte(src + i);
c.writeByte(dest + i, ch);
if (ch === 0) break;
i++;
}
r[10] = dest | 0;
break;
}
case 0x4000036c: { // strcmp(a, b) → int
const a = r[10] >>> 0;
const b = r[11] >>> 0;
let result = 0;
for (let i = 0; i < 0x100000; i++) {
const ca = c.readByte(a + i);
const cb = c.readByte(b + i);
if (ca !== cb) { result = ca - cb; break; }
if (ca === 0) break;
}
r[10] = result;
break;
}
case 0x40000374: { // strlen(s) → size_t
const s = r[10] >>> 0;
let len = 0;
while (len < 0x100000 && c.readByte(s + len) !== 0) len++;
r[10] = len;
break;
}
case 0x4000037c: { // bzero(dest, n)
const dest = r[10] >>> 0;
const n = r[11] >>> 0;
const safeN = Math.min(n, 0x100000);
for (let i = 0; i < safeN; i++) c.writeByte(dest + i, 0);
break;
}
// ── libgcc 64-bit integer math ───────────────────────────────────
case 0x400008ac: { // __udivdi3(a, b) → a / b (unsigned 64-bit)
const aLo = r[10] >>> 0, aHi = r[11] >>> 0;
const bLo = r[12] >>> 0, bHi = r[13] >>> 0;
const a = BigInt(aLo) | (BigInt(aHi) << 32n);
const b = BigInt(bLo) | (BigInt(bHi) << 32n);
const q = b !== 0n ? a / b : 0n;
r[10] = Number(q & 0xFFFFFFFFn) | 0;
r[11] = Number((q >> 32n) & 0xFFFFFFFFn) | 0;
break;
}
case 0x400008b0: { // __udivmoddi4(a, b, *rem) → a / b, *rem = a % b
const aLo = r[10] >>> 0, aHi = r[11] >>> 0;
const bLo = r[12] >>> 0, bHi = r[13] >>> 0;
const remPtr = r[14] >>> 0; // a4
const a = BigInt(aLo) | (BigInt(aHi) << 32n);
const b = BigInt(bLo) | (BigInt(bHi) << 32n);
const q = b !== 0n ? a / b : 0n;
const rem = b !== 0n ? a % b : 0n;
r[10] = Number(q & 0xFFFFFFFFn) | 0;
r[11] = Number((q >> 32n) & 0xFFFFFFFFn) | 0;
if (remPtr !== 0) {
c.writeWord(remPtr, Number(rem & 0xFFFFFFFFn));
c.writeWord(remPtr + 4, Number((rem >> 32n) & 0xFFFFFFFFn));
}
break;
}
case 0x400008b4: { // __udivsi3(a, b) → a / b (unsigned 32-bit)
const a = r[10] >>> 0, b = r[11] >>> 0;
r[10] = b !== 0 ? (a / b) >>> 0 : 0;
break;
}
case 0x400008bc: { // __umoddi3(a, b) → a % b (unsigned 64-bit)
const aLo = r[10] >>> 0, aHi = r[11] >>> 0;
const bLo = r[12] >>> 0, bHi = r[13] >>> 0;
const a = BigInt(aLo) | (BigInt(aHi) << 32n);
const b = BigInt(bLo) | (BigInt(bHi) << 32n);
const rem = b !== 0n ? a % b : 0n;
r[10] = Number(rem & 0xFFFFFFFFn) | 0;
r[11] = Number((rem >> 32n) & 0xFFFFFFFFn) | 0;
break;
}
case 0x400008c0: { // __umodsi3(a, b) → a % b (unsigned 32-bit)
const a = r[10] >>> 0, b = r[11] >>> 0;
r[10] = b !== 0 ? (a % b) >>> 0 : 0;
break;
}
case 0x400007b4: { // __divdi3(a, b) → a / b (signed 64-bit)
const aLo = r[10] >>> 0, aHi = r[11] | 0;
const bLo = r[12] >>> 0, bHi = r[13] | 0;
const a = BigInt.asIntN(64, BigInt(aLo) | (BigInt(aHi) << 32n));
const b = BigInt.asIntN(64, BigInt(bLo) | (BigInt(bHi) << 32n));
const q = b !== 0n ? a / b : 0n;
const qu = BigInt.asUintN(64, q);
r[10] = Number(qu & 0xFFFFFFFFn) | 0;
r[11] = Number((qu >> 32n) & 0xFFFFFFFFn) | 0;
break;
}
case 0x400007c0: { // __divsi3(a, b) → a / b (signed 32-bit)
const a = r[10] | 0, b = r[11] | 0;
r[10] = b !== 0 ? (a / b) | 0 : 0;
break;
}
case 0x4000083c: { // __moddi3(a, b) → a % b (signed 64-bit)
const aLo = r[10] >>> 0, aHi = r[11] | 0;
const bLo = r[12] >>> 0, bHi = r[13] | 0;
const a = BigInt.asIntN(64, BigInt(aLo) | (BigInt(aHi) << 32n));
const b = BigInt.asIntN(64, BigInt(bLo) | (BigInt(bHi) << 32n));
const rem = b !== 0n ? a % b : 0n;
const remu = BigInt.asUintN(64, rem);
r[10] = Number(remu & 0xFFFFFFFFn) | 0;
r[11] = Number((remu >> 32n) & 0xFFFFFFFFn) | 0;
break;
}
case 0x40000840: { // __modsi3(a, b) → a % b (signed 32-bit)
const a = r[10] | 0, b = r[11] | 0;
r[10] = b !== 0 ? (a % b) | 0 : 0;
break;
}
case 0x4000084c: { // __muldi3(a, b) → a * b (64-bit)
const aLo = r[10] >>> 0, aHi = r[11] >>> 0;
const bLo = r[12] >>> 0, bHi = r[13] >>> 0;
const a = BigInt(aLo) | (BigInt(aHi) << 32n);
const b = BigInt(bLo) | (BigInt(bHi) << 32n);
const p = BigInt.asUintN(64, a * b);
r[10] = Number(p & 0xFFFFFFFFn) | 0;
r[11] = Number((p >> 32n) & 0xFFFFFFFFn) | 0;
break;
}
case 0x40000858: { // __mulsi3(a, b) → a * b (32-bit)
r[10] = Math.imul(r[10], r[11]);
break;
}
// ── ESP-IDF ROM helpers ──────────────────────────────────────────
case 0x40000018: { // rtc_get_reset_reason() → 1 (POWERON_RESET)
r[10] = 1;
break;
}
case 0x40000050: { // ets_delay_us(us) → void
// Burn the equivalent number of CPU cycles so timers advance
const us = r[10] >>> 0;
const burnCycles = Math.min(us * (CPU_HZ / 1_000_000), 1_000_000);
this.core.cycles += burnCycles;
r[10] = 0;
break;
}
case 0x40000548: { // Cache_Set_IDROM_MMU_Size(...) → 0 (success)
r[10] = 0;
break;
}
case 0x40001960: { // rom_i2c_writeReg_Mask(...) → 0 (success)
r[10] = 0;
break;
}
default: {
// Unknown ROM function — return a0=0 (ESP_OK for esp_err_t functions)
r[10] = 0;
break;
}
}
}
/** /**
* Timer Group stub (TIMG0 / TIMG1). * Timer Group stub (TIMG0 / TIMG1).
* *

View File

@ -224,6 +224,8 @@ export function boardPinToNumber(boardId: string, pinName: string): number | nul
// ESP32 / ESP32-S3 / ESP32-C3 — GPIO numbers used directly // ESP32 / ESP32-S3 / ESP32-C3 — GPIO numbers used directly
if (boardId === 'esp32' || boardId.startsWith('esp32')) { if (boardId === 'esp32' || boardId.startsWith('esp32')) {
// Power / GND pins (GND, GND.1, 3V3, 3V3.1, 5V, 5V.1, etc.)
if (pinName.startsWith('GND') || pinName.startsWith('3V3') || pinName.startsWith('5V')) return -1;
// Try bare number first ("13" → 13) // Try bare number first ("13" → 13)
const num = parseInt(pinName, 10); const num = parseInt(pinName, 10);
if (!isNaN(num) && num >= 0 && num <= 39) return num; if (!isNaN(num) && num >= 0 && num <= 39) return num;