Download js/ulp.js from HelloSun/esp32emu: direct link, hf CLI and curl.
- Browser
- Download file 7.23 kB
-
https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/ulp.js
- Command line
-
hf download hf://spaces/HelloSun/esp32emu/js/ulp.js
-
curl -L -o ulp.js https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/ulp.js
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; | |
| } | |
| } | |