esp32emu / js /ulp.js
HelloSun's picture
ESP32 emulator update
8d577f5 verified
Raw History Blame Contribute Delete
7.23 kB
// ESP32 ULP coprocessor (FSM) interpreter + RTC_IO / SENS register blocks
const RTC_PAD_TO_GPIO = [36, 37, 38, 39, 34, 35, 25, 26, 33, 32, 4, 0, 2, 15, 13, 12, 14, 27];
const GPIO_TO_RTC = {}; RTC_PAD_TO_GPIO.forEach((g, i) => { GPIO_TO_RTC[g] = i; });
export class RtcIo {
constructor(soc) { this.soc = soc; this.reset(); }
reset() { this.out = 0; this.en = 0; this.r = new Int32Array(0x100); this.status = 0; }
drives(pad) { const i = GPIO_TO_RTC[pad]; return i !== undefined && ((this.en >>> (14 + i)) & 1) === 1; }
outLevel(pad) { return (this.out >>> (14 + GPIO_TO_RTC[pad])) & 1; }
inReg() { let v = 0; for (let i = 0; i < 18; i++) if (this.soc.gpio.level(RTC_PAD_TO_GPIO[i])) v |= (1 << (14 + i)); return v | 0; }
read(off) {
switch (off) {
case 0x00: return this.out; case 0x0c: return this.en; case 0x18: return this.status; case 0x24: return this.inReg();
}
return this.r[(off >> 2) & 0xFF];
}
write(off, v) {
const old = this.soc.gpio.snapshot();
switch (off) {
case 0x00: this.out = v; break; case 0x04: this.out |= v; break; case 0x08: this.out &= ~v; break;
case 0x0c: this.en = v; break; case 0x10: this.en |= v; break; case 0x14: this.en &= ~v; break;
case 0x1c: this.status |= v; break; case 0x20: this.status &= ~v; break;
default: this.r[(off >> 2) & 0xFF] = v;
}
this.soc.gpio.padsChanged(old);
}
}
export class Sens {
constructor(soc) { this.soc = soc; this.r = new Int32Array(0x100); }
reset() { this.r.fill(0); }
read(off) { return this.r[(off >> 2) & 0xFF]; }
write(off, v) {
this.r[(off >> 2) & 0xFF] = v;
if (off === 0x2c) {
if ((v >> 8) & 1 && (v >> 9) & 1) this.soc.ulp.start((v >> 11) & 0x7FF); // FORCE_START_TOP + START_TOP
}
}
}
export class Ulp {
constructor(soc) { this.soc = soc; this.reset(); }
reset() { this.running = false; this.pc = 0; this.reg = [0, 0, 0, 0]; this.stage = 0; this.zero = false; this.ovf = false; this.tNs = 0; this.cycNs = 125; }
mem() { return this.soc.rtcSlow32; }
start(pc) {
this.pc = pc; this.running = true; this.reg = [0, 0, 0, 0]; this.stage = 0; this.tNs = this.soc.timeNs;
}
advance(tNs) {
if (!this.running) return;
let guard = 0; const soc = this.soc;
while (this.tNs < tNs && this.running && guard++ < 100000) { soc.ulpNow = this.tNs; this.step(); }
soc.ulpNow = null;
if (this.tNs < tNs && !this.running) this.tNs = tNs;
}
periphRead(sel, addr) {
const soc = this.soc;
const bases = [0x3ff48000, 0x3ff48400, 0x3ff48800, 0x3ff48c00];
return soc.busRead(soc.cpus[0], bases[sel] + addr * 4, 4);
}
periphWrite(sel, addr, v) {
const soc = this.soc;
const bases = [0x3ff48000, 0x3ff48400, 0x3ff48800, 0x3ff48c00];
soc.busWrite(soc.cpus[0], bases[sel] + addr * 4, 4, v);
}
step() {
const m = this.mem();
const ins = m[this.pc & 0x7FF] >>> 0;
const op = ins >>> 28, R = this.reg;
let cyc = 5, npc = this.pc + 1;
switch (op) {
case 1: { // WR_REG
const addr = ins & 0xFF, sel = (ins >> 8) & 3, data = (ins >> 10) & 0xFF, low = (ins >> 18) & 31, high = (ins >> 23) & 31;
const width = high - low + 1;
// read-modify-write for partial; for full-word registers (W1TS style) write shifted value
const mask = (width >= 32 ? 0xFFFFFFFF : ((1 << width) - 1)) << low;
const full = (this.periphRead(sel, addr) & ~mask) | ((data << low) & mask);
this.periphWrite(sel, addr, this.isW1(sel, addr) ? ((data << low) & mask) : full);
cyc = 10; break;
}
case 2: { // RD_REG
const addr = ins & 0xFF, sel = (ins >> 8) & 3, low = (ins >> 18) & 31, high = (ins >> 23) & 31;
const v = this.periphRead(sel, addr) >>> 0;
R[0] = ((v >>> low) & ((1 << (high - low + 1)) - 1)) & 0xFFFF; cyc = 8; break;
}
case 4: cyc = (ins & 0xFFFF) + 1; break; // DELAY
case 6: { // ST
const d = ins & 3, s = (ins >> 2) & 3, off = (ins >> 10) & 0x7FF;
m[(R[s] + off) & 0x7FF] = ((this.pc & 0xFFFF) << 21) | (R[d] & 0xFFFF);
cyc = 4; break;
}
case 13: { // LD
const d = ins & 3, s = (ins >> 2) & 3, off = (ins >> 10) & 0x7FF;
R[d] = m[(R[s] + off) & 0x7FF] & 0xFFFF; cyc = 4; break;
}
case 7: { // ALU
const sub = (ins >> 21) & 7; // bits 25:21? ulp_insn: sub_opcode[27:25], sel[24:21]
const sub3 = (ins >> 25) & 7, sel = (ins >> 21) & 15;
if (sub3 === 0) { // reg
const d = ins & 3, s = (ins >> 2) & 3, t = (ins >> 4) & 3; this.alu(sel, d, R[s], R[t], false);
} else if (sub3 === 1) { // imm
const d = ins & 3, s = (ins >> 2) & 3, imm = (ins >> 4) & 0xFFFF; this.alu(sel, d, R[s], imm, true);
} else { // stage counter
const imm = (ins >> 4) & 0xFF;
if (sel === 0) this.stage = (this.stage + imm) & 0xFF; else if (sel === 1) this.stage = (this.stage - imm) & 0xFF; else this.stage = 0;
}
cyc = 6; break;
}
case 8: { // BRANCH
const sub = (ins >> 25) & 7;
if (sub === 0) { // BX
const type = (ins >> 28 - 0) & 0; const tp = (ins >> 22) & 7;
const target = ((ins >> 21) & 1) ? R[ins & 3] : ((ins >> 2) & 0x7FF);
let take = false;
if (tp === 0) take = true; else if (tp === 1) take = this.zero; else if (tp === 2) take = this.ovf;
if (take) npc = target;
} else if (sub === 1) { // B: relative, cond on R0
const imm = ins & 0xFFFF, cmp = (ins >> 16) & 1, off = (ins >> 17) & 0x7F, sign = (ins >> 24) & 1;
const take = cmp === 0 ? R[0] < imm : R[0] >= imm;
if (take) npc = this.pc + (sign ? -off : off);
} else if (sub === 2) { // BS
const imm = ins & 0xFF, cmp = (ins >> 15) & 3, off = (ins >> 17) & 0x7F, sign = (ins >> 24) & 1;
const take = cmp === 0 ? this.stage < imm : cmp === 1 ? this.stage >= imm : this.stage <= imm;
if (take) npc = this.pc + (sign ? -off : off);
}
cyc = 5; break;
}
case 9: { // END
const sub = (ins >> 25) & 7;
if (sub === 0 && (ins & 1)) this.soc.rtc.ulpWake();
cyc = 5; break;
}
case 11: this.running = false; cyc = 5; break; // HALT
case 5: R[ins & 3] = 0x800; cyc = 20; break; // ADC (stub)
case 10: R[ins & 3] = 0x80; cyc = 20; break; // TSENS (stub)
case 3: cyc = 20; break; // I2C (stub)
default: this.running = false; break;
}
this.pc = npc & 0x7FF;
this.tNs += cyc * this.cycNs;
}
isW1(sel, addr) { return false; }
alu(sel, d, a, b, isImm) {
const R = this.reg; let r;
switch (sel) {
case 0: r = a + b; this.ovf = r > 0xFFFF; break;
case 1: r = a - b; this.ovf = r < 0; break;
case 2: r = a & b; this.ovf = false; break;
case 3: r = a | b; this.ovf = false; break;
case 4: r = isImm ? b : a; this.ovf = false; break; // MOV: reg form copies sreg, imm form loads imm
case 5: r = a << b; this.ovf = r > 0xFFFF; break;
case 6: r = a >> b; this.ovf = false; break;
default: r = 0;
}
r &= 0xFFFF; R[d] = r; this.zero = r === 0;
}
}