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

eZ80 support #400

Open
vipoo opened this issue Jun 4, 2024 · 36 comments
Open

eZ80 support #400

vipoo opened this issue Jun 4, 2024 · 36 comments
Assignees

Comments

@vipoo
Copy link
Contributor

vipoo commented Jun 4, 2024

It looks like you are using the Zeta 2 MMU, which is ideal as a mechanism for this.
I am? Not sure what that is. I basically just copied the standard config for RCZ80. I went through and disabled all the drivers etc for the time being. Then added a driver for the eZ80 UART. I also added a new CPUFAM and PLATFORM config entries

Then went through the start-up code and just wrapped my changes in #IF (CPUFAM == EZ80)....`

A couple of decent macros may make this more palatable.
Yep - my thoughts also - part of the challenge is there is no assembly support for the new 24bit and other instructions of the eZ80.

1. In the implementation of HBX_BNKSEL, can I use the stack?

I need to use the OUT (C), r instruction --- which is technically OUT (BC), r.

This necessitated the use of BC register - so I had saved it onto the stack - then i was wondering if I was having a stack issue. I did try saving it in the alt registers (EXX). I think pushing onto the stack would work - but at the time of coding, I was having a hardware issue. (hardware issue since resolved). But yet to test this fully. But I do wonder if I can use the stack at all in the function? What if the function is swapping the segment with the stack ? If I cant use the stack, I can probably just use a couple of bytes in the on-chip's RAM.

Just as I type, I am wondering if I can implement a pseudo function to enable doing the i/o with out need to set the B register - hmmm - I think the eZ80 even has hooks for illegal instructions - so hmm wondering... thinking....
** Further progress is being made on this point - I will post a reply on how that went - should make it much easier to decorate the code to work for the eZ80 16bit IO.

  1. I created a new CPUFAM and PLATFORM, but I am not sure what's the best conditional variable to use here
PLATFORM	.EQU	PLT_RCEZ80
CPUFAM	.EQU	CPU_EZ80

But I have only used CPUFAM for the conditional test --- as mentioned above, not sure what would be best approach here.

3. What function belong in the on-chip ROM and what belongs in the RomWBW image?

The eZ80 will boot into its on-chip ROM (its initially mapped at address $000000). The on-chip ROM and RAM will be super fast - with minimal wait-states, unlike the external RC2014 ram module.

So I am thinking that the 'firmware' on the eZ80 is responsible for booting, setting up RAM/IO address ranges, wait states, address modes, and the initialize all the on-chip services (uarts, timers & counters, interrupts, the RTC, etc.). It should be 'agnostic' of RC2014 module configurations.

Once its ready, it then jumps to the first byte in the external 64K RAM address.

Perhaps an interface within the firmware could be made available for code such as HBIOS/RomWBW to call. This will be a table of jump functions --- allowing on-chip device configurations (baud rates etc). Also maybe a feature to copy and execute a block of code in the fast on-chip RAM.

The RomWBW would just need to initiate a far call (CALL.LIL) to access these functions.

Dev tool chains could be targeted for the specific platform -- I understand that the preferred approach for developing eZ80 code is to use a variant of clang that targets the eZ80 -- similar to how what has been developed for the Agon. I have not got that setup yet though. Just using the Zilog IDE which has a very old version of C (C98) but it does have an assembler to target the eZ80 instructions. This IDE is windows only (wine might work) - but it does have a nifty debugger.

  1. No idea what I am going to do for interrupts
    At the moment, I disabled INTs in the configuration. But there will be some complexity with INT management. The eZ80 mixed mode might work - but the handling of the correct version of RETI I think will be important.

I will need to do some experimenting on this.

5. Dual CPU Mode

I don't have the hardware wired up for the Dual CPU configuration. Have no idea if my idea will even work. This will be a little bit down the track. But the general idea will be the Z80 and the eZ80 can run side by side. Only 1 processor has access to the bus (the Z80 is sent the BUSREQ to remove it, but when the Z80 is controlling the bus, the eZ80 will be isolated by buffers, with it continuing to run on its internal ROM/RAM).

Thanks again Wayne.

@vipoo
Copy link
Contributor Author

vipoo commented Jun 4, 2024

Last night i did an experiement with the eZ80 instruction RST.L 8

This can be used by code running in the Z80 compatible mode, to jump to a function in the firmware - outside of the 64k address range.

Its only 2 bytes long, uses the SPL stack, not the Z80 compatible 16 bit stack (SPS).

So I think i might make something like the following work :

  1. In normal RomWBW code, where we need to do a IO operation with say OUT (nn), A, we precede this will just the 2 bytes for the RST.L instruction (bytes: $49 $CF).
  2. This causes the PC to jump to address $000008 in the on-chip ROM.
  3. the SPL (long stack) is pushed with the return address - the SPL is mapped into the on-chip RAM
  4. The handler for this trap, can walk the stack, and inspect the next instruction, and interpret the OUT as OUT ($FFnn), A
  5. It could interpret any of the I/O instructions and ensure the upper byte is always $FF
  6. No SPS (local stack within 64K address) is impacted or required.
  7. The preceding 2 bytes can be wrapped in a simple macro thats only applied for eZ80 config builds

@wwarthen
Copy link
Owner

wwarthen commented Jun 4, 2024

It looks like you are using the Zeta 2 MMU, which is ideal as a mechanism for this.
I am? Not sure what that is. I basically just copied the standard config for RCZ80. I went through and disabled all the drivers etc for the time being. Then added a driver for the eZ80 UART. I also added a new CPUFAM and PLATFORM config entries

Yes, implicitly you are. The 512K RAM/ROM module MMU is the Zeta 2 MMU.

1. In the implementation of HBX_BNKSEL, can I use the stack?

Yes. Since HBX_BNKSEL is a CALLed function, a valid stack must already exist (and already be in high 32K). You will notice that the API invocation and interrupt framework go to some trouble to ensure this. If a user calls HBX_BNKSEL they are required to have a stack established in high RAM.

I need to use the OUT (C), r instruction --- which is technically OUT (BC), r.

Right.

This necessitated the use of BC register - so I had saved it onto the stack - then i was wondering if I was having a stack issue. I did try saving it in the alt registers (EXX). I think pushing onto the stack would work - but at the time of coding, I was having a hardware issue. (hardware issue since resolved). But yet to test this fully. But I do wonder if I can use the stack at all in the function? What if the function is swapping the segment with the stack ? If I cant use the stack, I can probably just use a couple of bytes in the on-chip's RAM.

