maziac / DeZog

Visual Studio Code Debugger for Z80/ZX Spectrum.
MIT License
218 stars 35 forks source link

Support of the ZX81 #130

Closed andrivet closed 2 months ago

andrivet commented 2 months ago

This is backporting of ZX81 Debugger into the latest version of DeZog.

I modified as few DeZog files as possible so some code is duplicated. The summary of the modifications are:

Features:

Known limitations:

maziac commented 2 months ago

Could you share the zx81 rom permission from Nine Tiles with me.

andrivet commented 2 months ago

I have pushed commits that merges the ZX81 emulator (zx81sim) with the Z80 emulator. So now, there is no zx81sim, no ZX81SimType and no ZX81SimRemote. The ZX81SimulationView remains. I have also fixed a bug with the initial stack pointer. For the ZX81 ROM permission, I send you an email with it.

maziac commented 2 months ago

OK, thanks. I'm not so familiar anymore with the ZX81: What is special about the CAPS? Did the ZX81 have less keys than the Spectrum? And what about the display: I think I remember that the display was of dynamic size. From what variable could the size be read? And there was a FAST mode. Is this taken into account here?

andrivet commented 2 months ago

What is special about the CAPS? Did the ZX81 have less keys than the Spectrum? No, but some keys do not have the same meaning. image image The Spectrum CAPS Shift key is simply Shift on the ZX81. The Symbol Shift on the Spectrum in the dot on the ZX81.

I think I remember that the display was of dynamic size. Not really unless you are talking about the HiRes 1K mode. What is dynamic is where the D_FILE (display file) is placed and its size. On a 1K ZX81, it is compressed. This is correctly handled by the simulator.

And there was a FAST mode FAST mode corresponds to the way the ZX80 displays images. There was no image (but garbage) when the ZX80 was processing some part of a program. As the display is simulated, there is no garbage displayed in FAST mode. Other than that, yes it should work but I have not tested this mode.

Something else I forgot to mention: I have not tested with ZEsarUX or CSpect. I will and fix bugs if there are any.

maziac commented 2 months ago

I'm currently trying to get the ZX81 booting. I.e. after loading the .p file I change PC to 0x0000 and start it. It works to some extend. I can briefly see the ZX81 cursor but then it crashes and restarts at 0x0000. I get a similar behavior when executing your fork. Have you ever tested to start from a reset? (BTW your sample helloworld.p file itself is working.)

andrivet commented 2 months ago

Have you ever tested to start from a reset?

No. I wanted to run assembly programs, so I have not tested this. I will try.

andrivet commented 2 months ago

I think I know why the ROM code is crashing. It is because, in order to display an image, the ZX81 CPU literally executes the content of the screen (the D_FILE). It jumps to a high location that is a mirror of the D_FILE. This is intercepted by the ULA that returns NOPs to the CPU. At some points (end of lines), the HALT instruction is executed (and thus the CPU is paused). Since there is nothing like that in the emulator (no ULA), the emulated CPU actually executes until it reaches the end of the memory. And it crashes.

I will see how I can emulate this behavior. I have done this some years ago in another ZX81 emulator. I am not sure when I will be able to do this. Running the ROM was never a priority for me. If it implies too many modifications in DeZog, I am also not sure it is the right move and maybe I will integrate this only in the ZX81 Debugger.

maziac commented 2 months ago
It jumps to a high location that is a mirror of the D_FILE. This is intercepted by the ULA that returns NOPs to the CPU. At some points (end of lines), the HALT instruction is executed (and thus the CPU is paused). Since there is nothing like that in the emulator (no ULA), the emulated CPU actually executes until it reaches the end of the memory. And it crashes.

That would explain the behavior. But I thought this (ULA nopping etc.) happens only on interrupt (NMI) and there is no interrupt generated in this simulation.

andrivet commented 2 months ago

there is no interrupt generated in this simulation

Yes, and this is why it crashes (I debugged the ROM to understand what is going on):

maziac commented 2 months ago

How does it check for no NMI? Is there a loop that waits for some time? Can you point me to that Code.

andrivet commented 2 months ago

Sure. It is on the SLOW_FAST subroutine (address 0207):

  ...
  LD  A, $7F
  EX  AF, AF'
  LD  B, $11
  OUT ($FE), A

LOOP:
  DJNZ LOOP
  OUT  ($FD), A 
  EX  AF,AF'
  RLA
  JR  NC, NO_SLOW
  ...

OUT ($FE), A activates the NMI generator, OUT ($FD), A deactivate it. The NMI handler (adresse 0066) does this:

NMI:
  EX AF, AF'
  INC A 
  ...

The code sets flags depending on the result (CDFLAG) and determines if it will run in FAST or SLOW mode.

maziac commented 2 months ago

