floooh / chips

8-bit chip and system emulators in standalone C headers
zlib License
960 stars 73 forks source link

Z80: having _wait() before _mwrite(addr, data) looks incorrect. #76

Open hlide opened 1 year ago

hlide commented 1 year ago

https://github.com/floooh/chips/blob/05cd84e43a1070a16c4edbcaa53a761561b629b8/codegen/z80_gen.py#L342

This line looks incorrect for me.

Let me take an example. SHARP MZ-700 has an LSI which handles PAL/NTSC video circuit and also decodes address bus for dispatching between DRAM, ROM, peripherals and VRAM. If CPU is accessing the video RAM ($D000-$DFFF) and there is no horizontal blank currently then /WAIT is set to 0 to pause the CPU and avoid a VRAM access conflict. So, that /WAIT is set to 0 because LSI decodes a VRAM address present on the address bus (so a set_ab_x(addr, Z80_MREQ|Z80_WR) ; should be done before a call to _wait()).

That means, the emulated LSI chip would need to check first that address setting to set Z80_WAIT upon the first tick of MWR T1 and retrieves the data on the next tick (MWR T2) just after passing the _wait() call. So just setting Z80_MREQ|Z80_WR at the second tick is wrong and should be done in two parts: first setting the address bus with Z80_MREQ|Z80_WR|Z80_AS (so the system can set the Z80_WAIT if needed) then Z80_MREQ|Z80_WR|Z80_DS (so CPU can retrieve the final data given by the system when clears Z80_WAIT)

elif mcycle.type == 'mwrite': l(f'// -- mwrite') addr = mcycle.items['ab'] data = mcycle.items['db'] add(f'_set_ab_x({addr},Z80_MREQ|Z80_WR|Z80_AS)') add(f'_wait();_set_db_x({data},Z80_MREQ|Z80_WR|Z80_DS);{action}') add('')

Maybe there is a better solution than having Z80_AS and Z80_DS but I'm pretty sure Z80_WAIT is not well handled currently.

Same issue with Z80_IORQ|Z80_WR. :)

Still with SHARP MZ-700, the access to Monitor ROM ($0000-$0FFF) wants a one-wait state, so you also have the same issue here as well.

hlide commented 1 year ago

One possibility is to transform:

I think that something modern Z80s have done.

first setting address before calling _wait() through Z80_MREQ or Z80_IOREQ and reading/writing data bus though one of the new flags will be set after the call to _wait.

If the system is not setting /WAIT to 0, pins = system_tick(pins) can simply ignored Z80_MREQ and Z80_IOREQ and only handle Z80_MRD, Z80_MWR, Z80_IORD and Z80_IOWR the same way it formerly handled Z80_MREQ | Z80_RD/WR and Z80_IOREQ | Z80_RD/WR.

Obviously, we may also have Z80_M1 | Z80_MREQ | Z80_RD which may be turned into Z80_M1 | Z80_MRD so it is up to the system to handle Z80_M1 | Z80_MREQ case or not.

floooh commented 1 year ago

Yeah, I'm painfully aware of the issue :D

I wrote about that here https://floooh.github.io/2021/12/17/cycle-stepped-z80.html#pin-timing-differences-to-a-real-z80

A 'proper' emulation would simply keep the MREQ and IOREQ pins active over multiple cycles, but the current solution is a compromise to simplify the 'system tick function' by preventing that the memory or io accesses are done multiple times.

The current configuration is what worked best for the CPC emulation, but I'm aware that this may then break other emulators.

I didn't really think about a solution so far except that I wanted to tinker a bot more with generating different versions of z80.h (e.g. an alternative 'slow but correct' version where the memory and io request pins behave like on a real Z80).

I haven't thought about adding new 'virtual' pins yet, it's definitely an interesting idea.

Only problem is that I currently need to focus on other things :)

hlide commented 1 year ago

Just for your information, I built a MCLZ8 (https://github.com/MicroCoreLabs/Projects/tree/master/MCLZ8) but I stumbled about some obstacles which made the MCLZ8 firmware a bad Z80 emulator (especially if you want to be fully compatible). So I was considering your z80 chip emulator which I find quite excellent (not speaking about your documentation). So I ported it into MCLZ8. While it compiles, I found out it is pretty hard to adapt it without some big changes in the generated file.

The reason is that I'm trying to do the opposite of what this project was made for: I just want to emulate a Z80, not the whole system. So I can't ignore the hardware signals that I have to maintain on several T-states (tick). And I realized that the control pins were not at all in phase with the need. Indeed, I need to do things by clock phase (half tick) and not by clock cycle.

So I'm going the third way now (in a similar fashion as your archived rz80 but in C++ using template X, Y and Z argument so each opcode table entry will contain a call to an optimized function which is one template function using X, Y and Z to do the operation by opcode). Of course, I would have liked to achieve something with your chips version but I get more headaches than solutions, especially with this notion of overlapped.