Stack should be fine. It will be in high RAM.

Just as I type, I am wondering if I can implement a pseudo function to enable doing the i/o with out need to set the B register - hmmm - I think the eZ80 even has hooks for illegal instructions - so hmm wondering... thinking.... ** Further progress is being made on this point - I will post a reply on how that went - should make it much easier to decorate the code to work for the eZ80 16bit IO.

Interesting idea. Let me know.

  1. I created a new CPUFAM and PLATFORM, but I am not sure what's the best conditional variable to use here
PLATFORM	.EQU	PLT_RCEZ80
CPUFAM	.EQU	CPU_EZ80

But I have only used CPUFAM for the conditional test --- as mentioned above, not sure what would be best approach here.

You did this right. If possible, use CPUFAM for the conditionals. Ideally, over time, there would be the ability to put an eZ80 in other platforms. The use of PLT_RCEZ80 would be useful for maintaining a platform that has the eZ80 internal peripherals.

3. What function belong in the on-chip ROM and what belongs in the RomWBW image?

The eZ80 will boot into its on-chip ROM (its initially mapped at address $000000). The on-chip ROM and RAM will be super fast - with minimal wait-states, unlike the external RC2014 ram module.

So I am thinking that the 'firmware' on the eZ80 is responsible for booting, setting up RAM/IO address ranges, wait states, address modes, and the initialize all the on-chip services (uarts, timers & counters, interrupts, the RTC, etc.). It should be 'agnostic' of RC2014 module configurations.

Once its ready, it then jumps to the first byte in the external 64K RAM address.

This sounds fine to me.

Perhaps an interface within the firmware could be made available for code such as HBIOS/RomWBW to call. This will be a table of jump functions --- allowing on-chip device configurations (baud rates etc). Also maybe a feature to copy and execute a block of code in the fast on-chip RAM.

The RomWBW would just need to initiate a far call (CALL.LIL) to access these functions.

Also fine.

Dev tool chains could be targeted for the specific platform -- I understand that the preferred approach for developing eZ80 code is to use a variant of clang that targets the eZ80 -- similar to how what has been developed for the Agon. I have not got that setup yet though. Just using the Zilog IDE which has a very old version of C (C98) but it does have an assembler to target the eZ80 instructions. This IDE is windows only (wine might work) - but it does have a nifty debugger.

I don't have any immediate thoughts on this.

  1. No idea what I am going to do for interrupts
    At the moment, I disabled INTs in the configuration. But there will be some complexity with INT management. The eZ80 mixed mode might work - but the handling of the correct version of RETI I think will be important.

I will need to do some experimenting on this.

It looks like interrupt mode 2 would work more or less as-is, no? Have I missed something?

5. Dual CPU Mode

I don't have the hardware wired up for the Dual CPU configuration. Have no idea if my idea will even work. This will be a little bit down the track. But the general idea will be the Z80 and the eZ80 can run side by side. Only 1 processor has access to the bus (the Z80 is sent the BUSREQ to remove it, but when the Z80 is controlling the bus, the eZ80 will be isolated by buffers, with it continuing to run on its internal ROM/RAM).

This sounds great, but definitely outside my area of expertise.

Thanks!

Wayne

@wwarthen
Copy link
Owner

wwarthen commented Jun 4, 2024

Last night i did an experiement with the eZ80 instruction RST.L 8

This can be used by code running in the Z80 compatible mode, to jump to a function in the firmware - outside of the 64k address range.

Ah, OK, that explains the code I saw in your fork. I was trying to understand it and thought it was something like this. I am a little unclear how the eZ80 firmware is done. I assume that code is not in your fork?

Its only 2 bytes long, uses the SPL stack, not the Z80 compatible 16 bit stack (SPS).

So I think i might make something like the following work :

  1. In normal RomWBW code, where we need to do a IO operation with say OUT (nn), A, we precede this will just the 2 bytes for the RST.L instruction (bytes: $49 $CF).
  2. This causes the PC to jump to address $000008 in the on-chip ROM.
  3. the SPL (long stack) is pushed with the return address - the SPL is mapped into the on-chip RAM
  4. The handler for this trap, can walk the stack, and inspect the next instruction, and interpret the OUT as OUT ($FFnn), A
  5. It could interpret any of the I/O instructions and ensure the upper byte is always $FF
  6. No SPS (local stack within 64K address) is impacted or required.
  7. The preceding 2 bytes can be wrapped in a simple macro thats only applied for eZ80 config builds

Very creative. My only concern would be performance impact. That sounds like quite a few instructions. Not an issue in many cases, but it probably is in the bank switching. Perhaps bank switching would just use the stack to save/restore BC.

Thanks, Wayne

@wwarthen
Copy link
Owner

wwarthen commented Jun 4, 2024

Very creative. My only concern would be performance impact. That sounds like quite a few instructions. Not an issue in many cases, but it probably is in the bank switching. Perhaps bank switching would just use the stack to save/restore BC.

Hmmm... what if the entire HBX_BNKSEL routine was implemented in the on-chip ROM and a single RST.L just invoked it? Not sure if that is any better than just adding a push/pop of BC in the existing routine.

Thanks, Wayne

@vipoo
Copy link
Contributor Author

vipoo commented Jun 4, 2024

Very creative. My only concern would be performance impact. That sounds like quite a few instructions. Not an issue in many cases, but it probably is in the bank switching. Perhaps bank switching would just use the stack to save/restore BC.

Hmmm... what if the entire HBX_BNKSEL routine was implemented in the on-chip ROM and a single RST.L just invoked it? Not sure if that is any better than just adding a push/pop of BC in the existing routine.

  • Performance.
    Agreed it might have an impact, so since I can save BC on the stack that might better. I can play with the options and see how it performs. The idea of moving the bank switching code to the firmware - certainly could also work - and would probably be the fastest - as the code running from the on-chip rom will have minimal wait states.

I think the RST.L 8 might still be good to help with porting existing code. So perhaps a mixture of approaches.

It looks like interrupt mode 2 would work more or less as-is, no? Have I missed something?

Yeah, the interrupts of the eZ80 are always invoked in ADL mode (not the Z80 compat mode). With the complexity of multiple stacks, and the fact that the interrupt might have been triggered with the processor in ADL mode or not - well I am still figuring this out... I think it would mean that the RETI needs to be changed.