I'm digging into this display generation of the zx81. Very sophisticated stuff. What I not get is: there have been memory extension for the zx81, to extend to 48k, 64k. How does this work with the dfile: For everything above 32k the ULA will pull the data bits down. Or is it just usable for data and not for program code?

andrivet commented 2 months ago

Or is it just usable for data and not for program code?

Exactly. The ZX81 was never conceived for more than 16K of RAM and Sinclair has never produced RAM packs with more than 16K. There are other parts of the ROM that assume there no more than 16K (like the memory test on boot and the RAMTOP variable). All 32+ RAM packs are from third parties and have some limitations. In general, the 16K in high memory can only contain data. Some packs are also able to contain the HFILE for HiRes display (256x192 pixels). 48K packs add 8K of RAM juste after the ROM. This area can contain machine code but not BASIC and is not saved by SAVE (unless using a custom saving subroutine). There are also 64K packs but as I do not have one, I am not sure of what they are doing. From my understanding, some 48K packs are sold as 64K packs (the manufacturer adds the ROM and the RAM). Some are using some kind of banking. It looks like it is only hardware switchable (DIP switches) and the packs contains a battery to avoid loosing the content. There are probably other variations as many add-ons have been produced for ZX80 and ZX81.

maziac commented 2 months ago

Do you know what exactly triggers the interrupt $0038?

andrivet commented 2 months ago

INT on the CPU is physically wired to A6 (yes, it is not a mistake):

image

In practice, this is controlled by bit 6 of the R register (normally used to refresh dynamic RAM, but used in the video generation in a ZX81). The R register is automatically incremented by the Z80 CPU. Maskable interrupts are only active when "executing" the DFILE if I am not wrong (the details of the video generation are quite complex):

;; 02B5
  LD      R, A
  LD      A,$DD
  EI
  JP      (HL)

The interrupt handler itself does something similar:

;; 0038
  DEC     C
  JP      NZ, SCAN_LINE
  POP     HL
  DEC     B 
  RET     Z
  SET     3, C
WAIT_INT:
  LD      R, A
  EI      
maziac commented 2 months ago

Thanks for the help. I have now a draft version with a deeper emulation of the zx81 ready. Bildschirmfoto 2024-08-10 um 19 37 17

You can take a look at this branch: https://github.com/maziac/DeZog/tree/zx81

In zx81ulascreen.ts it generates the NMI and 0x0038 interrupts and it also reacts on the out and in ports. I had to make use of the R register for the generation of the interrupts. Furthermore there is a new read method for the z80 and the SimulatedMemory to read during an m1 cycle. The ulaM1Read intercepts it to manipulate the read opcodes if address is >= 32k.

It was a lot of fiddling but seems to work in the end.

You can try by loading the zx81 rom and setting pc to 0.

Here is the launch.json I used:

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "dezog",
            "request": "launch",
            "name": "Simulator - ZX81-16K",
            "remoteType": "zsim",
            "zsim": {
                "visualMemory": true,
                "memoryModel": "ZX81-16K",
                "ulaScreen": "zx81",
                "zxBorderWidth": 20,
                "zxKeyboard": true
            },
            // "sjasmplus": [
            //  {
            //      "path": "helloworld.sld"
            //  },
            //  {
            //      "path": "zx81-rom.sld"
            //  }
            // ],
            "commandsAfterLaunch": [],
            "history": {
                "reverseDebugInstructionCount": 1000000,
                "spotCount": 10,
                "codeCoverageEnabled": true
            },
            "startAutomatically": false,
            "rootFolder": "${workspaceFolder}",
            //"load": "helloworld.p",
            // "loadObjs": [
            //  {
            //      //"path": "helloworld.p",
            //      "path": "OSMO.P",
            //      "start": "0x4009"
            //  }
            // ],
            //"execAddress": "16514",   // 0x4082
            "execAddress": "0",
            "topOfStack": "0x7FFF"  // For 16k
        }

    ]
}

Please note that I redefined "ulaScreen" to "zx81" | "spectrum", but I'm not convinced that this is a good idea. Maybe I use a second name for it.

Anyway the display is still simulated. I could really let the Z80 generate it, but I'm not sure if it's worth it. It was the same with the ZX Spectrum screen. For debugging it is even better not to "ride the beam" but directly see what would be the effect on the screen.

andrivet commented 2 months ago

I just tried it. Nice work. I was surprised at first boot by the border color. And the keyboard is not working well but this should not be complex to fix.

I could really let the Z80 generate it, but I'm not sure if it's worth it.

This is what I have done in my other (old) emulator, based on EightyOne emulator. It is quite complex to make it right. There is an advantage to really generate the display: it works also for HiRes modes with minor modifications.

andrivet commented 2 months ago

I closed this PR since it is now obsolete with your support of the ZX81 in (future) DeZog 3.5. I also deleted my fork, also outdated.