maziac / DeZog

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

[zsim] Support for not-populated slots #74

Closed lmartorella closed 2 years ago

lmartorella commented 2 years ago

Added support to simulated not-populated slots in hardware: read 0xFF (due to TTL levels) and can't write. This will greatly help in simulating custom Z80 boards with partial memory, or with banked memories that doesn't cover the whole 16-bit addressing.

I've then added a new working ZX16K model with not-populated 2-3 slots. Now booting the machine will correctly set the RAMTOP system variable before 0x8000 (obviously the ROM is the same).

The not-populated slots will be rendered with a slanted "N/A" in the simulation view. CPU accesses to not-populated slots will be however be correctly reported as normal activity.

I would like initially to implemented the not-populated simply creating a bank full of 0xFF and making it as read-only (via romBanks). However I felt that implementing it at slot level would be more correct. Once a slot is set as not-populated, it cannot be reverted. Setting a bank index in slots of a not-populated slot will have no effects.

To be sure that the debugger will correctly report 0xFF in the IDE, I realized that the getMemory8/getMemory16/getMemory32. setMemory8/setMemory16, readBlock/writeBlock would have fixed to reflect such state. Is that correct? In that case I slightly refactored it to simplify the implementation when reading words and blocks across slot boundaries.

Thanks for the great project, it is incredibly smooth to use, a must-have for retro-design new code for vintage hardware!

L

maziac commented 2 years ago

Hi,

thanks that you like DeZog. It's always nice to see when people make use of it.

And also thanks for your PR. It seems very well thought and even includes unit tests !

I must admit I haven't though about RAMTOP. When I implemented the ZX48K model I thought it would do for the ZX16k as well and was to lazy to implement anything special.

You mentioned "I would like initially to implemented the not-populated simply creating a bank full of 0xFF and making it as read-only (via romBanks)". Actually this would be my approach as well, as it makes the implementation easier. Of course, it would be required to rename "romBanks" into something else, e.g. "nonWritableBank". Why did you do it differently. I think all of the changes to getMemoryX could be avoided.

lmartorella commented 2 years ago

You mentioned "I would like initially to implemented the not-populated simply creating a bank full of 0xFF and making it as read-only (via romBanks)". Actually this would be my approach as well, as it makes the implementation easier. Of course, it would be required to rename "romBanks" into something else, e.g. "nonWritableBank".

I realized that the setMemoryX/writeBlock functions are used by the debugger when the user interacts with the memory viewer, for example. In that case, even setting a bank as ROM will still allow the user to change the underlying data. For a real ROM bank, this can be quite useful (e.g. to apply temporary dev patches during debugging).

However, for not populated banks I would say that this should be unexpected. Trying to change a byte/word slot in the memory viewer or via debugging command should still result in an empty 0xFF.

Another (minor) reason is that for a custom board that contains both banked slots and not populated slots, the "ROM bank" approach would require an additional fake bank, making slightly less intuitive the bank indexes.

For example, a custom machine with 4 slots, with a ROM in slot 0 and some banked RAM (N banks) in slots 2 and 3 (0x8000 - 0xFFFF) will require an additional bank index for the not-populated slot 1. So this will result in:

The performance load due to this change for the simulator (Z80 accesses) should be negligible. The additional performance load for non-Z80 access is instead slightly more (for cross-slot access in particular); but I think this is reserved to debugging actions only (invoked by the dev during debugging).

Well, should you feel that the change in the PMMU logic of simmemory is over-engineered, I'll revert to the fake ROM approach (trying to maintains the same API so tests will be the same).

Thanks! L

maziac commented 2 years ago

However, for not populated banks I would say that this should be unexpected.

Maybe, one could also argue that it is a "feature". Anyhow the user should know what he is doing. So if he wants to change the value I would say: why prevent him doing so. Even more if this 'preventing' does require additional coding.

Another (minor) reason is that for a custom board that contains both banked slots and not populated slots, the "ROM bank" approach would require an additional fake bank, making slightly less intuitive the bank indexes.

Isn't it the same for 'not-populated' banks. In the current implementation a continuous block of memory is used for the banks. So maybe this memory could be saved if the upper banks are not used. But if e.g. some bank in between would be unpopulated then the RAM would be allocated anyway.

So, all in all, I think I like the fake ROM approach more.

lmartorella commented 2 years ago

Mmm yes, I was focused on machines with unpopulated pages (missing circuitry for decoding a whole range of addresses), but I've just realized that the same can be applied to missing banks: so circuitry for decoding address is there, but the particular bank assignment could led to empty banks (e.g. when banks count on a slot is not power of 2, and the user set an invalid assignment).

Will change the implementation and revert to an easier read-only bank, without impacting the internal API to write in such areas.

Thanks! L.

maziac commented 2 years ago

Ahh, forgot to ask: Is your contribution under MIT license?

lmartorella commented 2 years ago

Yep, MIT license is OK, thanks.

maziac commented 2 years ago

Thanks. I have done some changes and also introduced a "CUSTOM" memory model. Just to let you know: On the way I now changed the original romBanks boolean array into an bankTypes array with 3 possible values: ROM, RAM and UNUSED.

lmartorella commented 2 years ago

Hi, That's exactly what I was planning to do in this WIP branch: https://github.com/lmartorella/DeZog/tree/feat/mem-setting

The target is to have a completely declarative way to setup a banked setup without writing a line of code. I was planning something like that:

{
    // ... 
    // Custom Olivetti board
    "memoryModel": [
        // 0-3FFF: ROM
        // 4000-6000: RAM: smaller slot of 8K
        // 6000-8000: Not populated
        // 8000-FFFF: paged, 16 whole banks of 32K
        { "range": [0, "3FFFh"], "rom": "./rom.hex" },
        { "range": ["4000h", "5FFFh"] }, // RAM
        { "range": ["8000h", "FFFFh"], "banked": {
            "count": 16, // 16 banks of 32K  = 512K
            "control": {
                "ioPort": "80h",  // OUT to this port to select bank
                "ioBitMap": [0, 1, 2, 3],   // lower bits used
                "readWrite": false // port can't be read back
            }
        } }
    ]
}

When I have some free time I'll try to finalize this branch. The key point is to have a subclass of PagedMemory. Thanks! L

maziac commented 2 years ago

I was also thinking of doing paged memory access. My proposal would be to use "customCode" to do the switching. Then you could implement any kind of port access to switch the banks. It would require only some additional API that the custom code can switch memory banks. But the exact implementation could be done by the user in the custom code.

On the other hand I thought that this might be over-engineered. Who would really use it. It's uncertain that someone will create a custom board with Z80 bank paging.

Of course there are old Z80 computers available that support it. But supporting those would probably require also other changes in DeZog. And if a change in DeZog is required one could also implement the new memory bank/paging there.

But I'm open for discussions. Please share your thoughts.

I have seen in your json above that there is also an option to read in a hex file. This can be done already with the "loadObj" statement. Since RAM/ROM banks are writable for DeZog you can load an obj (binary) file into the ROM area at start.