This is a section from the eZ80 manual:

In mixed-ADL applications, some of these rules may represent exceptions to the
eZ80® CPU’s design goal; i.e., that legacy code does not require modification to
run on the eZ80® CPU. Assuming that legacy routines are not selectively
converted to ADL mode and do not call newly-written routines, the only rule that
could lead to such modification is Rule 5. If each legacy Z80 mode routine ends
with a single RET.L at its end, this conversion is easy. Internal and conditional
RETs require more careful review.

@wwarthen
Copy link
Owner

wwarthen commented Jun 4, 2024

I think the RST.L 8 might still be good to help with porting existing code. So perhaps a mixture of approaches.

Yes!

It looks like interrupt mode 2 would work more or less as-is, no? Have I missed something?

Yeah, the interrupts of the eZ80 are always invoked in ADL mode (not the Z80 compat mode). With the complexity of multiple stacks, and the fact that the interrupt might have been triggered with the processor in ADL mode or not - well I am still figuring this out... I think it would mean that the RETI needs to be changed.

This is a section from the eZ80 manual:

In mixed-ADL applications, some of these rules may represent exceptions to the
eZ80® CPU’s design goal; i.e., that legacy code does not require modification to
run on the eZ80® CPU. Assuming that legacy routines are not selectively
converted to ADL mode and do not call newly-written routines, the only rule that
could lead to such modification is Rule 5. If each legacy Z80 mode routine ends
with a single RET.L at its end, this conversion is easy. Internal and conditional
RETs require more careful review.

Gotcha. I also responded on the Google Group. I think we are still OK. Under RomWBW, we just need to transition back to Z80 mode at the start of the interrupt. If the ADL interrupt vectors point to corresponding CALL.L statements, we should be fine. The RETI.L at the end is easy.

Thanks, Wayne

@dinoboards
Copy link
Contributor

I have posted my ez80 firmware code to a new repo: https://github.com/dinoboards/rc2014-ez80-firmware/tree/main

Its a project for the Zilog ZDS IDE. So windows and rather old school....

@wwarthen
Copy link
Owner

wwarthen commented Jun 4, 2024

I have posted my ez80 firmware code to a new repo: https://github.com/dinoboards/rc2014-ez80-firmware/tree/main

Thanks!

@wwarthen
Copy link
Owner

wwarthen commented Jun 5, 2024

Hi @vipoo,

I would like to make sure I understand the memory map you are using for the eZ80. I'm not sure I am looking at the right places in the firmware source.

  • On-Chip ROM (128K) seems to be at 0x000000. I think I have that right.
  • On-Chip RAM (8K) seems to be at 0xB7E000. Is that right?
  • External Memory (64K window into 512K RAM/ROM) is unclear to me. I see MBASE set to 0xB9 implying 0xB90000. Is that right?

Can you clarify/confirm the above?

Thanks! Wayne

@dinoboards
Copy link
Contributor

Hi Wayne (its still me Dean - just using my alt github account that i am trying to move all my retro stuff too)

On-Chip ROM (128K) seems to be at 0x000000. I think I have that right.
Yep

On-Chip RAM (8K) seems to be at 0xB7E000. Is that right?
Yep

External Memory (64K window into 512K RAM/ROM) is unclear to me. I see MBASE set to 0xB9 implying 0xB90000. Is that right?
Yep

The problem with the ZDS IDE is it has a lot of settings hidden in project files. Gonna refactor some of that now to be a little more readable/understandable - and use less of the IDE project's 'configuration' files

Dean

@wwarthen
Copy link
Owner

wwarthen commented Jun 6, 2024

Thank you Dean. Glad I got it right. As you say, it is a little convoluted.

Thanks, Wayne

@vipoo
Copy link
Contributor Author

vipoo commented Jun 8, 2024

Hi Wayne,

Today i am trying to get the Compact Flash module working. But something not quite right. It seems to get the size of the unit wrong - and any attempt to initdir or clrdir does not seem to have any effect.

I updated the code to use ensure IO range of $FF1x for my CF module. It seems to detect there is a module installed, but it get scrambled. Its almost certainly something not implemented right be me --- but not sure what.

The CF card I have - was the one I got from spencer as part of the original kit (128Mb) - but its since been reformatted to a standard FAT image (done as part of my MSX build)

I turned on the tracing - wonder if you could parse your eye over this - and tell me what I should be expecting?


RomWBW HBIOS v3.5.0-dev.45, 2024-06-08

RCBus [RCEZ80_std] eZ80 @ 18.432MHz
0 MEM W/S, 1 I/O W/S, Z2 MMU
512KB ROM, 512KB RAM, HEAP=0x54A3
ROM VERIFY: 00 00 00 00 PASS

MD: UNITS=2 ROMDISK=384KB RAMDISK=256KB
IDE: IO=0x10 MODE=RC
IDE: RESET
IDE: SELUNIT MASTER
IDE: PROBE 50 E0 50 50 AA 55 [50 E0 00 55 AA 01 01]
IDE: INITDEV [50 E0 00 55 AA 01 01]
IDE: SETFEAT E0 01 EF --> 50
IDE: IDDEV E0 EC --> 58 GET 58
IDE: SIG=0x5A47
IDE: SELUNIT SLAVE
IDE: PROBE 58 F0 00 C1
IDE: NO MEDIA [00 C1 C1 C1 C1 C1 C1]
IDE0: ATA 8-BIT LBA BLOCKS=0x5A5A5A5A SIZE=740171MB
IDE1: NO MEDIA

Unit        Device      Type              Capacity/Mode
----------  ----------  ----------------  --------------------
Char 0      Terminal0:  RS-232            150,5,N,1
Disk 0      MD0:        RAM Disk          256KB,LBA
Disk 1      MD1:        ROM Disk          384KB,LBA
Disk 2      IDE0:       Hard Disk         740171MB,LBA
Disk 3      IDE1:       Hard Disk         --


RCBus [RCEZ80_std] Boot Loader

Boot [H=Help]:

@wwarthen
Copy link
Owner

wwarthen commented Jun 8, 2024 via email

@dinoboards
Copy link
Contributor

dinoboards commented Jun 8, 2024 via email

@dinoboards
Copy link
Contributor

Hi Wayne,

I am about to go travelling for the next week. So I thought I would just share some details of the latest updates/progress I have made.

As of writing, I was unable to the get my version of the Compact Flash module to work. My module is the very first iteration - it does not have an RC to delay the control signal - nor does it have the additional timing logic that the V2. I will come back to figuring this out.

Got my RP5C01 RTC module to work. Updated the rp5rtc.asm file to 'prefix' the OUT and IN operations to ensure they target the external I/O address range. All seems good.

Next I worked on my V9958 video module. Again, prefix the OUT and IN operators - and all good. Had the cpm console redirected to the CRT - cool! (interrupt logic disabled)

So having 2 modules working - give me confidence that many other modules will work. I had these modules interfacing just fine, despite the fact I had overclocked my eZ80 to 24Mhz. The configuration for the bus access is such, that from the individual modules perspective, the address/data and controls signals are the same as per a normal Z80 running around ~6Mhz.

I have, I think, mentioned in previous post about the configuration of the eZ80 -- below is a summary of the current config:

  1. The eZ80 software operates in MADL - Mixed-ADL mode.
    1. ADL mode is the mode where the eZ80 operates with 24bit registers, and 24bit stack (SPL) - with ability to access the full 16MB linear address range.
    2. Non-ADL mode (or Z80 mode), is where the eZ80 operates on 16bit registers and is constrained to a 64K byte address region. The upper 8 bits of the address is determined by the MBASE register.
  2. The onboard ROM is mapped to address $000000 to $01FFFF (128K ROM).
  3. The onboard RAM is mapped to address $B7E000 to $B7FFF (8K)
  4. The SPL stack is initialised at $B80000
  5. The external I/O is mapped to $FFxxxx
  6. the external ROM/RAM is mapped to $B9xxxx (MBASE is set to $B9)
    1. RomWBW will initialise itself and set the stack as it wishes , thus the SPS stack is within the $B9xxxx address range.
  7. The external addressing timing and sequence for both I/O and ROM/RAM is configured to use the Z80 bus mode.
    1. This is the typical 3-T states for triggering the IORQ/MREQ/RD/WR signals.
    2. The length of a T state is configured as per the config.inc file.
    3. No additional wait states are needed, as by clocking down the main clock, the effective bus interaction is equivalent to a Z80 running at approx 6-7Mhz.
    4. I have tested with a T state of 1 clock cycle, and it still functions nicely, but I chose to go conservative with timing to ensure maximum compatibility/reliability.
    5. Code that's loaded from the external ROM/RAM will be accessed 'slowly', but the internal eZ80 instruction processor will be much faster that a standard Z80. But I suspect some of the pipelining might get defeated, as it take so many clock cycles to load the next instruction, the previous instruction would have already been completed.
    6. Any code that loaded from the onboard ROM/RAM will be much faster and take full advantage of the eZ80 pipelining capabilities.

ADL or Z80 or Mixed Mode

I thought I would share my thinking around the possible options for configuring the CPU address/operating mode, as there are technically a few options:

  1. Operate in ADL mode exclusively. This means all software operates on 24bit registers and addresses. Interrupts are invoked in ADL mode.

    1. This option for RomWBW would be a complete rewrite - not really practical.
  2. Operate in Z80 mode exclusively. This means, on poweron/reset, the code in the onboard rom ($000000), simply configures the MBASE register - and then switches to Z80 mode. Interrupts would be invoked in Z80 mode, and operate much the same way as a Z80 processor using a vector jump table.

    1. This could be a potential option for RomWBW.
    2. But we lose any chance to use the additional register width
    3. All I/O operations would all need to be rewritten to ensure the external I/O are addressed correctly.
    4. Despite the fact all instructions only access the lower 16bits of the register, the upper 8 bits will be changed as an undefined side effect.
    5. As Interrupts would be processed in Z80 mode, the upper 8bits of the registers would not be protected, so if any ADL code were encountered, its registers might get corrupted - so no custom applications targeting this mode would work unless interrupts are disabled.
  3. Operate in MADL (mixed mode). The option I have gone with.

    1. The onboard rom configures on-chip services, then switches to Z80 mode and invokes the RomWBW boot code.
    2. The RomWBW and applications, can access eZ80 on-board ROM routines by invoking the RST.L $XX instruction (using helper macros)
    3. All interrupts are invoked in ADL mode.
    4. Interrupt vector table is within the on-board ROM and invokes code that marshalls to the RomWBW interrupt handler - switching to Z80 mode.
    5. Helper services can be implemented in the onboard ROM (eg: fast math functions)
    6. Helper routines can also be implemented for I/O - to help make it easy to migrate RomWBW I/O code.
    7. Any custom application or other code that hooks it own interrupt handler - will almost certainly fail unless the RETI instruction is changed to RET.L
    8. Now we have 2 ROM images that need to be built and flashed - version control, service responsibility/demarcation complexity. EG: Both the eZ80 ROM and the RomWBW have a #define for the CPU clock frequency.
  4. Switch between ADL and Z80 mode

    1. The switching between Z80 and ADL mode is the same as option 3, but the CPU does not have the MADL bit set, so will not do any mode switching when interrupts or RSTs are invoked.
    2. Not explored this - but might be an option to consider.
    3. I think it means, that when the processor is in Z80 mode, interrupt handlers addresses would be expected to be within the vector table at: {MBASE, I[7:0], XX}
    4. And if the processor is in ADL mode at the time of an interrupt, the interrupt vector table is at: {00h, I[7:0], XX}
    5. The I register needs to be configured to a table of 52 16bit words for each potential interrupt.
    6. For this to work, the I register would need to be a single value that would be correct for both Z80 and ADL execution modes.
    7. The interrupt handler in Z80 mode would not need to have its RETI modified.

Interrupt now working

So given all that, I proceeded to build the interrupt support code, using the V9958's HSYNC int signal, that the tms.asm implements to track the 50/60Hz timing counter.

The eZ80 interrupt handler for the external INT line (mapped to GPIO pin PB4), will marshal the interrupt to the RomWBW interrupt handler (by invoking RST.S %38). The RomWBW handler now ends with a RET.L instruction

If the interrupt was triggered when the processor was in ADL mode, then the eZ80 handler will save/restore the 24bit registers.

It does mean the interrupt processing can be a little bit slow though - all things considering. When I am back I might look at not using MADL - and just have 2 interrupt vector tables and associated handlers.

