/** * EEPROM regression tests (GitHub issue #203 — "Arduino EEPROM"). * * Before the fix, AVRSimulator never instantiated avr8js's AVREEPROM * peripheral, so the Arduino EEPROM library's `while (EECR & (1< (n & 0xff).toString(16).toUpperCase().padStart(2, '0'); const bytes: number[] = []; for (const w of words) bytes.push(w & 0xff, (w >> 8) & 0xff); let out = ''; for (let i = 0; i < bytes.length; i += 16) { const chunk = bytes.slice(i, i + 16); let sum = chunk.length + ((i >> 8) & 0xff) + (i & 0xff); let rec = `:${hh(chunk.length)}${hh(i >> 8)}${hh(i)}00`; for (const b of chunk) { rec += hh(b); sum += b; } out += `${rec}${hh((0x100 - (sum & 0xff)) & 0xff)}\n`; } return `${out}:00000001FF\n`; } /** Set EEAR to `addr` (r16 is clobbered). */ const setEepromAddr = (addr: number) => [ LDI(16, addr & 0xff), STS(EEARL, 16), LDI(16, (addr >> 8) & 0xff), STS(EEARH, 16), ]; /** * Write `value` to EEPROM `addr` (atomic), poll EEPE until the write * completes, then read it back into r20 — mirroring avr-libc's * eeprom_write_byte / eeprom_read_byte sequence. */ function eepromWritePollReadProgram(addr: number, value: number): Uint16Array { return assemble([ ...setEepromAddr(addr), LDI(16, value), STS(EEDR, 16), LDI(16, EEMPE), STS(EECR, 16), // EEMPE = 1 LDI(16, EEPE), STS(EECR, 16), // EEPE = 1 → start atomic write (within 4 cycles of EEMPE) // poll: while (EECR & EEPE) ; LDS(17, EECR), SBRC(17, 1), // skip the RJMP once EEPE (bit 1) is clear RJMP(-4), // read back ...setEepromAddr(addr), LDI(16, EERE), STS(EECR, 16), // EERE = 1 → EEDR = backend[addr] LDS(20, EEDR), // r20 = read value RJMP(-1), // spin ]); } describe('EEPROM (issue #203)', () => { let pm: PinManager; let sim: AVRSimulator; beforeEach(() => { pm = new PinManager(); sim = new AVRSimulator(pm); }); afterEach(() => sim.stop()); it('round-trips a byte and the EEPE write-completion poll terminates (no hang)', () => { sim.loadHex(toIntelHex(eepromWritePollReadProgram(0, 123))); // Step until the program reaches its read (r20 == 123). The cap is the // regression guard: with the EEPROM peripheral missing, EEPE never clears, // the poll spins forever, r20 stays 0, and the cap is hit -> assertion fails. const cpu = (sim as unknown as { cpu: { data: Uint8Array } }).cpu; for (let i = 0; i < 300000 && cpu.data[20] !== 123; i++) sim.step(); expect(cpu.data[20]).toBe(123); const backend = (sim as unknown as { eepromBackend: { readMemory(a: number): number } }) .eepromBackend; expect(backend.readMemory(0)).toBe(123); }); it('persists EEPROM contents across a reset (between boots)', () => { sim.loadHex(toIntelHex(eepromWritePollReadProgram(7, 42))); const cpu = (sim as unknown as { cpu: { data: Uint8Array } }).cpu; for (let i = 0; i < 300000 && cpu.data[20] !== 42; i++) sim.step(); expect(cpu.data[20]).toBe(42); // A reset re-boots the CPU but must leave EEPROM intact. sim.reset(); const backend = (sim as unknown as { eepromBackend: { readMemory(a: number): number } }) .eepromBackend; expect(backend.readMemory(7)).toBe(42); }); });