11import unittest
2- from k0emu .devices import MemoryDevice , InterruptControllerDevice
2+ from k0emu .devices import MemoryDevice , InterruptControllerDevice , WatchTimerDevice
33from k0emu .processor import Processor , Flags
44
55
@@ -66,7 +66,7 @@ def test_low_priority_blocked_when_isp_set(self):
6666 proc .step ()
6767 self .assertEqual (proc .pc , 1 ) # blocked, not vectored
6868
69- def test_high_priority_not_blocked_when_isp_set (self ):
69+ def test_high_priority_blocked_when_isp_set (self ):
7070 proc , mem , intc , dev = _make_processor ()
7171 mem .write (0 , 0x00 ) # NOP
7272 mem .write (0x0006 , 0x00 )
@@ -77,7 +77,7 @@ def test_high_priority_not_blocked_when_isp_set(self):
7777 proc .write_psw (Flags .IE | Flags .ISP )
7878 proc .pc = 0
7979 proc .step ()
80- self .assertEqual (proc .pc , 0x1000 )
80+ self .assertEqual (proc .pc , 1 ) # blocked, not vectored
8181
8282
8383class InterruptEntryTests (unittest .TestCase ):
@@ -255,3 +255,110 @@ def test_inttm52_vectors_to_003c(self):
255255 proc .pc = 0
256256 proc .step ()
257257 self .assertEqual (proc .pc , 0x2000 )
258+
259+
260+ def _make_processor_with_timer ():
261+ """Create a processor with memory, interrupt controller, and watch timer."""
262+ proc = Processor ()
263+ mem = MemoryDevice ("test_memory" , size = 0xFF41 )
264+ proc .bus .add_device (mem , (0x0000 , 0xFF40 ))
265+ intc = InterruptControllerDevice ("intc" )
266+ proc .bus .add_device (intc , (0xFFE0 , 0xFFEB ))
267+ proc .bus .set_interrupt_controller (intc )
268+ wt = WatchTimerDevice ("watch_timer" )
269+ proc .bus .add_device (wt , (0xFF41 , 0xFF41 ))
270+ intc .connect (wt , wt .INT_PRESCALER , intc .INTWTNI0 )
271+ return proc , mem , intc , wt
272+
273+
274+ class HaltTests (unittest .TestCase ):
275+ """Tests for the HALT instruction (0x71 0x10)."""
276+
277+ def test_halt_wakes_on_pending_interrupt (self ):
278+ proc , mem , intc , wt = _make_processor_with_timer ()
279+ # HALT at address 0
280+ mem .write (0 , 0x71 )
281+ mem .write (1 , 0x10 )
282+ # ISR at vector 0x0024 (INTWTNI0) points to 0x2000
283+ mem .write (0x0024 , 0x00 )
284+ mem .write (0x0025 , 0x20 )
285+ # Enable watch timer: WTNM00=1, n=0, fw=128 -> interval=2048
286+ wt .write (0 , 0x01 )
287+ # Unmask INTWTNI0
288+ intc .write (intc .MK1L , intc .read (intc .MK1L ) & 0xFE )
289+ proc .write_psw (Flags .IE )
290+ proc .write_sp (0xFE00 )
291+ proc .pc = 0
292+ proc .step ()
293+ # HALT should have waited for the timer, then serviced the interrupt
294+ self .assertEqual (proc .pc , 0x2000 )
295+
296+ def test_halt_advances_cycles (self ):
297+ proc , mem , intc , wt = _make_processor_with_timer ()
298+ mem .write (0 , 0x71 )
299+ mem .write (1 , 0x10 )
300+ mem .write (0x0024 , 0x00 )
301+ mem .write (0x0025 , 0x20 )
302+ wt .write (0 , 0x01 ) # interval=2048
303+ intc .write (intc .MK1L , intc .read (intc .MK1L ) & 0xFE )
304+ proc .write_psw (Flags .IE )
305+ proc .write_sp (0xFE00 )
306+ proc .pc = 0
307+ cycles_before = proc .total_cycles
308+ proc .step ()
309+ # Should have waited ~2048 cycles for the timer
310+ elapsed = proc .total_cycles - cycles_before
311+ self .assertGreaterEqual (elapsed , 2048 )
312+
313+ def test_halt_does_not_double_tick (self ):
314+ proc , mem , intc , wt = _make_processor_with_timer ()
315+ mem .write (0 , 0x71 )
316+ mem .write (1 , 0x10 )
317+ mem .write (0x0024 , 0x00 )
318+ mem .write (0x0025 , 0x20 )
319+ wt .write (0 , 0x01 ) # interval=2048
320+ intc .write (intc .MK1L , intc .read (intc .MK1L ) & 0xFE )
321+ proc .write_psw (Flags .IE )
322+ proc .write_sp (0xFE00 )
323+ proc .pc = 0
324+ proc .step ()
325+ # The timer should have fired exactly once (no double-ticking).
326+ # After firing, the prescaler counter resets to a small remainder.
327+ self .assertLess (wt ._prescaler_counter , 100 )
328+
329+ def test_halt_with_interrupt_already_pending (self ):
330+ proc , mem , intc , wt = _make_processor_with_timer ()
331+ mem .write (0 , 0x71 )
332+ mem .write (1 , 0x10 )
333+ mem .write (0x0024 , 0x00 )
334+ mem .write (0x0025 , 0x20 )
335+ # Set IF flag before HALT
336+ intc .write (intc .IF1L , 0x01 ) # INTWTNI0 already pending
337+ intc .write (intc .MK1L , intc .read (intc .MK1L ) & 0xFE )
338+ proc .write_psw (Flags .IE )
339+ proc .write_sp (0xFE00 )
340+ proc .pc = 0
341+ cycles_before = proc .total_cycles
342+ proc .step ()
343+ # Should wake almost immediately
344+ self .assertEqual (proc .pc , 0x2000 )
345+ elapsed = proc .total_cycles - cycles_before
346+ # Only the HALT fetch cycles + 1 tick to detect the pending interrupt
347+ self .assertLess (elapsed , 20 )
348+
349+ def test_halt_resumes_at_next_instruction_when_ie_disabled (self ):
350+ proc , mem , intc , wt = _make_processor_with_timer ()
351+ # HALT at address 0, NOP at address 2
352+ mem .write (0 , 0x71 )
353+ mem .write (1 , 0x10 )
354+ mem .write (2 , 0x00 ) # NOP
355+ # Set IF flag but IE=0 so interrupt won't be serviced
356+ intc .write (intc .IF1L , 0x01 )
357+ intc .write (intc .MK1L , intc .read (intc .MK1L ) & 0xFE )
358+ proc .write_psw (0 ) # IE=0
359+ proc .write_sp (0xFE00 )
360+ proc .pc = 0
361+ proc .step ()
362+ # HALT wakes on the unmasked interrupt request but since IE=0,
363+ # it executes the next instruction instead of vectoring
364+ self .assertEqual (proc .pc , 2 )
0 commit comments