Cheers
Dean

@wwarthen
Copy link
Owner

Wow, awesome progress Dean. I don't have time to fully review your comments this weekend, but will do so soon.

Thanks!

Wayne

@wwarthen
Copy link
Owner

Hi @dinoboards,

Sorry for the slow response. Thank you for the thorough description of the eZ80 project status. Seems to be going really well. I'll just comment on a couple things.

Regarding the operating mode, I like the MADL mode you are currently using. The ability to hook interrupts and RST calls with eZ80 code is very powerful. Since you have proven that the RomWBW interrupt framework can be adapted to this (via marshalling), this seems like a good way to go.

As you have pointed out, the trickiest part of this going forward is supporting the internal ROM. In addition to your valid point that the internal ROM must be properly matched with the external ROM, there is also the issue of building the internal ROM.

With respect to coordinating the ROMs, I wonder if the internal ROM could pass a version number to the external ROM when it initially transfers control? The external ROM could probably boot far enough to issue a warning if it does not like the internal ROM version.

Building the internal ROM is another question. As far as I know, the Zilog assembler is the only viable option. From what I understand this toolchain is too large to imbed in the RomWBW distribution, not to mention I think it is a Windows-only tool. The fallback solution would be to include the source in the RomWBW distribution along with a pre-built binary. Do you have any other thoughts?

Thanks, Wayne

@dinoboards
Copy link
Contributor

Regarding the operating mode, I like the MADL mode you are currently using. The ability to hook interrupts and RST calls with eZ80 code is very powerful. Since you have proven that the RomWBW interrupt framework can be adapted to this (via marshalling), this seems like a good way to go.

Cool. I only have one hardware module that I can use for testing, that will generate interrupts - my V9958 VDP video module. The 50/60Hz timer. (And I have since implemented a 60Hz timer using the on-chip timers -- so a lot more efficient).

With the way the hardware/gpio pins work on the ez80, there can only be a single interrupt handler in RomWBW. No vectors or fancy interrupt mode stuff. The eZ80 can only work in IM2 mode (technically the ez80f92 chip forces the eZ80 to run only in IM2) , with the various on-chip and GPIO mapped to a specific jump address in the vector table. But as far as HBIOS is concerned, the processor is working in IM1 mode -> $0038.

With respect to coordinating the ROMs, I wonder if the internal ROM could pass a version number to the external ROM when it initially transfers control? The external ROM could probably boot far enough to issue a warning if it does not like the internal ROM version.

Yep - agree - was thinking of something like this.

Updating the eZ80 on-chip flash can only be programmed with Zilog's Acclaim smart cable (cost ~$100US).

I believe it is possible to write code to flash the chip -- so I could write a simple cp/m app to flash a new version of the firmware - but with the usual risk, that if something goes wrong during the reflashing - then the only recovery option would be to use the smart cable.

Building the internal ROM is another question. As far as I know, the Zilog assembler is the only viable option. From what I understand this toolchain is too large to imbed in the RomWBW distribution, not to mention I think it is a Windows-only tool. The fallback solution would be to include the source in the RomWBW distribution along with a pre-built binary. Do you have any other thoughts?

I agree that this code and tool-chain would not be a great fit within your repo - but perhaps a sub-module.
I did manage to use wine to run the tool-chain - I may try and get my firmware repo to have a github-actions script to build the project -- using a bit of docker magic.
I want to think and experiment some more with the tool chain.


Further commits/updates

I have pushed some more recent changes to the RomWBW branch, and my firmware repo. Have got things like the RTC working, a 60Hz timer and cleaned up the interfacing between HBIOS and the eZ80 firmware.

I also managed to get my Compact Flash module working (after some hardware changes). But it only works when i clock the eZ80 at 7Mhz. I think I understand why it wont work at higher clock speeds - and have some ideas to apply to the hardware to make it work.

The timing issue is not just about compatibility with CF module. -- the timing is generally a bit marginal at the moment for certain I/O writes for certain modules. Have a new PCB on the way that hopefully improves things a bit.

Cheers
Dean

PS: Once I get the hardware somewhat stable, I would really like to send you an assembled/working kit -- if you would be interested....

@wwarthen
Copy link
Owner

Thanks for the update @dinoboards.

Cool. I only have one hardware module that I can use for testing, that will generate interrupts - my V9958 VDP video module. The 50/60Hz timer. (And I have since implemented a 60Hz timer using the on-chip timers -- so a lot more efficient).

Great. Does the on-chip timer need to be 60 Hz? RomWBW normally uses a 50 Hz timer mostly because it is convenient for playing audio files.

With the way the hardware/gpio pins work on the ez80, there can only be a single interrupt handler in RomWBW. No vectors or fancy interrupt mode stuff. The eZ80 can only work in IM2 mode (technically the ez80f92 chip forces the eZ80 to run only in IM2) , with the various on-chip and GPIO mapped to a specific jump address in the vector table. But as far as HBIOS is concerned, the processor is working in IM1 mode -> $0038.

I'm a little confused here. The CPU is in IM2. Are you saying that all of the vectors in the vector table point to $38 of Z80 address space? Could they point to someplace in the HBIOS proxy instead?

Updating the eZ80 on-chip flash can only be programmed with Zilog's Acclaim smart cable (cost ~$100US).

I believe it is possible to write code to flash the chip -- so I could write a simple cp/m app to flash a new version of the firmware - but with the usual risk, that if something goes wrong during the reflashing - then the only recovery option would be to use the smart cable.

I didn't realize a $100 cable is required for direct programming. Unfortunate. In-situ programming is an option, but as you say, it can be a problem if anything goes wrong.

Building the internal ROM is another question. As far as I know, the Zilog assembler is the only viable option. From what I understand this toolchain is too large to imbed in the RomWBW distribution, not to mention I think it is a Windows-only tool. The fallback solution would be to include the source in the RomWBW distribution along with a pre-built binary. Do you have any other thoughts?

I agree that this code and tool-chain would not be a great fit within your repo - but perhaps a sub-module. I did manage to use wine to run the tool-chain - I may try and get my firmware repo to have a github-actions script to build the project -- using a bit of docker magic. I want to think and experiment some more with the tool chain.

OK, something to work through.

Further commits/updates

