Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MCLZ8 issue with halt? #15

Open
lintweaker opened this issue Feb 25, 2023 · 12 comments
Open

MCLZ8 issue with halt? #15

lintweaker opened this issue Feb 25, 2023 · 12 comments

Comments

@lintweaker
Copy link

lintweaker commented Feb 25, 2023

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

`

@MicroCoreLabs
Copy link
Owner

MicroCoreLabs commented Feb 25, 2023 via email

@lintweaker
Copy link
Author

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.

@MicroCoreLabs
Copy link
Owner

MicroCoreLabs commented Feb 26, 2023 via email

@lintweaker
Copy link
Author

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.

@MicroCoreLabs
Copy link
Owner

MicroCoreLabs commented Feb 28, 2023 via email

@lintweaker
Copy link
Author

This also did not fix the issue. It gets stuck in HALT. The 'register_pc--' in halt should really be changed I think.

@MicroCoreLabs
Copy link
Owner

MicroCoreLabs commented Mar 2, 2023 via email

@hlide
Copy link

hlide commented Mar 5, 2023

@lintweaker

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.

@hlide
Copy link

hlide commented Mar 12, 2023

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.

@MicroCoreLabs
Copy link
Owner

MicroCoreLabs commented Mar 13, 2023 via email

@lintweaker
Copy link
Author

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.

@MicroCoreLabs
Copy link
Owner

MicroCoreLabs commented Mar 13, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants