Download js/soc.js from HelloSun/esp32emu: direct link, hf CLI and curl.
- Browser
- Download file 39.7 kB
-
https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/soc.js
- Command line
-
hf download hf://spaces/HelloSun/esp32emu/js/soc.js
-
curl -L -o soc.js https://huggingface.co/spaces/HelloSun/esp32emu/resolve/main/js/soc.js
39.7 kB
| // ESP32 SoC model: memory map, interrupt matrix, timers, UART, SPI flash, GPIO, ... | |
| import { XtensaCPU } from './xtensa.js'; | |
| import { XtensaCPUWasm, STATE_WORDS } from './cpu_wasm.js'; | |
| import { FlashChip, PsramChip, SpiController } from './spi.js'; | |
| import { GPIO } from './gpio.js'; | |
| import { I2S } from './i2s.js'; | |
| import { ShaEngine } from './sha.js'; | |
| import { Ulp, RtcIo, Sens } from './ulp.js'; | |
| import { Ps2Device } from './ps2.js'; | |
| const SLOW = 1; | |
| // heap layout (bytes) | |
| const HEAP_BASE = 0x100000; // first 1MB of linear memory belongs to the wasm module (data/stack) | |
| const H_ROM_PRO = HEAP_BASE + 0x000000, H_ROM_APP = HEAP_BASE + 0x080000, H_RAM = HEAP_BASE + 0x100000, H_DRAM = HEAP_BASE + 0x150000; | |
| const H_RTCF = HEAP_BASE + 0x1A2000, H_RTCS = HEAP_BASE + 0x1A4000, H_ZERO = HEAP_BASE + 0x1A6000, H_PSRAM = HEAP_BASE + 0x200000, H_FLASH = HEAP_BASE + 0x600000; | |
| const PSRAM_SIZE = 4 * 1024 * 1024; | |
| const FLASH_MAX = 16 * 1024 * 1024; | |
| const HEAP_END = H_FLASH + FLASH_MAX; | |
| const STATE_BASE = HEAP_END; // per-cpu state arrays (4KB each) | |
| const TBL_BASE = (HEAP_END + 0x10000) & ~0xFFF; // page tables: rd0, wr0, rd1, wr1 (4MB each) | |
| const MEM_SIZE = 64 * 1024 * 1024; // must match the wasm module's imported memory (1024 pages) | |
| const EXT_INT_MASK = 0xDFFE373F; | |
| const NSRC = 69; | |
| class EventQueue { | |
| constructor() { this.h = []; this.seq = 0; } | |
| push(t, fn) { const e = { t, fn, id: ++this.seq, dead: false }; const h = this.h; h.push(e); let i = h.length - 1; while (i > 0) { const p = (i - 1) >> 1; if (h[p].t <= e.t) break; h[i] = h[p]; i = p; } h[i] = e; return e; } | |
| peek() { const h = this.h; while (h.length && h[0].dead) this.pop(); return h.length ? h[0] : null; } | |
| pop() { | |
| const h = this.h; const top = h[0]; const last = h.pop(); | |
| if (h.length) { let i = 0; const n = h.length; for (;;) { let c = 2 * i + 1; if (c >= n) break; if (c + 1 < n && h[c + 1].t < h[c].t) c++; if (h[c].t >= last.t) break; h[i] = h[c]; i = c; } h[i] = last; } | |
| return top; | |
| } | |
| } | |
| // create an ESP32 with the WebAssembly CPU core (wasmBytes: Uint8Array of xtensa.wasm) | |
| export async function createESP32(opts = {}) { | |
| const memory = new WebAssembly.Memory({ initial: MEM_SIZE >> 16, maximum: MEM_SIZE >> 16 }); | |
| let soc = null; | |
| const imports = { env: { | |
| memory, | |
| mmio_read: (cpu, addr, size) => soc.busRead(soc.cpus[cpu], addr >>> 0, size) | 0, | |
| mmio_write: (cpu, addr, size, val) => soc.busWrite(soc.cpus[cpu], addr >>> 0, size, val), | |
| rer: (cpu, addr) => soc.rer(soc.cpus[cpu], addr), | |
| wer: (cpu, addr, val) => soc.wer(soc.cpus[cpu], addr, val), | |
| hook: (cpu, pc, wb4) => (soc.runHook(soc.cpus[cpu], pc, wb4) ? 1 : 0), | |
| timer_changed: () => {}, | |
| } }; | |
| const mod = opts.wasmModule || await WebAssembly.compile(opts.wasmBytes); | |
| const inst = await WebAssembly.instantiate(mod, imports); | |
| inst.exports.xt_init(); | |
| soc = new ESP32({ ...opts, memory, wasm: inst.exports }); | |
| return soc; | |
| } | |
| export class ESP32 { | |
| constructor(opts = {}) { | |
| this.opts = opts; | |
| this.hooks = new Map(); | |
| this.memory = opts.memory || new WebAssembly.Memory({ initial: MEM_SIZE >> 16, maximum: MEM_SIZE >> 16 }); | |
| this.buffer = this.memory.buffer; | |
| this.h8 = new Uint8Array(this.buffer); this.h16 = new Uint16Array(this.buffer); | |
| this.h16s = new Int16Array(this.buffer); this.h32 = new Int32Array(this.buffer); | |
| if (opts.wasm) { | |
| this.engine = 'wasm'; | |
| this.cpus = [0, 1].map((i) => new XtensaCPUWasm(this, i, this.buffer, STATE_BASE + i * 0x1000, TBL_BASE + i * 0x800000, TBL_BASE + i * 0x800000 + 0x400000, opts.wasm)); | |
| } else { | |
| this.engine = 'js'; | |
| this.cpus = [new XtensaCPU(this, 0), new XtensaCPU(this, 1)]; | |
| this.cpus.forEach((c, i) => { | |
| c.h8 = this.h8; c.h16 = this.h16; c.h16s = this.h16s; c.h32 = this.h32; | |
| c.rd = new Int32Array(this.buffer, TBL_BASE + i * 0x800000, 1 << 20); c.wr = new Int32Array(this.buffer, TBL_BASE + i * 0x800000 + 0x400000, 1 << 20); | |
| c.rd.fill(SLOW); c.wr.fill(SLOW); | |
| }); | |
| } | |
| this.events = new EventQueue(); | |
| this.timeNs = 0; | |
| this.cpuHz = 40e6; this.apbHz = 40e6; this.cpi = opts.cpi || 1; | |
| this.srcLevel = new Uint8Array(NSRC); | |
| this.srcLevel1 = new Uint8Array(NSRC); // per-cpu variants (cpu1) when srcPerCpu | |
| this.srcPerCpu = new Uint8Array(NSRC); | |
| this.intMap = [new Uint8Array(NSRC).fill(6), new Uint8Array(NSRC).fill(6)]; | |
| this.uartTx = [[], [], []]; | |
| this.onUart = null; | |
| this.log = opts.log || ((s) => console.log(s)); | |
| this.dport = new Int32Array(0x1000); | |
| this.mmu = [new Uint16Array(256), new Uint16Array(256)]; | |
| this.cacheCtrl = [0, 0]; this.cacheCtrl1 = [0, 0]; | |
| this.flashSize = 0; | |
| this.misc = new Map(); | |
| this.sha = new ShaEngine(this); | |
| this.rtcSlow32 = new Int32Array(this.buffer, H_RTCS, 2048); | |
| this.gpio = new GPIO(this); | |
| this.rtcio = new RtcIo(this); this.sens = new Sens(this); this.ulp = new Ulp(this); | |
| this.i2s = [new I2S(this, 0), new I2S(this, 1)]; | |
| this.spi = [null, new SpiController(this, 1), new SpiController(this, 2), new SpiController(this, 3)]; | |
| this.rtc = new RtcCntl(this); | |
| this.timg = [new TimerGroup(this, 0), new TimerGroup(this, 1)]; | |
| this.uart = [new Uart(this, 0), new Uart(this, 1), new Uart(this, 2)]; | |
| this.efuse = new Efuse(this); | |
| this.frc = [new Frc(this, 0), new Frc(this, 1)]; | |
| this.devs = new Map(); | |
| const reg = (base, dev, pages = 1) => { for (let i = 0; i < pages; i++) this.devs.set((base >>> 12) + i, { dev, off: i << 12 }); }; | |
| reg(0x3ff40000, this.uart[0]); reg(0x3ff50000, this.uart[1]); reg(0x3ff6E000, this.uart[2]); | |
| reg(0x3ff42000, this.spi[1]); reg(0x3ff64000, this.spi[2]); reg(0x3ff65000, this.spi[3]); | |
| reg(0x3ff44000, this.gpio); reg(0x3ff49000, this.gpio.iomux); | |
| reg(0x3ff47000, new FrcPage(this)); | |
| reg(0x3ff48000, new RtcPage(this)); reg(0x3ff5A000, this.efuse); | |
| reg(0x3ff5F000, this.timg[0]); reg(0x3ff60000, this.timg[1]); | |
| reg(0x3ff4F000, this.i2s[0]); reg(0x3ff6D000, this.i2s[1]); | |
| reg(0x3ff66000, new Syscon(this)); reg(0x3ff75000, new Wdev(this)); | |
| reg(0x3ff43000, new Spi0(this)); | |
| this.ps2 = { kbd: new Ps2Device(this, 33, 32, 'keyboard'), mouse: new Ps2Device(this, 26, 27, 'mouse') }; | |
| this.appRunning = false; | |
| this.appBoot = 0; this.appResetState = true; this.appClk = false; this.appStall = false; | |
| this.rom = { pro: null, app: null }; | |
| this.bpHit = null; | |
| } | |
| // ---------------- ROM HLE hooks ---------------- | |
| addRomHook(addr, fn) { | |
| this.hooks.set(addr, fn); | |
| for (const base of [H_ROM_PRO, H_ROM_APP]) { const o = base + (addr - 0x40000000); this.h8[o] = 0; this.h8[o + 1] = 0; this.h8[o + 2] = 0; } | |
| } | |
| runHook(cpu, pc, wb4) { | |
| const fn = this.hooks.get(pc >>> 0); | |
| if (!fn) return false; | |
| const ar = cpu.ar, ci = (cpu.ps >> 16) & 3, b = wb4 + 4 * ci; | |
| const args = []; for (let k = 2; k < 8; k++) args.push(ar[(b + k) & 63]); | |
| const ra = ar[b & 63]; | |
| const ret = fn.call(this, ...args); | |
| ar[(b + 2) & 63] = ret | 0; | |
| cpu.hookPc = ci === 0 ? ra : ((ra & 0x3FFFFFFF) | (pc & 0xC0000000)) | 0; | |
| return true; | |
| } | |
| installRomHooks() { | |
| const i2c = (this.i2c = new Map()); | |
| const upd = () => { | |
| const g = (r) => i2c.get((0x6D << 8) | r) | 0; | |
| const sdm2 = g(7) & 0x3F, sdm1 = g(8) & 0xFF, sdm0 = g(9) & 0xFF, od = g(4) & 0x1F; | |
| this.apllHz = 40e6 * (4 + sdm2 + sdm1 / 256 + sdm0 / 65536) / (2 * (od + 2)); | |
| }; | |
| const wmask = (block, host, reg, msb, lsb, val) => { | |
| const key = (block << 8) | reg; const old = i2c.get(key) | 0; | |
| const mask = (((1 << (msb - lsb + 1)) - 1) << lsb); | |
| i2c.set(key, (old & ~mask) | ((val << lsb) & mask)); if (block === 0x6D) upd(); return 0; | |
| }; | |
| this.apllHz = 0; | |
| this.addRomHook(0x400041fc, function (block, host, reg, msb, lsb, val) { return wmask(block & 0xFF, host, reg & 0xFF, msb, lsb, val); }); | |
| this.addRomHook(0x400041a4, function (block, host, reg, val) { i2c.set(((block & 0xFF) << 8) | (reg & 0xFF), val & 0xFF); if ((block & 0xFF) === 0x6D) upd(); return 0; }); | |
| const rd = (block, reg) => { const v = i2c.get(((block & 0xFF) << 8) | (reg & 0xFF)) | 0; return (block & 0xFF) === 0x6D && (reg & 0xFF) === 3 ? v | 0x80 : v; }; // APLL: calibration done | |
| this.addRomHook(0x40004148, function (block, host, reg) { return rd(block, reg); }); | |
| this.addRomHook(0x400041c0, function (block, host, reg, msb, lsb) { return (rd(block, reg) >> lsb) & ((1 << (msb - lsb + 1)) - 1); }); | |
| } | |
| // ---------------- time / events ---------------- | |
| schedule(atNs, fn) { return this.events.push(atNs, fn); } | |
| scheduleIn(dtNs, fn) { return this.events.push(this.nowNs + dtNs, fn); } | |
| cancel(e) { if (e) e.dead = true; } | |
| get nowNs() { return this.ulpNow !== null && this.ulpNow !== undefined ? this.ulpNow : this.timeNs + this.curSliceNs(); } | |
| curSliceNs() { return this.inSlice ? ((this.cpus[this.curCpu].sliceLen - this.cpus[this.curCpu].n) * this.cpi * 1e9 / this.cpuHz) : 0; } | |
| setClocks(cpuHz, apbHz) { | |
| this.cpuHz = cpuHz; this.apbHz = apbHz; | |
| for (const t of this.timg) t.clockChanged(); | |
| for (const f of this.frc) f.clockChanged(); | |
| } | |
| // ---------------- interrupts ---------------- | |
| setIrq(src, level) { | |
| level = level ? 1 : 0; | |
| if (this.srcLevel[src] === level) return; | |
| this.srcLevel[src] = level; | |
| this.updateIrq(); | |
| } | |
| setIrqCpu(cpu, src, level) { // per-cpu sources (GPIO) | |
| this.srcPerCpu[src] = 1; | |
| if (cpu === 0) this.srcLevel[src] = level ? 1 : 0; else this.srcLevel1[src] = level ? 1 : 0; | |
| this.updateIrq(); | |
| } | |
| updateIrq() { | |
| this.irqCores = 0; | |
| for (let c = 0; c < 2; c++) { | |
| let pins = 0; | |
| const map = this.intMap[c], lv = c === 1 && this.srcPerCpu ? null : this.srcLevel; | |
| for (let s = 0; s < NSRC; s++) { | |
| const l = (c === 1 && this.srcPerCpu[s]) ? this.srcLevel1[s] : this.srcLevel[s]; | |
| if (l) pins |= (1 << map[s]); | |
| } | |
| pins &= EXT_INT_MASK; | |
| const cpu = this.cpus[c]; | |
| if (cpu.intPins !== pins) { | |
| cpu.intPins = pins; cpu.chkInt = true; if (cpu.halted) cpu.checkWake(); | |
| // model simultaneous interrupt entry on the cores that are not currently executing (they would sample the line in parallel on real silicon) | |
| if (!(this.inSlice && c === this.curCpu)) { if (cpu.pendingLevel() > 0) this.irqCores++; cpu.deliverNow(); } | |
| } | |
| } | |
| if (this.irqCores >= 2) this.lockstep = 1500; // both cores entered ISRs at the same instant: interleave finely so they race like real silicon | |
| } | |
| cpuTimerChanged(cpu) { /* picked up at next slice */ } | |
| // ---------------- memory ---------------- | |
| mapRegion(c, vaddr, heapOff, size, writable = true) { | |
| const rd = c.rd, wr = c.wr; | |
| for (let a = 0; a < size; a += 4096) { | |
| const p = ((vaddr + a) >>> 12); | |
| const d = (heapOff + a - (vaddr + a)) | 0; | |
| rd[p] = d; wr[p] = writable ? d : SLOW; | |
| } | |
| } | |
| buildMaps() { | |
| for (let i = 0; i < 2; i++) { | |
| const c = this.cpus[i]; | |
| c.rd.fill(SLOW); c.wr.fill(SLOW); | |
| this.mapRegion(c, 0x40000000, i === 0 ? H_ROM_PRO : H_ROM_APP, 0x70000, false); | |
| this.mapRegion(c, 0x3FF90000, (i === 0 ? H_ROM_PRO : H_ROM_APP) + 0x60000, 0x10000, false); | |
| this.mapRegion(c, 0x40070000, H_RAM, 0x50000); | |
| this.mapRegion(c, 0x3FFAE000, H_DRAM, 0x52000); | |
| this.mapRegion(c, 0x50000000, H_RTCS, 0x2000); | |
| if (i === 0) { this.mapRegion(c, 0x400C0000, H_RTCF, 0x2000); this.mapRegion(c, 0x3FF80000, H_RTCF, 0x2000); } | |
| if (this.psram) this.mapRegion(c, 0x3F800000, H_PSRAM, PSRAM_SIZE); | |
| this.applyCache(i); | |
| } | |
| } | |
| applyCache(i) { | |
| const c = this.cpus[i], tbl = this.mmu[i]; | |
| const flashPages = this.flashSize >>> 16; | |
| const map = (vbase, idxBase, startPage) => { | |
| for (let k = startPage; k < 64; k++) { | |
| const e = tbl[idxBase + k]; | |
| const v = vbase + k * 0x10000; | |
| for (let q = 0; q < 16; q++) { | |
| const p = (v + q * 4096) >>> 12; | |
| if (c.rd[p] !== SLOW && c._cacheMapped && !c._cacheMapped[p]) continue; // static mapping wins | |
| if (!c._cacheMapped) c._cacheMapped = new Uint8Array(1 << 20); | |
| if ((e & 0x100) || (e & 0xFF) >= flashPages || flashPages === 0) { c.rd[p] = SLOW; c.wr[p] = SLOW; c._cacheMapped[p] = 0; } | |
| else { const d = (H_FLASH + (e & 0xFF) * 0x10000 + q * 4096 - (v + q * 4096)) | 0; c.rd[p] = d; c.wr[p] = SLOW; c._cacheMapped[p] = 1; } | |
| } | |
| } | |
| }; | |
| if (!c._cacheMapped) c._cacheMapped = new Uint8Array(1 << 20); | |
| map(0x3F400000, 0, 0); | |
| // IRAM0: pages below 0x400C2000 are static (rom/ram); page 12 partially | |
| map(0x40000000, 64, 12); | |
| map(0x40400000, 128, 0); | |
| map(0x40800000, 192, 0); | |
| } | |
| // ---------------- bus slow path ---------------- | |
| busRead(cpu, addr, size) { | |
| this.curCpu = cpu.id; | |
| let a = addr; | |
| if (a >= 0x60000000 && a < 0x60100000) a = (a - 0x60000000 + 0x3ff40000) >>> 0; | |
| const page = a >>> 12; | |
| if (page === 0x3ff03) return this.shaRead(a, size); | |
| if (page >= 0x3ff00 && page < 0x3ff14) return this.dportRead(cpu, a, size); | |
| const d = this.devs.get(page); | |
| if (d) { | |
| const aligned = a & ~3; | |
| const v = d.dev.read(d.off + (aligned & 0xFFF), cpu) | 0; | |
| if (size === 4) return v; | |
| const sh = (a & 3) * 8; | |
| return size === 2 ? (v >>> sh) & 0xFFFF : (v >>> sh) & 0xFF; | |
| } | |
| if (a >= 0x3F400000 && a < 0x3FC00000) return 0xbaadbaad >> ((a & 3) * 8) & (size === 4 ? -1 : size === 2 ? 0xFFFF : 0xFF); | |
| return 0; | |
| } | |
| busWrite(cpu, addr, size, val) { | |
| this.curCpu = cpu.id; | |
| let a = addr; | |
| if (a >= 0x60000000 && a < 0x60100000) a = (a - 0x60000000 + 0x3ff40000) >>> 0; | |
| const page = a >>> 12; | |
| if (page === 0x3ff03) { this.sha.write(a & 0xFFF, val | 0); return; } | |
| if (page >= 0x3ff00 && page < 0x3ff14) { this.dportWrite(cpu, a, size, val); return; } | |
| const d = this.devs.get(page); | |
| if (d) { | |
| const off = d.off + (a & 0xFFC); | |
| if (size === 4) { d.dev.write(off, val | 0, cpu); return; } | |
| const sh = (a & 3) * 8; | |
| const mask = size === 2 ? 0xFFFF : 0xFF; | |
| // sub-word write: for FIFO-like registers (UART FIFO) write byte directly | |
| if (d.dev.writeSub) { d.dev.writeSub(off, a & 3, size, val, cpu); return; } | |
| const old = d.dev.read(off, cpu) | 0; | |
| d.dev.write(off, (old & ~(mask << sh)) | ((val & mask) << sh), cpu); | |
| } | |
| } | |
| shaRead(a, size) { const v = this.sha.read(a & 0xFFF); if (size === 4) return v; const sh = (a & 3) * 8; return size === 2 ? (v >>> sh) & 0xFFFF : (v >>> sh) & 0xFF; } | |
| rer() { return 0; } | |
| wer() {} | |
| // ---------------- DPORT ---------------- | |
| dportRead(cpu, a, size) { | |
| const off = a & 0x3FFF; | |
| let v = 0; | |
| if (a >= 0x3ff10000 && a < 0x3ff10400) v = this.mmu[0][(off - 0x0) / 4 & 0xFF] ; | |
| else if (a >= 0x3ff12000 && a < 0x3ff12400) v = this.mmu[1][((a - 0x3ff12000) / 4) & 0xFF]; | |
| else if (a >= 0x3ff10000 && a < 0x3ff11000) v = this.mmu[0][((a - 0x3ff10000) / 4) & 0xFF]; | |
| else if (a >= 0x3ff12000 && a < 0x3ff13000) v = this.mmu[1][((a - 0x3ff12000) / 4) & 0xFF]; | |
| else { | |
| const o = a - 0x3ff00000; | |
| switch (o) { | |
| case 0x2c: v = this.appResetState ? 1 : 0; break; | |
| case 0x30: v = this.appClk ? 1 : 0; break; | |
| case 0x34: v = this.appStall ? 1 : 0; break; | |
| case 0x38: v = this.appBoot; break; | |
| case 0x3c: v = this.cpuPerSel | 0; break; | |
| case 0x40: v = this.cacheCtrl[0]; break; | |
| case 0x44: v = this.cacheCtrl1[0]; break; | |
| case 0x58: v = this.cacheCtrl[1]; break; | |
| case 0x5c: v = this.cacheCtrl1[1]; break; | |
| case 0x3f0: case 0x418: v = 1 << 7; break; | |
| default: | |
| if (o >= 0x104 && o < 0x104 + NSRC * 4) v = this.intMap[0][(o - 0x104) >> 2]; | |
| else if (o >= 0x218 && o < 0x218 + NSRC * 4) v = this.intMap[1][(o - 0x218) >> 2]; | |
| else v = this.dport[(o >> 2) & 0x3FF] ; | |
| if (o >= 0xdc && o < 0xec) v = this.dport[(o >> 2)]; | |
| else if (!(o >= 0x104 && o < 0x104 + NSRC * 4) && !(o >= 0x218 && o < 0x218 + NSRC * 4)) v = 0; | |
| } | |
| } | |
| if (size === 4) return v | 0; | |
| const sh = (a & 3) * 8; return size === 2 ? (v >>> sh) & 0xFFFF : (v >>> sh) & 0xFF; | |
| } | |
| dportWrite(cpu, a, size, val) { | |
| if (size !== 4) { const old = this.dportRead(cpu, a & ~3, 4); const sh = (a & 3) * 8; const m = (size === 2 ? 0xFFFF : 0xFF) << sh; val = (old & ~m) | ((val << sh) & m); a &= ~3; } | |
| if (a >= 0x3ff10000 && a < 0x3ff11000) { const i = ((a - 0x3ff10000) / 4) & 0xFF; if (this.mmu[0][i] !== (val & 0x1FF)) { this.mmu[0][i] = val & 0x1FF; this.applyCache(0); } return; } | |
| if (a >= 0x3ff12000 && a < 0x3ff13000) { const i = ((a - 0x3ff12000) / 4) & 0xFF; if (this.mmu[1][i] !== (val & 0x1FF)) { this.mmu[1][i] = val & 0x1FF; this.applyCache(1); } return; } | |
| const o = a - 0x3ff00000; | |
| switch (o) { | |
| case 0x2c: { const old = this.appResetState; this.appResetState = (val & 1) !== 0; if (old && !this.appResetState) this.appCpuRelease(); this.updateAppRun(); break; } | |
| case 0x30: this.appClk = (val & 1) !== 0; this.updateAppRun(); break; | |
| case 0x34: this.appStall = (val & 1) !== 0; this.updateAppRun(); break; | |
| case 0x38: this.appBoot = val; break; | |
| case 0x3c: this.cpuPerSel = val & 3; this.clockUpdate(); break; | |
| case 0x40: if (val & 0x10) { val |= 0x20; val &= ~0x10; } this.cacheCtrl[0] = val; break; | |
| case 0x44: this.cacheCtrl1[0] = val; break; | |
| case 0x58: if (val & 0x10) { val |= 0x20; val &= ~0x10; } this.cacheCtrl[1] = val; break; | |
| case 0x5c: this.cacheCtrl1[1] = val; break; | |
| default: | |
| if (o >= 0xdc && o < 0xec) { this.dport[o >> 2] = val & 1; this.setIrq(24 + ((o - 0xdc) >> 2), val & 1); } | |
| else if (o >= 0x104 && o < 0x104 + NSRC * 4) { this.intMap[0][(o - 0x104) >> 2] = val & 31; this.updateIrq(); } | |
| else if (o >= 0x218 && o < 0x218 + NSRC * 4) { this.intMap[1][(o - 0x218) >> 2] = val & 31; this.updateIrq(); } | |
| } | |
| } | |
| appCpuRelease() { | |
| const c = this.cpus[1]; c.reset(); c.cyc = this.cpus[0].cyc; this.appPending = true; | |
| } | |
| updateAppRun() { | |
| const c = this.cpus[1]; | |
| c.stalled = this.appResetState || !this.appClk || this.appStall || this.rtc.appStall; | |
| } | |
| clockUpdate() { | |
| const r = this.rtc; | |
| let apb, cpu; | |
| if (r.socClk === 1) { apb = 80e6; cpu = [80e6, 160e6, 240e6][this.cpuPerSel | 0] || 80e6; } | |
| else { apb = 40e6; cpu = 40e6; } | |
| this.setClocks(cpu, apb); | |
| } | |
| // ---------------- system control ---------------- | |
| setFlash(data) { | |
| let size = 2 * 1024 * 1024; | |
| for (const s of [2, 4, 8, 16]) if (data.length <= s * 1024 * 1024) { size = s * 1024 * 1024; break; } | |
| if (data.length > FLASH_MAX) throw new Error('flash image too large'); | |
| this.flashSize = size; | |
| this.h8.fill(0xFF, H_FLASH, H_FLASH + FLASH_MAX); | |
| this.h8.set(data, H_FLASH); | |
| this.flashChip = new FlashChip(this.h8.subarray(H_FLASH, H_FLASH + size), size); | |
| this.flashDirty = false; | |
| } | |
| getFlash() { return this.h8.subarray(H_FLASH, H_FLASH + this.flashSize); } | |
| loadRoms(pro, app) { | |
| this.h8.set(pro.subarray(0, 0x70000), H_ROM_PRO); | |
| this.h8.set(app.subarray(0, 0x70000), H_ROM_APP); | |
| this.installRomHooks(); | |
| } | |
| enablePsram(on = true) { | |
| this.psram = on; | |
| this.psramChip = on ? new PsramChip(this.h8.subarray(H_PSRAM, H_PSRAM + PSRAM_SIZE), 4) : null; | |
| } | |
| reset(kind = 'power') { | |
| // power-on reset (RAM contents preserved, except at power on where we clear) | |
| if (kind === 'power') { this.h8.fill(0, H_RAM, H_RTCS + 0x2000); this.h8.fill(0, H_PSRAM, H_PSRAM + PSRAM_SIZE); } | |
| this.timeNs = this.timeNs || 0; | |
| this.events = new EventQueue(); | |
| this.srcLevel.fill(0); this.srcLevel1.fill(0); this.intMap[0].fill(6); this.intMap[1].fill(6); | |
| this.mmu[0].fill(0); this.mmu[1].fill(0); this.cacheCtrl = [0, 0]; this.cacheCtrl1 = [0, 0]; | |
| this.dport.fill(0); this.cpuPerSel = 0; this.appResetState = true; this.appClk = false; this.appStall = false; this.appBoot = 0; | |
| this.rtc.reset(kind); this.gpio.reset(); this.efuse.reset(); this.rtcio.reset(); this.sens.reset(); this.ulp.reset(); | |
| for (const d of Object.values(this.ps2)) { d.reset(); d.gpio.setPullup(d.clk, true); d.gpio.setPullup(d.dat, true); } | |
| for (const t of this.timg) t.reset(); for (const u of this.uart) u.reset(); | |
| for (const f of this.frc) f.reset(); | |
| for (const s of this.spi) if (s) s.reset(); | |
| for (const i of this.i2s) i.reset(); | |
| this.setClocks(40e6, 40e6); | |
| this.buildMaps(); | |
| for (const c of this.cpus) c.reset(); | |
| this.cpus[1].stalled = true; | |
| this.pendingReset = null; | |
| } | |
| requestReset(kind) { this.pendingReset = kind; } | |
| // ---------------- run loop ---------------- | |
| // run for `maxInstr` instructions on cpu0 worth of time (both cores); returns when done | |
| run(maxCycles) { | |
| const MAXSLICE = this.opts.slice || 256; | |
| let done = 0; | |
| const cpus = this.cpus; | |
| while (done < maxCycles) { | |
| if (this.pendingReset) { const k = this.pendingReset; this.reset(k === 'power' ? 'power' : 'soft'); } | |
| // compute slice length in instructions | |
| let slice = this.lockstep > 0 ? 2 : MAXSLICE; | |
| if (this.lockstep > 0) this.lockstep -= 2; | |
| const ev = this.events.peek(); | |
| const nsPerInstr = this.cpi * 1e9 / this.cpuHz; | |
| if (ev) { const dn = (ev.t - this.timeNs) / nsPerInstr; if (dn < slice) slice = Math.max(1, Math.ceil(dn)); } | |
| let allIdle = true; | |
| for (let i = 0; i < 2; i++) { | |
| const c = cpus[i]; | |
| // cpu timers | |
| let s = slice; | |
| const cc = (c.cyc * this.cpi + c.ccountOff) >>> 0; | |
| c.cyc = c.cyc; // keep | |
| for (let k = 0; k < 3; k++) { | |
| const bit = [6, 15, 16][k]; | |
| if (!((c.intSet >>> bit) & 1)) { | |
| const dt = (c.ccompare[k] - cc) >>> 0; | |
| const di = dt / this.cpi; | |
| if (di < s) s = Math.max(1, Math.ceil(di)); | |
| } | |
| } | |
| if (s < slice) slice = s; | |
| } | |
| for (let i = 0; i < 2; i++) { | |
| const c = cpus[i]; | |
| this.curCpu = i; this.inSlice = true; | |
| const wasHalted = c.halted || c.stalled; | |
| c.run(slice); | |
| this.inSlice = false; | |
| if (!(c.halted || c.stalled)) allIdle = false; | |
| if (i === 1 && this.appPending) { this.appPending = false; } | |
| // timer compare checks | |
| const cc = (c.cyc * this.cpi + c.ccountOff) >>> 0; | |
| for (let k = 0; k < 3; k++) { | |
| const bit = [6, 15, 16][k]; | |
| // match if ccompare is within (cc - slice*cpi, cc] | |
| const back = (cc - c.ccompare[k]) >>> 0; | |
| if (back <= slice * this.cpi + 1 && !((c.intSet >>> bit) & 1)) { | |
| if (this.tcheck(c, k, cc)) { c.raiseIntSet(1 << bit); } | |
| } | |
| } | |
| } | |
| this.timeNs += slice * this.cpi * 1e9 / this.cpuHz; | |
| done += slice; | |
| if (this.ulp.running) this.ulp.advance(this.timeNs); | |
| // events | |
| let ev2; | |
| while ((ev2 = this.events.peek()) && ev2.t <= this.timeNs) { this.events.pop(); ev2.fn(); } | |
| if (allIdle) { | |
| // all cores asleep: skip to next event | |
| const nx = this.events.peek(); | |
| let target = null; | |
| // cpu timers | |
| for (const c of cpus) { | |
| if (c.stalled) continue; | |
| const cc = (c.cyc * this.cpi + c.ccountOff) >>> 0; | |
| for (let k = 0; k < 3; k++) { | |
| const bit = [6, 15, 16][k]; | |
| if ((c.intenable >>> bit) & 1 && !((c.intSet >>> bit) & 1)) { | |
| const dt = (c.ccompare[k] - cc) >>> 0; | |
| const tn = this.timeNs + (dt / this.cpi) * (this.cpi * 1e9 / this.cpuHz); | |
| if (target === null || tn < target) target = tn; | |
| } | |
| } | |
| } | |
| if (nx && (target === null || nx.t < target)) target = nx.t; | |
| if (target === null) { this.idleForever = true; return done; } | |
| if (this.idleLimit !== undefined && target > this.idleLimit) { this.idleBlocked = true; return done; } | |
| const dt = target - this.timeNs; | |
| if (dt > 0) { | |
| const cycles = dt * this.cpuHz / 1e9 / this.cpi; | |
| for (const c of cpus) c.cyc += cycles; | |
| this.timeNs = target; | |
| } | |
| } | |
| } | |
| return done; | |
| } | |
| tcheck(c, k, cc) { | |
| return ((cc - c.ccompare[k]) >>> 0) < 0x40000000; | |
| } | |
| } | |
| // -------------- simple peripherals --------------- | |
| class Uart { | |
| constructor(soc, n) { this.soc = soc; this.n = n; this.reg = new Int32Array(0x100); this.reset(); } | |
| reset() { | |
| this.reg.fill(0); this.reg[0x14 >> 2] = 0x2B6; this.rx = []; this.baud = 115200; | |
| this.txThr = 0; this.rxThr = 0; this.toutEna = false; this.toutThr = 0; this.rawExtra = 0; | |
| this.soc.setIrq(34 + this.n, 0); | |
| } | |
| get src() { return 34 + this.n; } | |
| updateIrq() { | |
| const r = this.reg; | |
| let raw = r[1] & ~((1 << 0) | (1 << 1) | (1 << 14) | (1 << 8)); | |
| raw |= (this.rx.length >= this.rxThr && this.rxThr >= 0 && this.rx.length > 0 ? 1 : 0); | |
| raw |= 2; raw |= (1 << 14); // tx empty & tx done (fifo drained immediately) | |
| if (this.rxTimeout) raw |= (1 << 8); | |
| r[1] = raw; | |
| r[2] = raw & r[3]; | |
| this.soc.setIrq(this.src, r[2] !== 0); | |
| } | |
| rxPush(bytes) { | |
| for (const b of bytes) if (this.rx.length < 128) this.rx.push(b); | |
| this.rxTimeout = true; | |
| this.updateIrq(); | |
| } | |
| read(off) { | |
| const r = this.reg; | |
| switch (off) { | |
| case 0: { if (!this.rx.length) return 0xEE; const b = this.rx.shift(); if (!this.rx.length) this.rxTimeout = false; this.updateIrq(); return b; } | |
| case 0x1C: return this.rx.length | 0; | |
| case 0x28: case 0x2C: return 337; | |
| case 0x58: return (1 << 3) | (1 << 7); | |
| case 0x60: return (this.rx.length === 128 ? 0 : this.rx.length) << 13; | |
| case 0x78: return 0x15122500; | |
| default: return r[(off >> 2) & 0xFF]; | |
| } | |
| } | |
| write(off, v) { | |
| const r = this.reg; | |
| switch (off) { | |
| case 0: if (this.soc.onUart) this.soc.onUart(this.n, v & 0xFF); break; | |
| case 0x10: r[4] = v; r[2] &= ~v; if (v & (1 << 8)) this.rxTimeout = false; break; | |
| case 0xC: r[3] = v; break; | |
| case 0x14: { r[5] = v; const div = ((v & 0xFFFFF) << 4) + ((v >> 20) & 15); this.baud = div ? Math.floor((40000000 * 16) / div) : 115200; break; } | |
| case 0x18: r[6] = v; r[0x30 >> 2] = (v & 1) ? 0x3FF : 0; break; | |
| case 4: case 8: case 0x1C: break; | |
| case 0x24: r[9] = v; this.txThr = (v >> 8) & 0x7F; this.rxThr = v & 0x7F; this.toutEna = !!(v >>> 31); this.toutThr = (v >> 24) & 0x7F; break; | |
| default: r[(off >> 2) & 0xFF] = v; | |
| } | |
| this.updateIrq(); | |
| } | |
| writeSub(off, byteOff, size, val) { if (off === 0) this.write(0, val & 0xFF); else this.write(off, val); } | |
| } | |
| class Syscon { | |
| constructor(soc) { this.soc = soc; } | |
| read(off) { if (off === 0x78) return 0x51454d55; if (off === 0x7c) return 0x16042000 | 0x80000000; return 0; } | |
| write() {} | |
| } | |
| class Wdev { | |
| constructor(soc) { this.soc = soc; this.s = 0x1234567 >>> 0; } | |
| read(off) { if (off === 0x144) { let x = this.s; x ^= x << 13; x >>>= 0; x ^= x >>> 17; x ^= x << 5; x >>>= 0; this.s = x; return x | 0; } return 0; } | |
| write() {} | |
| } | |
| class Spi0 { | |
| constructor(soc) { this.soc = soc; this.r = new Int32Array(0x100); } | |
| read(off) { return this.r[(off >> 2) & 0xFF]; } | |
| write(off, v) { this.r[(off >> 2) & 0xFF] = v; } | |
| } | |
| class Efuse { | |
| constructor(soc) { this.soc = soc; this.reset(); } | |
| reset() { | |
| this.rd = new Int32Array(0x80); this.cmd = 0; this.conf = 0; | |
| this.rd[3] |= (1 << 15); this.rd[5] |= (1 << 20); // chip rev 3 | |
| this.rd[1] = 0x12345678; this.rd[2] = 0x00A4CC0D & 0xFFFF | 0x00A40000; // fake MAC | |
| this.intRaw = 0; this.intEna = 0; this.wr = new Int32Array(0x80); | |
| } | |
| read(off) { | |
| if (off < 0x98) return this.rd[off >> 2]; | |
| if (off === 0xf8) return 0; if (off === 0x100) return 0; | |
| if (off === 0x104) return this.cmd; if (off === 0x108) return this.intRaw; if (off === 0x1fc) return 0x16042600; | |
| return 0; | |
| } | |
| write(off, v) { | |
| if (off === 0x104) { if (v & 3) { this.cmd = v & 3; this.soc.scheduleIn(2000, () => { this.intRaw |= this.cmd; this.cmd = 0; }); } } | |
| else if (off === 0x114) this.intRaw &= ~v; | |
| else if (off === 0x110) this.intEna = v; | |
| } | |
| } | |
| class Frc { | |
| constructor(soc, n) { this.soc = soc; this.n = n; this.mask = n === 0 ? 0x7FFFFF : 0xFFFFFFFF; this.reset(); } | |
| reset() { this.load = 0; this.alarm = 0; this.countBase = 0; this.nsBase = 0; this.en = false; this.level = false; this.auto = false; this.presc = 1; this.status = false; this.ev = null; this.soc.setIrq(56 + this.n, 0); } | |
| clockChanged() { if (this.en) { this.countBase = this.count(); this.nsBase = this.soc.nowNs; this.arm(); } } | |
| count() { | |
| if (!this.en) return this.countBase; | |
| const ticks = Math.floor((this.soc.nowNs - this.nsBase) * this.soc.apbHz / 1e9 / this.presc); | |
| return (this.countBase + ticks) % (this.mask + 1); | |
| } | |
| arm() { | |
| this.soc.cancel(this.ev); this.ev = null; | |
| if (!this.en) return; | |
| let cur = this.count(), al = this.alarm & this.mask; | |
| let to = al - cur; if (to <= 0) to += this.mask + 1; | |
| const ns = to * this.presc * 1e9 / this.soc.apbHz; | |
| this.ev = this.soc.scheduleIn(ns, () => this.fire()); | |
| } | |
| fire() { | |
| this.ev = null; | |
| if (this.level) { this.status = true; this.soc.setIrq(56 + this.n, 1); } | |
| if (this.auto) { this.countBase = this.count(); this.nsBase = this.soc.nowNs; this.arm(); } | |
| } | |
| read(off) { | |
| switch (off) { | |
| case 0x00: return this.load; case 0x04: return this.count() | 0; | |
| case 0x08: return (this.level ? 1 : 0) | ((this.presc === 1 ? 0 : this.presc === 16 ? 2 : 4) << 1) | (this.auto ? 0x40 : 0) | (this.en ? 0x80 : 0) | (this.status ? 0x100 : 0); | |
| case 0x10: return this.alarm; | |
| } | |
| return 0; | |
| } | |
| write(off, v) { | |
| switch (off) { | |
| case 0x0c: if (v & 1) { this.status = false; this.soc.setIrq(56 + this.n, 0); } break; | |
| case 0x00: this.load = v; this.nsBase = this.soc.nowNs; this.countBase = v & this.mask; this.arm(); break; | |
| case 0x08: { | |
| this.countBase = this.count(); this.nsBase = this.soc.nowNs; | |
| this.level = !!(v & 1); const p = (v >> 1) & 7; this.presc = p === 2 ? 16 : p === 4 ? 256 : 1; | |
| this.auto = !!(v & 0x40); this.en = !!(v & 0x80); this.arm(); break; | |
| } | |
| case 0x10: this.alarm = v; this.arm(); break; | |
| } | |
| } | |
| } | |
| class FrcPage { | |
| constructor(soc) { this.soc = soc; } | |
| read(off) { return off < 0x20 ? this.soc.frc[0].read(off) : off < 0x40 ? this.soc.frc[1].read(off - 0x20) : 0; } | |
| write(off, v) { if (off < 0x20) this.soc.frc[0].write(off, v); else if (off < 0x40) this.soc.frc[1].write(off - 0x20, v); } | |
| } | |
| class RtcCntl { | |
| constructor(soc) { this.soc = soc; this.store = new Int32Array(8); this.reset('power'); } | |
| reset(kind) { | |
| this.socClk = 0; this.fastClk = 1; this.slowClk = 0; this.options0 = 0; this.timeBaseNs = this.soc.timeNs; | |
| this.swStall = 0; this.appStall = false; this.resetCause = kind === 'power' ? 1 : 3; this.statVec = 3 << 12; | |
| this.timeReg = 0; this.clkConf = 0; this.intEna = 0; this.intRaw = 0; | |
| this.regs = new Int32Array(0x200); | |
| if (kind === 'power') this.store.fill(0); | |
| } | |
| read(off) { | |
| switch (off) { | |
| case 0: return this.options0; | |
| case 0x0c: return 1 << 30; | |
| case 0x10: return this.timeReg % 4294967296 | 0; | |
| case 0x14: return Math.floor(this.timeReg / 4294967296) | 0; | |
| case 0x34: return (this.resetCause & 0x3f) | ((this.resetCause & 0x3f) << 6) | this.statVec; | |
| case 0x4c: case 0x50: case 0x54: case 0x58: return this.store[(off - 0x4c) >> 2]; | |
| case 0x3c: return this.intEna; case 0x40: return this.intRaw; case 0x44: return this.intRaw & this.intEna; | |
| case 0x70: return this.clkConf; | |
| case 0xac: return this.swStall; | |
| case 0xb0: case 0xb4: case 0xb8: case 0xbc: return this.store[4 + ((off - 0xb0) >> 2)]; | |
| case 0x13c: return 0x16042000; | |
| default: return 0; | |
| } | |
| } | |
| write(off, v) { | |
| const soc = this.soc; | |
| switch (off) { | |
| case 0: | |
| if (v & (1 << 31)) { this.resetCause = 3; soc.requestReset('soft'); v &= ~(1 << 31); } | |
| if (v & (1 << 5)) { this.resetCause = 12; soc.requestReset('soft'); v &= ~(1 << 5); } | |
| if (v & (1 << 4)) { v &= ~(1 << 4); soc.cpus[1].reset(); soc.appResetState = true; soc.updateAppRun(); } | |
| this.options0 = v; this.updateStall(); break; | |
| case 0x0c: if (v & (1 << 31)) this.timeReg = Math.floor(((soc.nowNs - this.timeBaseNs) / 1e9) * this.slowHz()); break; | |
| case 0x34: this.statVec = v & (3 << 12); break; | |
| case 0x3c: this.intEna = v; this.updateIrq(); break; | |
| case 0x48: this.intRaw &= ~v; this.updateIrq(); break; | |
| case 0x4c: case 0x50: case 0x54: case 0x58: this.store[(off - 0x4c) >> 2] = v; break; | |
| case 0x70: this.clkConf = v; this.socClk = (v >> 27) & 3; this.slowClk = (v >> 30) & 3; soc.clockUpdate(); break; | |
| case 0xac: this.swStall = v; this.updateStall(); break; | |
| case 0xb0: case 0xb4: case 0xb8: case 0xbc: this.store[4 + ((off - 0xb0) >> 2)] = v; break; | |
| } | |
| } | |
| ulpWake() { this.intRaw |= (1 << 5); this.updateIrq(); } | |
| updateIrq() { this.soc.setIrq(46, (this.intRaw & this.intEna) !== 0); } | |
| slowHz() { return [150000, 32768, 31250][this.slowClk] || 150000; } | |
| updateStall() { | |
| const m = 0x86; | |
| const proc = (((this.swStall >>> 26) & 0x3f) << 2) | ((this.options0 >> 2) & 3); | |
| const app = (((this.swStall >>> 20) & 0x3f) << 2) | (this.options0 & 3); | |
| this.soc.cpus[0].stalled = proc === m; | |
| this.appStall = app === m; this.soc.updateAppRun(); | |
| } | |
| } | |
| class RtcPage { | |
| constructor(soc) { this.soc = soc; } | |
| read(off, cpu) { if (off < 0x400) return this.soc.rtc.read(off); if (off < 0x800) return this.soc.rtcio.read(off - 0x400); if (off < 0xC00) return this.soc.sens.read(off - 0x800); return 0; } | |
| write(off, v, cpu) { if (off < 0x400) this.soc.rtc.write(off, v); else if (off < 0x800) this.soc.rtcio.write(off - 0x400, v); else if (off < 0xC00) this.soc.sens.write(off - 0x800, v); } | |
| } | |
| class TimerGroup { | |
| constructor(soc, n) { this.soc = soc; this.n = n; this.t = [new GTimer(this, 0), new GTimer(this, 1), new GTimer(this, 3)]; this.reset(); } | |
| reset() { | |
| this.intEna = 0; this.intRaw = 0; this.wdt = { cfg0: 0x0004c000, cfg1: 0x00010000, to: [0x018cba80, 0x07ffffff, 0xfffff, 0xfffff], prot: 0x50D83AA1 }; | |
| this.calMax = 1; this.calClkSel = 0; this.calStart = 1; this.calRdy = false; this.calValue = 0; | |
| for (const t of this.t) t.reset(); | |
| this.doCal(); | |
| this.updateIrq(); | |
| } | |
| clockChanged() { for (const t of this.t) t.rebase(); } | |
| doCal() { | |
| const f = this.calClkSel === 0 ? this.soc.rtc.slowHz() : this.calClkSel === 1 ? 32768 : 8000000 / 256; | |
| this.calValue = Math.floor(40000000 * this.calMax / f); this.calRdy = true; | |
| } | |
| updateIrq() { | |
| const st = this.intEna & this.intRaw; | |
| const base = this.n === 0 ? 14 : 18; | |
| for (let i = 0; i < 4; i++) this.soc.setIrq(base + i, (st >> i) & 1); | |
| } | |
| read(off) { | |
| if (off <= 0x20) return this.t[0].read(off); | |
| if (off <= 0x44) return this.t[1].read(off - 0x24); | |
| if (off >= 0x70 && off <= 0x94) return this.t[2].read(off, true); | |
| const w = this.wdt; | |
| switch (off) { | |
| case 0x48: return w.cfg0; case 0x4c: return w.cfg1; | |
| case 0x50: case 0x54: case 0x58: case 0x5c: return w.to[(off - 0x50) >> 2]; | |
| case 0x64: return w.prot; | |
| case 0x68: return (this.calStart << 31) | (this.calMax << 16) | ((this.calRdy ? 1 : 0) << 15) | (this.calClkSel << 13); | |
| case 0x6c: return this.calValue << 7; | |
| case 0x98: return this.intEna; case 0x9c: return this.intRaw; case 0xa0: return this.intEna & this.intRaw; | |
| } | |
| return 0; | |
| } | |
| write(off, v) { | |
| if (off <= 0x20) { this.t[0].write(off, v); return; } | |
| if (off <= 0x44) { this.t[1].write(off - 0x24, v); return; } | |
| if (off >= 0x70 && off <= 0x94) { this.t[2].write(off, v, true); return; } | |
| const w = this.wdt; | |
| switch (off) { | |
| case 0x48: if (w.prot === 0x50D83AA1) w.cfg0 = v; break; | |
| case 0x4c: if (w.prot === 0x50D83AA1) w.cfg1 = v; break; | |
| case 0x50: case 0x54: case 0x58: case 0x5c: if (w.prot === 0x50D83AA1) w.to[(off - 0x50) >> 2] = v; break; | |
| case 0x64: w.prot = v; break; | |
| case 0x68: this.calStart = (v >>> 31) & 1; this.calMax = (v >> 16) & 0x7FFF; this.calClkSel = (v >> 13) & 3; this.doCal(); break; | |
| case 0x98: this.intEna = v; this.updateIrq(); break; | |
| case 0xa4: this.intRaw &= ~v; this.updateIrq(); break; | |
| } | |
| } | |
| } | |
| class GTimer { | |
| constructor(tg, idx) { this.tg = tg; this.soc = tg.soc; this.idx = idx; this.reset(); } | |
| reset() { | |
| this.cfg = (1 << 30) | (1 << 29) | (1 << 13); this.alarmVal = 0; this.loadVal = 0; this.countBase = 0; this.nsBase = 0; this.last = 0; | |
| this.ev = null; this.parse(); | |
| } | |
| parse() { | |
| const c = this.cfg; | |
| this.en = !!(c >>> 31); this.inc = !!((c >> 30) & 1); this.auto = !!((c >> 29) & 1); | |
| let d = (c >> 13) & 0xFFFF; this.div = d === 0 ? 65536 : (d === 1 || d === 2) ? 2 : d; | |
| this.edge = !!((c >> 12) & 1); this.level = !!((c >> 11) & 1); this.alarmEn = !!((c >> 10) & 1); | |
| } | |
| count(now) { | |
| if (!this.en) return this.countBase; | |
| const ticks = Math.floor((now - this.nsBase) * this.soc.apbHz / 1e9 / this.div); | |
| return this.countBase + (this.inc ? ticks : -ticks); | |
| } | |
| rebase() { const now = this.soc.nowNs; this.countBase = this.count(now); this.nsBase = now; this.arm(); } | |
| arm() { | |
| this.soc.cancel(this.ev); this.ev = null; | |
| if (!this.en || !this.alarmEn) return; | |
| const now = this.soc.nowNs; | |
| const cur = this.count(now); | |
| const to = (this.alarmVal - cur) * (this.inc ? 1 : -1); | |
| if (to <= 0) return; | |
| const ns = to * this.div * 1e9 / this.soc.apbHz; | |
| this.ev = this.soc.schedule(now + ns, () => this.fire()); | |
| } | |
| fire() { | |
| const tg = this.tg; const bit = this.idx === 3 ? 3 : this.idx; | |
| this.ev = null; | |
| if (this.level) { tg.intRaw |= (1 << bit); tg.updateIrq(); } | |
| this.alarmEn = false; this.cfg &= ~(1 << 10); // alarm_en auto-clears | |
| if (this.auto) { this.reload(); this.alarmEn = true; this.cfg |= (1 << 10); this.arm(); } | |
| } | |
| reload() { this.nsBase = this.soc.nowNs; this.countBase = this.loadVal; } | |
| // offsets relative to timer base: 0 cfg, 4 lo, 8 hi, c update, 10 alarmlo, 14 alarmhi, 18 loadlo, 1c loadhi, 20 load | |
| // for LACT (lact=true) offsets are absolute: 0x70 cfg,0x74 rtc,0x78 lo,0x7c hi,0x80 update,0x84 alo,0x88 ahi,0x8c llo,0x90 lhi,0x94 load | |
| norm(off, lact) { if (!lact) return off; if (off === 0x70) return 0; if (off === 0x74) return -1; return off - 0x78 + 4; } | |
| read(off, lact) { | |
| off = this.norm(off, lact); | |
| switch (off) { | |
| case 0: return this.cfg; | |
| case 4: return this.last % 4294967296 | 0; | |
| case 8: return Math.floor(this.last / 4294967296) | 0; | |
| case 0x10: return this.alarmVal % 4294967296 | 0; case 0x14: return Math.floor(this.alarmVal / 4294967296) | 0; | |
| case 0x18: return this.loadVal % 4294967296 | 0; case 0x1c: return Math.floor(this.loadVal / 4294967296) | 0; | |
| } | |
| return 0; | |
| } | |
| write(off, v, lact) { | |
| off = this.norm(off, lact); | |
| switch (off) { | |
| case 0: this.rebase(); this.cfg = v & ~(1 << 10) | (v & (1 << 10)); this.parse(); this.nsBase = this.soc.nowNs; this.arm(); break; | |
| case 0xc: this.last = this.count(this.soc.nowNs); break; | |
| case 0x10: this.alarmVal = (Math.floor(this.alarmVal / 4294967296) * 4294967296) + (v >>> 0); this.arm(); break; | |
| case 0x14: this.alarmVal = (v >>> 0) * 4294967296 + (this.alarmVal % 4294967296); this.arm(); break; | |
| case 0x18: this.loadVal = (Math.floor(this.loadVal / 4294967296) * 4294967296) + (v >>> 0); break; | |
| case 0x1c: this.loadVal = (v >>> 0) * 4294967296 + (this.loadVal % 4294967296); break; | |
| case 0x20: this.reload(); this.arm(); break; | |
| } | |
| } | |
| } | |