I have pushed some more recent changes to the RomWBW branch, and my firmware repo. Have got things like the RTC working, a 60Hz timer and cleaned up the interfacing between HBIOS and the eZ80 firmware.

Nice.

I also managed to get my Compact Flash module working (after some hardware changes). But it only works when i clock the eZ80 at 7Mhz. I think I understand why it wont work at higher clock speeds - and have some ideas to apply to the hardware to make it work.

At least it is working!

The timing issue is not just about compatibility with CF module. -- the timing is generally a bit marginal at the moment for certain I/O writes for certain modules. Have a new PCB on the way that hopefully improves things a bit.

OK

PS: Once I get the hardware somewhat stable, I would really like to send you an assembled/working kit -- if you would be interested....

I would very much like to get one of these when stable and convenient. I would like to compensate your for the parts and shipping (at a minimum). No rush though.

Thanks,

Wayne

@dinoboards
Copy link
Contributor

Great. Does the on-chip timer need to be 60 Hz? RomWBW normally uses a 50 Hz timer mostly because it is convenient for playing audio files.

Nop - I can change that - in fact, I think I can make it configurable as per the HBIOS configuration file.

I'm a little confused here. The CPU is in IM2. Are you saying that all of the vectors in the vector table point to $38 of Z80 address space? Could they point to someplace in the HBIOS proxy instead?

Yeah - its confusing... Only one of the vectors in the table is marshalled into HBIOS. This is ISR at vector address 38H - which is tied to a GPIO pin (PB4). And this GPIO is wired to the RCBUS's interrupt line. So when a hardware device activates the int signal, the GPIO pin's coresponding interrupt vector routine is executed, which has a handler that delegates to HBIOS.

There are only a couple of other ISR routines in the vector table - one for the on-chip UART, and one for the re-programmable timer (current set to 60hz). These 2 ISR are all managed in the on-chip ROM.

You can see the implemented ISRs in this file https://github.com/dinoboards/rc2014-ez80-firmware/blob/main/src/startup/vectors16.asm#L139

Progress updates

I am feeling a bit of an achievement tonight!

I have think I have solved the hardware compatibility issues, for interacting with such things as the Compact Flash Module. And the timing for the RAM/ROM bank switching ports is now not so marginal.. timings should be in spec.

  1. I now have Spencer's Compact Flash module (version 0). Tested with eZ80 overclocked to 24Mhz.*

  2. Got my version of CH376 USB module working at full speed. Also tested with eZ80 running at 24Mhz

  3. UART fully implemented. I can now use apps like MODE to change the serial baud and control parameters.

  4. CPU clock frequency auto detected and reported during HBIOS start up.

  5. HBIOS platform configuration can now control the Bus Cycle setting for the eZ80 memory and I/O.(there is no way to configure wait states, instead you configure the eZ80 to map clock ticks to T-STATES. So with a Bus Cycle of 4, a read/write operation will happen over the usual Z80 3 T-STATES, where each T-STATE take 4 CPU clocks. ) The current Bus Cycle setting is reported within the HBIOS boot report.

** The V2 of the Compact Flash module may prove more challenging. As this module uses the RC2014' bus clock (which is typically the CPU's clock). For the eZ80 system, I have not connected the CPU clock to the bus clock. I have used one of the Programmable Timers to create a slower bus clock.

The issue for the V2 compact flash module, will be the different phasing of the bus clock. When an I/O operation is initiated, the slower bus clock might be about to change state, or it might have just changed state....

I have purchased the V2 version, so will see if I can make it work also.

But i might work on other modules first - sounds, keyboards, etc....

Cheers
Dean

@wwarthen
Copy link
Owner

Hi @dinoboards,

Thanks for the interrupt explanation. I think there might be a way to improve this to some extent in the future, but I see what is going on at this point.

Excellent progress all around!

Thanks for the update!

-Wayne

@dinoboards
Copy link
Contributor

Hi Wayne,

Me again.

I have been making steady progress on the porting for eZ80 - at least for CP/M

I would expect that there would still be issues with any application or other OS systems that do any I/O - Basic for example -- its OUT/IN commands would not apply a 16bit IO address. There are a lot of services/apps etc that do direct I/O - not sure how far you would want the support for eZ80 to be extended to.

I think i have finally got the hardware design stable. The latest revisions seem good to me - (no bodges required). I had the CPU modules factory assembled now - so a bit easier to get a working system (still lots of through hole components that require soldering - to keep it fun!)

Over the weekend, I was able to get a firmware update process working - without the need for the external Zilog programmer. The eZ80 still needs an initial image programmed in. This image includes a full working firmware (with the boot loader) - it is flashed to the first 64K of ROM space in the on-chip ROM.

And then with a new CP/M application I am wiring, I can 'flash' an alternative firmware image in the 2nd 64K of the on-chip rom (0x010000-0x01FFFF).

The main image will still boot, but detect the alternative image and has been flashed, and so will switch over to it. And if this 2nd (alternative) firmware fails to boot, a reset should cause the 1st (main) firmware to be used.

Of course, there still a risk that a software error causes the on-chip firmware to be non-functioning - and the only recovery option would be the external programmer - I don't think i can totally eliminate that issue (This is the external programmer I purchased: https://au.mouser.com/ProductDetail/ZiLOG/ZUSBASC0200ZACG?qs=IPgv5n7u5QaRBsLmFgyelw%3D%3D)

I cant imagine you would be in a position to accept any PRs I generate without working hardware to review and verify yourself - so I am happy and keen, if you are happy also, to send you a working eZ80 module. Can you email me the address you would like me to post it to. (dean.netherton at gmail.com)

Dean

I do believe it will be possible to build a programmer with something like a Pi Pico or other microcontroller. The ZDI interface is similar (but sufficient different) to SPI - so I guess manually driving some GPIO should work - the registers and commands seemed to be defined sufficiently in the relevant manuals. Another future project for sure

@wwarthen
Copy link
Owner

wwarthen commented Sep 9, 2024

Hi @dinoboards,

This sounds great. I am going to want one of the programmers regardless, so I will be ordering one of them. I hope that eZ80 becomes a more common alternative for RomWBW over time.

Indeed, I would prefer to have hardware sufficient to confirm things are working. I will send you an email to puruse this.

Thanks! Wayne

@dinoboards
Copy link
Contributor

Hi Wayne,

I see you've merge the mega commit - wow that was fast - was expecting it would take a little while.

