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

Incorrect halted CPU behaviour #40

Open
simonowen opened this issue Oct 25, 2021 · 2 comments
Open

Incorrect halted CPU behaviour #40

simonowen opened this issue Oct 25, 2021 · 2 comments
Labels

Comments

@simonowen
Copy link
Collaborator

The traditional way to implement HALT has been to keep PC on the same instruction and execute NOPs. PC is incremented as part of acknowledging the next interrupt to step over it. However, this has been shown to not match the real Z80 CPU behaviour, and it's possible to detect the difference in code.

The following article describes it, in the section "Halt and the special reset":
http://www.primrosebank.net/computers/z80/z80_special_reset.htm

When HALT is executed it puts the CPU into a halted state (already implemented in your core). It also advances PC to point to the next instruction. During the halted state the opcode fetch runs on this new PC value but a NOP is executed instead, and PC isn't advanced. When an interrupt occurs the halted state is cleared and execution continues from the current point as normal.

It's possible to detect this behaviour with a HALT in the last byte before a contention boundary, such as 0x7FFF on the ZX Spectrum. The incorrect behaviour reads from 0x7FFF for each NOP executed at the HALT, which has more contention than the correct fetches from 0x8000. The difference in timing can be detected by measuring how much has R changed when the next interrupt is ackknowledged.

simonowen added a commit that referenced this issue Oct 25, 2021
Advance PC as part of executing HALT, then fetch (and discard) the next
instruction opcode as long as the CPU remains halted.
@simonowen
Copy link
Collaborator Author

I've made some test changes to give the behaviour described above in a halt_tweak branch. It may be incomplete but it was enough to pass the test I wrote to detect the difference in my emulator.

@simonowen simonowen added the bug label Oct 25, 2021
@kosarev
Copy link
Owner

kosarev commented Nov 6, 2021

What a nice catch! I'm playing with our new shiny transistor-level simulator, https://github.com/kosarev/z80/blob/c6ad460108c514df05f47e568b1ca0ea77e12705/tests/z80sim/z80sim.py, trying to understand what is supposed to happen in silicon on HALT. Clearly the address bus freezes, but otherwise it behaves like a normal fetch cycle. And then indeed it seemingly proceeds as if it was a NOP. So maybe let's commit what your branch does, and then I think I'd like to spend just a little bit more time looking into the actual hardware behaviour.

PC 0000, A 55, R 00, clk 0, abus 0000, dbus 00, m1 1, t1 1, t2 0, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 0, mreq 0
PC 0001, A 55, R 00, clk 1, abus 0000, dbus 00, m1 1, t1 0, t2 1, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 00, clk 0, abus 0000, dbus 76, m1 1, t1 0, t2 1, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 00, clk 1, abus 0000, dbus 76, m1 1, t1 0, t2 0, t3 1, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 00, clk 0, abus 0000, dbus 76, m1 0, t1 0, t2 0, t3 1, t4 0, t5 0, t6 0, rfsh 1, rd 0, mreq 0
PC 0001, A 55, R 01, clk 1, abus 0000, dbus 76, m1 0, t1 0, t2 0, t3 0, t4 1, t5 0, t6 0, rfsh 1, rd 0, mreq 1
PC 0001, A 55, R 01, clk 0, abus 0000, dbus 76, m1 0, t1 0, t2 0, t3 0, t4 1, t5 0, t6 0, rfsh 1, rd 0, mreq 1
PC 0001, A 55, R 01, clk 1, abus 0000, dbus 76, m1 0, t1 1, t2 0, t3 0, t4 0, t5 0, t6 0, rfsh 1, rd 0, mreq 0

PC 0001, A 55, R 01, clk 0, abus 0001, dbus 76, m1 1, t1 1, t2 0, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 0, mreq 0
PC 0001, A 55, R 01, clk 1, abus 0001, dbus 76, m1 1, t1 0, t2 1, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 01, clk 0, abus 0001, dbus c5, m1 1, t1 0, t2 1, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 01, clk 1, abus 0001, dbus c5, m1 1, t1 0, t2 0, t3 1, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 01, clk 0, abus 0001, dbus c5, m1 0, t1 0, t2 0, t3 1, t4 0, t5 0, t6 0, rfsh 1, rd 0, mreq 0
PC 0001, A 55, R 02, clk 1, abus 0001, dbus c5, m1 0, t1 0, t2 0, t3 0, t4 1, t5 0, t6 0, rfsh 1, rd 0, mreq 1
PC 0001, A 55, R 02, clk 0, abus 0001, dbus c5, m1 0, t1 0, t2 0, t3 0, t4 1, t5 0, t6 0, rfsh 1, rd 0, mreq 1
PC 0001, A 55, R 02, clk 1, abus 0001, dbus c5, m1 0, t1 1, t2 0, t3 0, t4 0, t5 0, t6 0, rfsh 1, rd 0, mreq 0

PC 0001, A 55, R 02, clk 0, abus 0001, dbus c5, m1 1, t1 1, t2 0, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 0, mreq 0
PC 0001, A 55, R 02, clk 1, abus 0001, dbus c5, m1 1, t1 0, t2 1, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 02, clk 0, abus 0001, dbus c5, m1 1, t1 0, t2 1, t3 0, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 02, clk 1, abus 0001, dbus c5, m1 1, t1 0, t2 0, t3 1, t4 0, t5 0, t6 0, rfsh 0, rd 1, mreq 1
PC 0001, A 55, R 02, clk 0, abus 0002, dbus c5, m1 0, t1 0, t2 0, t3 1, t4 0, t5 0, t6 0, rfsh 1, rd 0, mreq 0
PC 0001, A 55, R 03, clk 1, abus 0002, dbus c5, m1 0, t1 0, t2 0, t3 0, t4 1, t5 0, t6 0, rfsh 1, rd 0, mreq 1
PC 0001, A 55, R 03, clk 0, abus 0002, dbus c5, m1 0, t1 0, t2 0, t3 0, t4 1, t5 0, t6 0, rfsh 1, rd 0, mreq 1
PC 0001, A 55, R 03, clk 1, abus 0002, dbus c5, m1 0, t1 1, t2 0, t3 0, t4 0, t5 0, t6 0, rfsh 1, rd 0, mreq 0

kosarev pushed a commit that referenced this issue Nov 6, 2021
Advance PC as part of executing HALT, then fetch (and discard) the next
instruction opcode as long as the CPU remains halted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants