MCLZ8 issue with halt?
lintweaker opened this issue · comments
Thanks for the great MCZL8 solution! I am having a lot of fun using it with my MSX.
It looks like the HALT instruction is not working as it should. When a HALT is issued, it gets repeating the halt instruction.
I think the 'register_pc--' should not be there?
`void opcode_0x76() { // Halt
digitalWriteFast(PIN_HALT,0x0);
halt_in_progress=1;
register_pc--;
return;
}
`
Tested this code in 'IM 1' mode. After a HALT is issued, an interrupt should resume the CPU but it does not.
`
ei // Enable interrupts
halt // Wait for interrupt
ret
`
Thanks for you reply. On MSX the RETI instruction is not used, only the normal RET is used, also in the interrupt handler.
MSX uses interrupt mode 1 (IM 1). Looking at the Z80 user manual (UM008011-0816) the HALT instruction does not lower the program counter as the current implementation of MCLZ8 is doing.
executing:
ei
halt // wait for inteerupt to occur
should work. Tested on a MSX with a real Z80 and MSX emulator.
void opcode_0xFB() { register_iff1=1; register_iff2=1; last_instruction_set_a_prefix=1; return; } // ei
did not help, does not get out of halt.
This also did not fix the issue. It gets stuck in HALT. The 'register_pc--' in halt should really be changed I think.
uint8_t Fetch_opcode()
is always incrementing register_pc
so there is a real reason to decrementing register_pc
when executing HALT. So the issue may be elsewhere.
May I suggest for you to add a USB debug trace Serial.printf("INT entered!\n");
in INTR_Handler() to check that you effectively enter that interrupt handling? you could then check if you enter if (halt_in_progress==1)
.
Note that /INT should be scanned at the RISING edge of the last T-state of the last M-cycle of the current instruction so the next instruction would be interrupted. And MCLZ8 is not fully respecting that with while (clock_counter>0) { wait_for_CLK_falling_edge(); }
after the call to execute_instruction();
. Of course I don't think it is the real issue because if the period of /INT = 0 is long enough, it would be catched on the next instruction.
DI and EI inhibit the interrupt on the next instruction. So I would expect for a pause_interrupts = 1;
in those instructions (and not last_instruction_set_a_prefix=0;
which makes no sense) but it seems that pause_interrupts
is currently never set to 1 anywhere.
When INTR_Handler();
is called, you must have direct_int == 0
(that is /INT was scanned as 0) and register_iff1==1
. And register_pc
should point HALT if in progress. If so, Push(register_pc+1);
should be right to skip HALT after the execution of the interrupt. So I strongly recommend for you to add some debug traces to check whether you are really entering those paths.
When a software HALT instruction is executed, the CPU executes NOPs until an interrupt is received (either a nonmaskable or a maskable interrupt while the interrupt flip-flop is enabled). [...]. If a nonmaskable interrupt is received or a maskable interrupt is received and the interrupt enable flip-flop is set, then the HALT state is exited on the next rising clock edge. The following cycle is an interrupt acknowledge cycle corresponding to the type of interrupt that was received. If both are received at this time, then the nonmaskable interrupt is acknowledged because it is the highest priority. The purpose of executing NOP instructions while in the HALT state is to keep the memory refresh signals active. Each cycle in the HALT state is a normal M1 (fetch) cycle except that the data received from the memory is ignored and an NOP instruction is forced internally to the CPU. The HALT acknowledge signal is active during this time indicating that the processor is in the HALT state.
The halt state can be exited by a maskable interrupt (if enabled), a non-maskable interrupt or either type of reset. Until one of these happens databooks say that "the CPU executes NOPs to maintain memory refresh" without giving any more detail. HALT needs to be executed only once to place the CPU in the halt state and if the HALT opcode is continually read thereafter this would have the same effect presumably as executing NOPs. If HALT were replaced by NOP after /HALT has gone low would this be another way to exit the halt state?
The answer is no. When /HALT is low PC has already been incremented and the opcode fetched is for the instruction after HALT. The halt state stops this instruction from being executed and PC from incrementing so this opcode is read again and again until an exit condition occurs. When an interrupt occurs during the halt state PC is pushed unchanged onto the stack as it is already the correct return address. This is no different from an interrupt when not halted as INT and NMI are sampled and accepted at the end of an instruction by which time PC has incremented. (N.B. What "The Undocumented Z80 Documented" says about HALT and PC is wrong.)
So what happens is PC is really incremented when executing HALT. HALT will then set a HALT state as active. Every consecutive opcode fetching won't increment PC if HALT state is active and just be executed as a NOP (opcode = 0) while ignoring the read data as an opcode. So there is no need to modify PC when releasing the HALT state after scanning INT/NMI/RESET at the rising edge of T4 of our "HALT" NOPs if that behavior is reproduced.
I need to find time to properly dive into this. I found some other code which I can use as an additional test case. It uses a few HALTs in a row the get a little delay.