From 39fe0472ce7b7f54438e69f47705086bc60d9716 Mon Sep 17 00:00:00 2001 From: Uri Shaked Date: Fri, 10 Sep 2021 01:35:06 +0300 Subject: feat(watchdog): implement watchdog timer #106 --- src/peripherals/watchdog.spec.ts | 210 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 src/peripherals/watchdog.spec.ts (limited to 'src/peripherals/watchdog.spec.ts') diff --git a/src/peripherals/watchdog.spec.ts b/src/peripherals/watchdog.spec.ts new file mode 100644 index 0000000..6fea3b3 --- /dev/null +++ b/src/peripherals/watchdog.spec.ts @@ -0,0 +1,210 @@ +/** + * AVR8 Watchdog Timer Test Suite + * Part of AVR8js + * + * Copyright (C) 2021 Uri Shaked + */ + +import { AVRClock, clockConfig } from '..'; +import { CPU } from '../cpu/cpu'; +import { asmProgram, TestProgramRunner } from '../utils/test-utils'; +import { AVRWatchdog, watchdogConfig } from './watchdog'; + +const R20 = 20; + +const MCUSR = 0x54; +const WDRF = 1 << 3; + +const WDTCSR = 0x60; +const WDP0 = 1 << 0; +const WDP1 = 1 << 1; +const WDP2 = 1 << 2; +const WDE = 1 << 3; +const WDCE = 1 << 4; +const WDP3 = 1 << 5; +const WDIE = 1 << 6; + +const INT_WDT = 0xc; + +describe('Watchdog', () => { + it('should correctly calculate the prescaler from WDTCSR', () => { + const cpu = new CPU(new Uint16Array(1024)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock); + cpu.writeData(WDTCSR, WDCE | WDE); + cpu.writeData(WDTCSR, 0); + expect(watchdog.prescaler).toEqual(2048); + cpu.writeData(WDTCSR, WDP2 | WDP1 | WDP0); + expect(watchdog.prescaler).toEqual(256 * 1024); + cpu.writeData(WDTCSR, WDP3 | WDP0); + expect(watchdog.prescaler).toEqual(1024 * 1024); + }); + + it('should not change the prescaler unless WDCE is set', () => { + const cpu = new CPU(new Uint16Array(1024)); + const clock = new AVRClock(cpu, 16e6, clockConfig); + const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock); + cpu.writeData(WDTCSR, 0); + expect(watchdog.prescaler).toEqual(2048); + cpu.writeData(WDTCSR, WDP2 | WDP1 | WDP0); + expect(watchdog.prescaler).toEqual(2048); + + cpu.writeData(WDTCSR, WDCE | WDE); + cpu.cycles += 5; // WDCE should expire after 4 cycles + cpu.writeData(WDTCSR, WDP2 | WDP1 | WDP0); + expect(watchdog.prescaler).toEqual(2048); + }); + + it('should reset the CPU when the timer expires', () => { + const { program } = asmProgram(` + ; register addresses + _REPLACE WDTCSR, ${WDTCSR} + + ; Setup watchdog + ldi r16, ${WDE | WDCE} + sts WDTCSR, r16 + ldi r16, ${WDE} + sts WDTCSR, r16 + + nop + + break + `); + const cpu = new CPU(program); + const clock = new AVRClock(cpu, 16e6, clockConfig); + const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock); + const runner = new TestProgramRunner(cpu); + + // Setup: enable watchdog timer + runner.runInstructions(4); + expect(watchdog.enabled).toBe(true); + + // Now we skip 8ms. Watchdog shouldn't fire, yet + cpu.cycles += 16000 * 8; + runner.runInstructions(1); + + // Now we skip an extra 8ms. Watchdog should fire and reset! + cpu.cycles += 16000 * 8; + cpu.tick(); + expect(cpu.pc).toEqual(0); + expect(cpu.readData(MCUSR)).toEqual(WDRF); + }); + + it('should extend the watchdog timeout when executing a WDR instruction', () => { + const { program } = asmProgram(` + ; register addresses + _REPLACE WDTCSR, ${WDTCSR} + + ; Setup watchdog + ldi r16, ${WDE | WDCE} + sts WDTCSR, r16 + ldi r16, ${WDE} + sts WDTCSR, r16 + + wdr + nop + + break + `); + const cpu = new CPU(program); + const clock = new AVRClock(cpu, 16e6, clockConfig); + const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock); + const runner = new TestProgramRunner(cpu); + + // Setup: enable watchdog timer + runner.runInstructions(4); + expect(watchdog.enabled).toBe(true); + + // Now we skip 8ms. Watchdog shouldn't fire, yet + cpu.cycles += 16000 * 8; + runner.runInstructions(1); + + // Now we skip an extra 8ms. We extended the timeout with WDR, so watchdog won't fire yet + cpu.cycles += 16000 * 8; + runner.runInstructions(1); + + // Finally, another 8ms bring us to 16ms since last WDR, and watchdog should fire + cpu.cycles += 16000 * 8; + cpu.tick(); + expect(cpu.pc).toEqual(0); + }); + + it('should fire an interrupt when the watchdog expires and WDIE is set', () => { + const { program } = asmProgram(` + ; register addresses + _REPLACE WDTCSR, ${WDTCSR} + + ; Setup watchdog + ldi r16, ${WDE | WDCE} + sts WDTCSR, r16 + ldi r16, ${WDE | WDIE} + sts WDTCSR, r16 + + nop + sei + + break + `); + const cpu = new CPU(program); + const clock = new AVRClock(cpu, 16e6, clockConfig); + const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock); + const runner = new TestProgramRunner(cpu); + + // Setup: enable watchdog timer + runner.runInstructions(4); + expect(watchdog.enabled).toBe(true); + + // Now we skip 8ms. Watchdog shouldn't fire, yet + cpu.cycles += 16000 * 8; + runner.runInstructions(1); + + // Now we skip an extra 8ms. Watchdog should fire and jump to the interrupt handler + cpu.cycles += 16000 * 8; + runner.runInstructions(1); + + expect(cpu.pc).toEqual(INT_WDT); + // The watchdog timer should also clean the WDIE bit, so next timeout will reset the MCU. + expect(cpu.readData(WDTCSR) & WDIE).toEqual(0); + }); + + it('should not reset the CPU if the watchdog has been disabled', () => { + const { program } = asmProgram(` + ; register addresses + _REPLACE WDTCSR, ${WDTCSR} + + ; Setup watchdog + ldi r16, ${WDE | WDCE} + sts WDTCSR, r16 + ldi r16, ${WDE} + sts WDTCSR, r16 + + ; disable watchdog + ldi r16, ${WDE | WDCE} + sts WDTCSR, r16 + ldi r16, 0 + sts WDTCSR, r16 + + ldi r20, 55 + + break + `); + const cpu = new CPU(program); + const clock = new AVRClock(cpu, 16e6, clockConfig); + const watchdog = new AVRWatchdog(cpu, watchdogConfig, clock); + const runner = new TestProgramRunner(cpu); + + // Setup: enable watchdog timer + runner.runInstructions(4); + expect(watchdog.enabled).toBe(true); + + // Now we skip 8ms. Watchdog shouldn't fire, yet. We disable it. + cpu.cycles += 16000 * 8; + runner.runInstructions(4); + + // Now we skip an extra 20ms. Watchdog shouldn't reset! + cpu.cycles += 16000 * 20; + runner.runInstructions(1); + expect(cpu.pc).not.toEqual(0); + expect(cpu.data[R20]).toEqual(55); // assert that `ldi r20, 55` ran + }); +}); -- cgit v1.2.3