only had one question about your Pull Request. You have modified the Makefile's to reduce their verbosity (removed -x from .SHELLOPTIONS and added '@' in front of some commands. This certainly makes the output much cleaner, but I am a little worried it will be harder to chase down build issues in the future. Please let me know your thoughts on this.

Of course this is entirely your preference. I usually just use the command like options --debug and/or --trace when invoking the make command if I need to see what its doing. - or just temporarily turn em back on within a specific task if I am trying to hunt down a build issue. But as most of the time the makefiles just work - i found it far easier and quicker to see and identify any assembly errors.

I did an offline merge and found a few minor things that I needed to change to get a clean build on both Linux and Windows. Windows and Linux use different assemblers that are theoretically identical, but they both have some idiosyncracies. I had to make some adjustments due to this.

Oh yes of course - i didn't even think to check for the window builds - kind of assumed the same tool set -- bad me. Haven't look at your fixes yet, i assume this might be in regards to some of the macros I added to help target the ez80?

Hopefully this evening, my time, I will be able to pull your fixes and understand

I expect future PRs will be much smaller. Probably over the coming weekend I will get the SN76489 changes applied correctly, and submit the PR for it.

Thanks again

@wwarthen
Copy link
Owner

wwarthen commented Sep 16, 2024 via email

@wwarthen
Copy link
Owner

wwarthen commented Oct 4, 2024

Hi @dinoboards,

Had some time today to do some compatibility testing. Here are the results along with the changes I made in some cases. Overall very good results. I will be checking in my work shortly.

I did encounter one mystery when working on the IDE driver. It appears that when doing a port read for an unoccupied port, the EZ80_IO macro winds up returning $00. However, doing an IN A,(C) with B set to $FF, returns $CD. The $CD is what I would normally expect.. It appears that port reads in ADL mode are returning $00 for unoccupied ports. I have worked around this in the IDE driver, but curious if you know why this would be.

Thanks, Wayne

  • PPIDE: works
  • IDE: works
    • Increased DELAY -- too short for Pico SD
    • Removed use of EZ80_IO macro in Pico SD wait loop
  • FD: works
    • Added a missing EZ80_IO macro
    • Updated the DLY functions in util.asm to increase delay time
  • LPT: works
    • Added EZ80_IO macros throughout driver
  • IMM: works
    • Added EZ80_IO macros throughout driver
  • PPA: works
    • Added EZ80_IO macros throughout driver
  • LCD: works
    • Added EZ80_IO macros throughout driver
  • TMS: works
  • AY: works
  • CHUSB: working in some cases
    • CH375 works
    • CH376 not working
  • KBD: not working
    • Added EZ80_IO macros throughout driver

wwarthen added a commit that referenced this issue Oct 4, 2024
@dinoboards
Copy link
Contributor

Hi @wwarthen

Fantastic stuff. Exciting... Below are my thoughts

I did encounter one mystery when working on the IDE driver. It appears that when doing a port read for an unoccupied port, the EZ80_IO macro winds up returning $00. However, doing an IN A,(C) with B set to $FF, returns $CD. The $CD is what I would normally expect.. It appears that port reads in ADL mode are returning $00 for unoccupied ports. I have worked around this in the IDE driver, but curious if you know why this would be.

The macro help might be missing a scenario or just broken - i will have a look asap

IDE: works - Increased DELAY -- too short for Pico SD

Is this the extra CALL DELAY added to VDELAY:?

One of the things I tried to do was to verify the DELAY function does indeed delay the approx 16us - regardless of CPU frequency. If VDELAY calls DELAY 3 times, I would think that would cause VDELAY to be longer than specified?

Was this an issue with just the SD IDE or a more general problem?

I certainly found it challenging to introduce appropriate delays - given the way the cpu's pipelining and wait states contribute to effective execution time.

CHUSB: working in some cases
CH375 works
CH376 not working

Hmm - odd. I have my CH376 module - i assume its mostly the same as the other kits based around the CH376 module. I dont have a CH375. my CH376 was working - but perhaps there has been a regression, or perhaps there is a difference in the specific kits.

KBD: not working
Added EZ80_IO macros throughout driver

I assume you mean its not working, despite you placing the EZ80_IO macro everywhere?

Do you think its another timing issue? Or perhaps maybe the macro helper has another bug?

Cheers
Dean

@wwarthen
Copy link
Owner

wwarthen commented Oct 4, 2024

Hi @dinoboards,

Well, I think the EZ80_IO macro and associated firmware code are working fine. Zero issues. It just seems to behave differently when reading an unoccupied port. Since such operations are essentially undefined, this is not an error per se. Just a curiosiry really.

Yes, I was referring to the extra calls to DELAY in VDELAY. I didn't take the time to try and be exact about it, but there is a place in the code that should try for 5 seconds. It is pretty exact on other hardware. It was about 2 seconds when run on eZ80. So, I added more calls. Feel free to take a whack at making it exact, but it was definitely "short" for me. The problem only manifested with the Pico SD in my testing, but the VDELAY routine is critical in many places in the code.

Now that I know you have a CH376 working, I may play with it some more. I need to figure out how to manipulate the I/O wait states as a start.

Correct, I was unable to get reliable communication with a keyboard using the KBD driver. Yes, I had already place the EZ80_IO macro. It felt like perhaps an issue with the I/O pulse not being long enough, but really not sure. I need to do more work.

I will puruse this, but it may be a few days before I can get back to it. Very busy weekend.

Thanks, Wayne

@wwarthen
Copy link
Owner

wwarthen commented Oct 4, 2024

Changing EZ80_IO_MIN_WS from 7 to 8 in the RomWBW config resolves the issues with both the CH376 and the PS/2 KBD driver.

I realize that this implies a change from eZ80 bus cycles to Z80 bus cycles, but I don't currently know the implications of that.

Thanks, Wayne

@dinoboards
Copy link
Contributor

Well, I think the EZ80_IO macro and associated firmware code are working fine. Zero issues. It just seems to behave differently when reading an unoccupied port. Since such operations are essentially undefined, this is not an error per se. Just a curiosiry really.

You are far more trusting of my code than I 😏. I would have expected my macro would do that same as your explicit code and return what ever the data lines float to. Will have a look...

