MicroCoreLabs / Projects

Ted Fried's MicroCore Labs Projects which include microsequencer-based FPGA cores and emulators for the 8088, 8086, 8051, 6502, 68000, Z80, Risc-V, and also Typewriter and EPROM Emulator projects. MCL51, MCL64, MCL65, MCL65+, MCL68, MCL86, MCL86+, MCL86jr, MCLR5, MCLZ8
372 stars 78 forks source link

MCLZ8 issue with halt? #15

Open lintweaker opened 1 year ago

lintweaker commented 1 year ago

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 commented 1 year ago

I believe EI should be the last opcode before the RETI opcode in an interrupt routine. I think you are getting an infinite loop because you are receiving an interrupt between the EI and WAIT opcodes, and once the interrupt finishes with the RETI and runs the next opcode which is HALT, there is no opportunity to re-enable interrupts and you get an infinite loop inside of HALT.


From: Jurgen Kramer @.> Sent: Saturday, February 25, 2023 2:31 AM To: MicroCoreLabs/Projects @.> Cc: Subscribed @.***> Subject: [MicroCoreLabs/Projects] MCLZ8 issue with halt? (Issue #15)

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 halt // Wait for interrupt ret

— Reply to this email directly, view it on GitHubhttps://github.com/MicroCoreLabs/Projects/issues/15, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM4AVEOY6HUIKBHKISPNKV3WZHNPDANCNFSM6AAAAAAVHZBTWU. You are receiving this because you are subscribed to this thread.Message ID: @.***>

lintweaker commented 1 year ago

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 commented 1 year ago

I decrement the PC in HALT just so I can replay the same opcode. I also assert halt_in_progress which when we are in the interrupt handler code which causes the PC we push to the stack to be +1 which should be the next opcode after HALT. I believe this method is correct as far as I can tell.

void opcode_0xFB() { register_iff1=1; register_iff2=1; last_instruction_set_a_prefix=0; return; } // ei

Could you please change this line of code to this: last_instruction_set_a_prefix=1;            This will delay the enabling of interrupts by one opcode which is described in the Z80 datasheet.


From: Jurgen Kramer @.> Sent: Sunday, February 26, 2023 3:00 AM To: MicroCoreLabs/Projects @.> Cc: MicroCore Labs @.>; Comment @.> Subject: Re: [MicroCoreLabs/Projects] MCLZ8 issue with halt? (Issue #15)

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.

— Reply to this email directly, view it on GitHubhttps://github.com/MicroCoreLabs/Projects/issues/15#issuecomment-1445329609, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM4AVEMYKIYR3WOILP7PDF3WZMZVRANCNFSM6AAAAAAVHZBTWU. You are receiving this because you commented.Message ID: @.***>

lintweaker commented 1 year ago

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 commented 1 year ago

Ok, could you try changing this: if (halt_in_progress==1) Push(register_pc+1); else Push(register_pc);

to this: if (halt_in_progress==1) Push(register_pc+2); else Push(register_pc);


From: Jurgen Kramer @.> Sent: Monday, February 27, 2023 8:10 AM To: MicroCoreLabs/Projects @.> Cc: MicroCore Labs @.>; Comment @.> Subject: Re: [MicroCoreLabs/Projects] MCLZ8 issue with halt? (Issue #15)

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.

— Reply to this email directly, view it on GitHubhttps://github.com/MicroCoreLabs/Projects/issues/15#issuecomment-1446610125, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM4AVELX722MKCMKOSJFWW3WZTGVRANCNFSM6AAAAAAVHZBTWU. You are receiving this because you commented.Message ID: @.***>

lintweaker commented 1 year ago

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

MicroCoreLabs commented 1 year ago

Is this something that you can try?


From: Jurgen Kramer @.> Sent: Thursday, March 2, 2023 8:47 AM To: MicroCoreLabs/Projects @.> Cc: MicroCore Labs @.>; Comment @.> Subject: Re: [MicroCoreLabs/Projects] MCLZ8 issue with halt? (Issue #15)

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

— Reply to this email directly, view it on GitHubhttps://github.com/MicroCoreLabs/Projects/issues/15#issuecomment-1452187158, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM4AVEJ45JP64VI2PZX3SY3W2DFI7ANCNFSM6AAAAAAVHZBTWU. You are receiving this because you commented.Message ID: @.***>

hlide commented 1 year ago

@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 commented 1 year ago

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 commented 1 year ago

When I single-step through a HALT and an interrupt occurs, the interrupt is taken, and after the RET opcode the code resumes on the opcode after HALT which is correct.

Something is new/different with your setup where interrupts are probably masked when a HALT is executed which would stall the CPU forever - or until an NMI occurs. Or maybe you have two consecutive HALT opcodes where the first one is escaped by the interrupt, but the second one is not...

Adding some printf's and/or single-stepping when you encounter a HALT would be useful. So would a list of the opcodes are run during the interrupt sequence.


From: hlide @.> Sent: Sunday, March 12, 2023 4:31 AM To: MicroCoreLabs/Projects @.> Cc: MicroCore Labs @.>; Comment @.> Subject: Re: [MicroCoreLabs/Projects] MCLZ8 issue with halt? (Issue #15)

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.

— Reply to this email directly, view it on GitHubhttps://github.com/MicroCoreLabs/Projects/issues/15#issuecomment-1465168754, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM4AVENEEY45CEU34OFBP63W3WXY5ANCNFSM6AAAAAAVHZBTWU. You are receiving this because you commented.Message ID: @.***>

lintweaker commented 1 year ago

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 commented 1 year ago

Thanks. I single-stepped with back to back HALTs, and interrupts enabled, disabled, and asserted in middle of instructions and I could not observe a hang. Is it possible that your interrupt is actually not asserted? The only thing stopping HALT from exiting is either interrupt is masked or becasue it is not asserted. A printf would show this.


From: Jurgen Kramer @.> Sent: Monday, March 13, 2023 9:17 AM To: MicroCoreLabs/Projects @.> Cc: MicroCore Labs @.>; Comment @.> Subject: Re: [MicroCoreLabs/Projects] MCLZ8 issue with halt? (Issue #15)

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.

— Reply to this email directly, view it on GitHubhttps://github.com/MicroCoreLabs/Projects/issues/15#issuecomment-1466458995, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AM4AVEMS7MTIGYGAYSXQMYLW35CCLANCNFSM6AAAAAAVHZBTWU. You are receiving this because you commented.Message ID: @.***>