Yes, I was referring to the extra calls to DELAY in VDELAY. I didn't take the time to try and be exact about it, but there is a place in the code that should try for 5 seconds. It is pretty exact on other hardware. It was about 2 seconds when run on eZ80. So, I added more calls. Feel free to take a whack at making it exact, but it was definitely "short" for me. The problem only manifested with the Pico SD in my testing, but the VDELAY routine is critical in many places in the code.

Hmm. OK. Sounds like an inconsistency between the DELAY and VDELAY's loop over DELAY not quite adding up - some overhead/pipelining causing a time-slip!

Changing EZ80_IO_MIN_WS from 7 to 8 in the RomWBW config resolves the issues with both the CH376 and the PS/2 KBD driver.

That's cool to hear.

I realize that this implies a change from eZ80 bus cycles to Z80 bus cycles, but I don't currently know the implications of that.

That's interesting. One of the issues I have with managing I/O timing is the need to stretch the positive edge hold of the IORQ requests. The PLD has a counter, that will count and raise IORQ and WR a cycle before the eZ80 does (for default settings)

This counter value that's used to identify when to raise the signals is hard coded in the PLD. Its currently set to 5 clock cycles.

So increasing the wait states will increase the positive-edge hold period. Which is fine and if this fixes the issue - then it implies that its needed by the KBD/CH376 driver.

The only real implication is a general performance hit - as now all IO requests will take that bit longer - including bank switching.

I guess its technically possible to change the IO wait states prior to specific io operations (eg: only have this wait state applied when doing KBD IO. Maybe another macro (EZ80_SLOW_IO). Not sure....

@dinoboards
Copy link
Contributor

Yes, I was referring to the extra calls to DELAY in VDELAY. I didn't take the time to try and be exact about it, but there is a place in the code that should try for 5 seconds. It is pretty exact on other hardware. It was about 2 seconds when run on eZ80. So, I added more calls. Feel free to take a whack at making it exact, but it was definitely "short" for me. The problem only manifested with the Pico SD in my testing, but the VDELAY routine is critical in many places in the code.

Hmm. OK. Sounds like an inconsistency between the DELAY and VDELAY's loop over DELAY not quite adding up - some overhead/pipelining causing a time-slip!

Ok - my bad - yep -- i see now. the eZ80 version of VDELAY was not correct - and your commit fixed it.

DELAY -> invokes the MACRO EZ80_DELAY (RST.L $18) - 3 are needs to get an approx 16-17us delay
VDELAY -> also invokes said MACRO (but was only invoking it once) - as it inlines DELAY, it needs to do three calls also

@dinoboards
Copy link
Contributor

Well, I think the EZ80_IO macro and associated firmware code are working fine. Zero issues. It just seems to behave differently when reading an unoccupied port. Since such operations are essentially undefined, this is not an error per se. Just a curiosiry really.

You are far more trusting of my code than I 😏. I would have expected my macro would do that same as your explicit code and return what ever the data lines float to. Will have a look...

Hmm. I doubled check and tested the implementation for the macro for IN A, (C) and it appears all correct.

My guess is as the read is performing a request where nothing will drive the data lines - they float - and what state they are in could be influence by what was executed before. Maybe the ADL/Z80 mode has an influence - but i doubt it.

I did a test on my rig. I queried an address with nothing connected. I placed some pull up resistors on a couple of data lines. The value returned was aligned with the pullups. So the data lines were defaulting to 0 - without the pullups.

I got the same value when running in Z80 mode and when using the helper macro (RST.L)

And I would guess that some rigs could have different experiences - depending on the capacitance/load on the data lines.


As such, I cant reproduce the issue you are having.

The only suggestion I have at this stage, is you may want to replace LD B, 0xFF with LD B, IO_SEGMENT

Cheers

@wwarthen
Copy link
Owner

wwarthen commented Oct 5, 2024

Yes, I think it is simply that the data lines are floating (normal when nothing is responding at the port address). It appears (on my system) that the floating data read when in ADL mode is different than in Z80 mode. This is entirely reasonable. I am curious what value you get if you use the RomWBW monitor to read an unoccupied port. The RomWBW monitor is 16 bit aware for port accesses, so you can use a command like "I FF55". On my system, I get CD as a result.

Yes, I will absolute change my workaround code to use IO_SEGMENT. Good catch.

Thanks, Wayne

@dinoboards
Copy link
Contributor

dinoboards commented Oct 5, 2024

I have a theory....

When I did as you asked, queried an unoccupied port with the monitor - I did indeed get 0xCD

I changed the code in the PIN: function in the monitor - and got these results:

Code Snippet Result of `I FF55`
IN A,(C) CALL .... 0xCD
IN A,(C) NOP CALL .... 0x00
PUSH BC POP BC CALL ... 0xC5

It appears that the IN operation is reading the op code of the next instruction.

I wonder if this is a thing for a stock Z80 as well?

So my guess then is, when the IN A, (C) is performed in the on-chip ROM, the external address and data lines are not driven at all - the external RAM will be silent. -- so therefore it reads 0 - (or if you have pull ups on the data lines - it will read the floated data lines as high)

I am a little concerned this might be highlighting an issue with my design. What might be happening, is that as the PC counter will point to the next instruction, and a read is then requested (an I/O read), that somehow the RAM is responding - at least for a short period of time. We know the I/O's work ok - so perhaps by the time the I/O is performed the RAM has been released. But in the case of an unoccupied port that the floating data lines are just holding to the last driven value.

Not concerned so much with that, more just wonder if I might have a bus contention for a period of time. Gonna wire up the scope and do some more experiments

Of course, this might also just be the eZ80 doing it thing - it might be a function of the pipelining. As I type this - i would think that is very likely - as that its design - on each cycle, the eZ80 reads the next byte to processes through its instruction pipeline - so now i am wondering if this is just 'normal'

Learn something new every day!

@dinoboards
Copy link
Contributor

Made a cup of tea - and thought about this a bit more

Not sure how i could test or verify...

But I seem to be convincing myself this is all totally fine and normal.

During the decoding and processing of the IN A,(C) operation, the eZ80 will be reading the next byte from RAM for the instruction pipeline. Hence why the data lines would have been charged with the byte pattern of the op-code of the next instruction.

Dean

@wwarthen
Copy link
Owner

wwarthen commented Oct 5, 2024

I suspect it is totally normal. I do know that a regular Z80 exhibits a similar behavior in many cases (depending on the loading of the data lines).

Thanks, Wayne

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants