• Please review our updated Terms and Rules here

Z80-in-PC: how to go further?

Ruud

Veteran Member
Joined
Nov 30, 2009
Messages
1,989
Location
Heerlen, NL
I created an ISA card for the PC called Z80-in-PC:

Z80inPC.jpg

Why an ISA card? I like both CP/M and older PCs and I wanted to have something that combines both hobbies. It is just a Z80, RAM, ROM, some I/O to handle the extra RAM and a 8255 to communicate with the PC. The PC part is another 8255 and a 74LS688 with DIP switches to set the address where the 8255 can be used.

The CP/M part of the hardware is based on the Bondwell 14: the first 32 KB of RAM can be swapped, the second 32 KB is common RAM. I'm busy writing programs that first test each part separately and then combined. I don't expect any trouble here. but the the CP/M part. I need a ROM that copies part of itself to the top 32 KB of RAM and then disables itself. Shouldn't be a problem either IMHO. But then.

The original CP/M wants to load one or more sectors from a floppy drive but I don't want to use one, I want to work from an hard disk drive. But I think that is also a matter of asking for the right track and sector. Also, AFAIK the only file that has to generated to match the hardware is the BIOS. Here I have no idea where to start at all.

So the basic question: is there a kind of "CP/M for dummies" that explains how to make CP/M working on new hardware ?

Thank you in advance!
 
The 'simplest' way (in my mind) is to run a program on the PC side (depending upon what Operating System you are running of course) that interfaces with your add-in Z80 card.

What you require in the Z80 end is a ROM that copies itself to the upper 32K of RAM (exactly as you have stated) that emulates the CP/M BIOS. By this I mean take the register values passed to the BIOS functions and transfer the values to the PC side via your interface then wait for the response (register values) to arrive down your interface before returning to the code that called the BIOS function.

The BIOS itself would then be coded in whatever high-level language you fancy on the PC side.

The simplest way to implement a 'virtual' CP/M disk would be to allocate a disk file (under your PCs operating system) as the disk drive itself that is specified to the PC program when you run it. One file per emulated CP/M disk drive

See https://www.seasip.info/Cpm/bios.html for a list of BIOS function that you will need to emulate. The full list will depend upon which version of CP/M you are planning on using.

So (for example) the CONST (console status) BIOS function will be entered via a CALL to the BIOS with no register values of any consequence. You would pass a byte code (say a value of 2) to your PC program and wait for a byte response from the PC before continuing.

The PC would decode the byte value of 2 sent to it, and go and look to see what the status of the console buffer is (from the PC keyboard). If no key is pressed, a byte value of 0 is passed back to the interface. If a key is pressed, a byte value of 0xFF is passed back to the interface.

The Z80 BIOS program then retrieves the byte value sent to it from the PC end, and loads that value into Z80 register A and RETurns from the BIOS. This satisfies the BIOS CONST call.

Of course, some BIOS functions will require the transferring of data bytes to/from the Z80/PC (e.g. disk sectors), but this is all down to adequately describing the command/response protocol between the Z80 and PC sides of your interface.

Some of the BIOS functions can just return 'dummy' values for now (for example, LIST, PUNCH, READER, and LISTST).

Functions such as HOME, SELDSK, SETTRK, SETSEC, SETDMA just store data values for the subsequent sector READ and WRITE function.

SELDSK and SECTRAN (on CP/M 2.2 for example) return a disk parameter table (describing the disk drive geometry) and perform sector translation. You can ignore the sector translation, but you will have to think about the disk parameter table (as this will determine the size of the 'virtual disk' file on the PC required to hold the disk information).

Some of the code may be more appropriate on the Z80 side than the PC side and may simplify the interface between the two. For example, you may just want to implement the CONST, CONIN, CONOUT, READ and WRITE functions via the PC interface and the rest within the Z80 BIOS.

I have probably wittered on too much and confused you further by now...

If you want me to expand on this, please ask. I may be able to write you a simple bit of code if you explain how the interface between your Z80 and PC works...

EDIT: This may be too complex for the present time: http://www.cpm.z80.de/randyfiles/DRI/CPM_2_0_System_Alteration_Guide.pdf

I have sketched out a proposed strategy for CP/M 2.2 if you are interested in more details.

Dave
 
Last edited:
So the basic question: is there a kind of "CP/M for dummies" that explains how to make CP/M working on new hardware ?
The CP/M Alteration Guide is still the best source for a good explanation, together with lots of existing implementations (on Github and elsewhere) targeting simple hardware or emulators.

However, I question your hardware design choices. As an ISA card, your Z80 can assume that a PC is present, and the PC is also far more capable than the Z80 system: It provides all I/O (console, floppy, printer), which already requires a PC program. Conceptually, it makes a lot more sense to treat the PC as the "host" and the Z80 as the "device": The Z80 runs as a slave/guest and requests I/O from the PC.

The Z80 card does not need any bankswitching or ROM, just provide 64 KB dedicated RAM and map it into the PC address space. You also need a way to control the Z80 from the PC (at least /RESET, /BUSREQ and /BUSACK - the latter can be used to switch the RAM between "PC access" and "Z80 access"). From the Z80 perspective, the PC then becomes a DMA device and communicates through a shared memory region. This is easy and efficient.

You did not mention any I/O port or interrupt support. But the PC program can poll the shared memory every once in a while (pull /BUSREQ, read the shared memory region for requests, answer them if necessary, release /BUSREQ) and that would be good enough. Allowing the Z80 to interrupt the PC (to tell it "there is a command") would be nicer, but PC interrupt lines are scarce.

CP/M does not need any interrupt support, but other systems (such as MP/M or FUZIX) use them. So the ability for your PC program to send an interrupt to the Z80 can be useful. Even more so if you want to use the Z80 as a coprocessor for something (say, a music processor or something like that), although the lack of external I/O makes that less useful.

In any case, how you design your communication between Z80 card and PC is up to you. But you need to absolutely make sure that you can properly synchronize that communication. And having the PC be the master is likely far easier than having the Z80 being in control.
 
IMHO it depends a lot of how have you implemented the interface. If I had to design it, I would check first how systems like the SEGA Megadrive (Genesys in the US) and NEO-GEO MVS/AES do (both have a 68k with a Z80 coprocessor).

For the CP/M, I would start with a version of CP/M-86 first and modify it to start from a hard drive, ignoring the card at the beginning.

You would need a detection mechanism to check if the card is installed via software. Once the card was detected, I think you should load the Z80 bootstrap code and the required program at the card's memory, then delegate the execution to the Z80. From there, you could make the Z80 act only when required or even make it run asynchronically if you wanted to make a more complex design.

In any case, for the Z80 I wouldn't employ a ROM to make it start. I would employ the same trick as the Mega Drive. The MD, when it boots, it automatically puts the Z80 CPU to a halt by employing the /wait signal, then makes the 68k copy the Z80 program into the Z80 memory space. Once the copy is done, the Z80 is allowed to run again. How is this good? Because if you run this from an HDD or even a floppy, you could load the Z80 bootstrap code from a file, making distribution of the program much easier and also improving the update chain.

For peripheral access... I would employ the PC hardware as is and just request them from the Z80 when needed. Also, the Z80 should have the NMI pin employed to be notified of a command by the main system.

I don't know the specific details about your card, but there are components needed for the arbitrariation that I don't see in there... What is the hardware approach to load contents in there?
 
The Z80 card does not need any bankswitching or ROM, just provide 64 KB dedicated RAM and map it into the PC address space. You also need a way to control the Z80 from the PC (at least /RESET, /BUSREQ and /BUSACK - the latter can be used to switch the RAM between "PC access" and "Z80 access"). From the Z80 perspective, the PC then becomes a DMA device and communicates through a shared memory region. This is easy and efficient.

FWIW, this is my recollection of how several commercial Z80-in-a-PC cards were set up; the RAM on the card for the Z80 was set up so the host could access it (both read and write) and communication was done via shared buffers. The beauty of this technique is that you don't need to have a ROM on the Z80 side of the fence at all; the application on the PC side that drives the CP/M environment can just write the BIOS directly into RAM and trigger a RESET to get the Z80 side running, you don't even need a "boot loader" to get the core of CP/M going.

The big design decision for something like this is how much memory space you want to occupy with it. When these cards came out originally it was probably a perfectly reasonable decision to just give it 64K of space in upper memory, IE, plop it into the D or E page in your 5150, and be able to access the entire Z80 address space. If you don't want your card to suck up that much address space then you might want to define a smaller window and add some kind of paging register. (Since the card in this thread has 512K onboard and a large-granularity bank switching setup to make it all available to the Z80 then presumably you'll also need a pager on the PC side, since unless this was an AT card and occupied Extended memory space you're obviously not going to be able to see all 512K at once.)

(* FWIW, if you designed your Z80 card so it just shows up as a 64K UMB memory block you could actually just use it for that if you're not using the Z80 functionality...)

In any case, how you design your communication between Z80 card and PC is up to you. But you need to absolutely make sure that you can properly synchronize that communication. And having the PC be the master is likely far easier than having the Z80 being in control.

Yeah. Considering how you really can't do bus-mastering with an 8-bit ISA slot there's going to be no way around it, really; I don't think it's feasible to *actually*, say, write an NEC 765 floppy disk driver in Z80 code and let the PC hand the reins over. Essentially you're going to have to think of this as a "paravirtualized" environment, where I/O transactions are reduced to just grabbing bytes on and off a port address or from a shared buffer after sending a request to the PC driver software to read or write a disk sector or whatever. This is why the shared memory window kind of shines as an implementation strategy: you can just define somewhere in high memory a few locations (think of them as "registers") for sending disk I/O commands, console, serial requests, whatever, and the PC software can either regularly poll these locations or the BIOS code on the Z80 side can fire that attention interrupt to get the command serviced. (And in the case of disk I/O you could even have the disk routine on the PC side just magically fill the requested sector into wherever the disk buffer is set to in memory; the "disk driver" in the Z80 code just busy-loops until the semaphore signal that the last command is complete is set by the PC software.)

Not to say that this alternative of having the Z80 have its own ROM onboard and communicate with the host through an I/O channel isn't going to work, it's just not really how the old Z80 cards usually worked. It's going to be a little slower for some operations since you'll have to have each side run software loops to do all the disk/console/control I/O a character at a time, and you also don't get that superpower of allowing the host CPU to just magically make things like boot code appear in the Z80 space.
 
FWIW, this is my recollection of how several commercial Z80-in-a-PC cards were set up; the RAM on the card for the Z80 was set up so the host could access it (both read and write) and communication was done via shared buffers. The beauty of this technique is that you don't need to have a ROM on the Z80 side of the fence at all; the application on the PC side that drives the CP/M environment can just write the BIOS directly into RAM and trigger a RESET to get the Z80 side running, you don't even need a "boot loader" to get the core of CP/M going.
We pretty much said the same thing! :ROFLMAO:

I was thinking more about the interrupt system when I wrote my post. For as much as one writes Z80 drivers, one can't hijack the interrupt system to interrupt the Z80 instead of the 8088. And if one could, the Z80 has a very known incompatibility with the 8259 PIC.

The very good thing is that the CP/M (8080) and the CP/M executables can be differentiated by extension (COM vs CMD), so I think the best way to make it work is activating the card just whenever needed for COM execution.

@Ruud I am sorry, but I think you will have to return to the design board this time. The idea was good, but not everything was considered and I fear it won't work as is. I am also interested in a board of this kind and I have some ideas, so if you wanted to exchange ideas or even accept collaboration, I would like to participate.
 
Thank you all for your answers. First: I completely forgot to add the link to this project on my site. Second: this design is on purpose. See it as an EasyZ80 but with a complete PC as terminal instead of a real one. There are enough emulators around that can do the job much faster if I needed that. I just wanted to play with the real hardware from both worlds. FYI: I even thought about using a C64 instead of a PC but the 40-char screen was the main stopper. But I'm still thinking about a CBM 8032 version.

Regarding interrupts: I already mentioned that the design uses the Bondwell 14 as base. The Z80 only receives interrupts from the serial port and the 6821 that handles the keyboard, the printer port and the interrupt from the 8272 Floppy driver IC through one of its I/O pins. As none of these ICs are present, I thought I didn't need an interrupt. But then I realised that a "keyboard" interrupt still is needed so I connected one of the 8255's PC pins to the INT pin of the Z80.

I'll start with this CP/M 2.0 System Alternation Guide to see what that brings.

Thanks again!
 
Thank you all for your answers. First: I completely forgot to add the link to this project on my site. Second: this design is on purpose. See it as an EasyZ80 but with a complete PC as terminal instead of a real one. There are enough emulators around that can do the job much faster if I needed that. I just wanted to play with the real hardware from both worlds. FYI: I even thought about using a C64 instead of a PC but the 40-char screen was the main stopper. But I'm still thinking about a CBM 8032 version.

Regarding interrupts: I already mentioned that the design uses the Bondwell 14 as base. The Z80 only receives interrupts from the serial port and the 6821 that handles the keyboard, the printer port and the interrupt from the 8272 Floppy driver IC through one of its I/O pins. As none of these ICs are present, I thought I didn't need an interrupt. But then I realised that a "keyboard" interrupt still is needed so I connected one of the 8255's PC pins to the INT pin of the Z80.

I'll start with this CP/M 2.0 System Alternation Guide to see what that brings.

Thanks again!
Another piece of advice: employ a 8 MHz-graded Z80 and you will be able to remove the oscillator, simplifying the design.
 
Ah, a picture (or a schematic in this case) is worth a thousand words...

This is all doable!

I see you have specified CP/M 3.0.

Dave
 
Can your Z80 card generate an interrupt on the ISA bus?
Since that would be quite handy to handover the CPM bios calls to the PC side.
 
There is a link on the schematic for this purpose (according to Ruud's online schematic).

There is also the potential for an interrupt capability the other way (ISA to Z80).

Dave
 
Last edited:
Found the correct schematic.

Have you thought about using the 8255 in Mode 2 (directly connecting port A to the ISA bus?) instead of using two 8255 back-to-back ?
 
Considering how you really can't do bus-mastering with an 8-bit ISA slot there's going to be no way around it, really; I don't think it's feasible to *actually*, say, write an NEC 765 floppy disk driver in Z80 code and let the PC hand the reins over. Essentially you're going to have to think of this as a "paravirtualized" environment, where I/O transactions are reduced to just grabbing bytes on and off a port address or from a shared buffer after sending a request to the PC driver software to read or write a disk sector or whatever. This is why the shared memory window kind of shines as an implementation strategy: you can just define somewhere in high memory a few locations (think of them as "registers") for sending disk I/O commands, console, serial requests, whatever, and the PC software can either regularly poll these locations or the BIOS code on the Z80 side can fire that attention interrupt to get the command serviced. (And in the case of disk I/O you could even have the disk routine on the PC side just magically fill the requested sector into wherever the disk buffer is set to in memory; the "disk driver" in the Z80 code just busy-loops until the semaphore signal that the last command is complete is set by the PC software.)

Is it not possible to drive a peripheral from a peripheral on IBM PC? E.g. DMA controller on cascade will float off the CPU and itself.
 
Is it not possible to drive a peripheral from a peripheral on IBM PC? E.g. DMA controller on cascade will float off the CPU and itself.

I know the AT bus has some provisions for slot busmasters (there’s a MASTER signal on the 16 bit extension, that everything I’ve read about it paints as notoriously difficult to use), but I’m pretty certain there’s no clean way to negotiate it on the XT bus?
 
This might be interesting but they already mention that DMA on the XT-ISA bus is slow:


But then again, how much speed do you need for a Z80 to interact with the PC hardware.
For CP/M is probably more than enough to communicate from CPM BIOS to an PC host program/driver running under interrupt through a single bidirectional register. Something a single 8255 can handle.
 
ISA DMA is only slow on systems faster than a non-turbo PC/XT. But because of its limitations and lack of DMA channels, I wouldn't bother.

Mapping the whole Z80 address space into the PC is convenient, but also troublesome: It is not always easy to find a full 64 KB hole in upper memory, especially when EMS hardware is also present. Unused UMB space is also valuable on 386+ systems. Adding bank-switching on the Z80 side also adds a ton of complications. It's something I'd seriously consider leaving out.

Controlling the Z80 already requires a register in I/O space, so configuring an I/O range is necessary anyway. Might as well use that for indirect memory accesses, too. It's not like there's a huge need for bandwidth, especially not while the Z80 itself is running, and allows accessing the whole 512 KB or whatever the Z80 board has. Another option is to just map a smaller, fixed range (4 or 8 KB) as a shared memory buffer and have Z80 software deal with it.

Interrupts are way too useful to ignore. Giving the Z80 the ability to interrupt the host makes it possible to (efficiently) implement the PC-side software as a TSR. At that point, the Z80 card can be used to run CP/M or as a co-processor. An 8 MHz Z80 with dedicated memory in an XT-class machine is powerful enough to make a difference.
 
Another option would be to use a dual port memory of a few kB and some signalling protocol.

You could even modify a simple MDA card with SRAM and replace that with dual port memory and connect the Z80 to the other side...:)
 
Can your Z80 card generate an interrupt on the ISA bus?
Since that would be quite handy to handover the CPM bios calls to the PC side.
No, but it could be done. The needed parts are present. But as I see it, it is not needed. The only thing I see the PC doing is polling the 8255 for commands. The PC is only used as a glorified terminal. If that is wise..... but for the moment I like it.

There is also the potential for an interrupt capability the other way (ISA to Z80).
I already mentioned the need for it so I implemented it in the new version. I only need to connect one of pins of port C with the interrupt input of the Z80.

Have you thought about using the 8255 in Mode 2 (directly connecting port A to the ISA bus?) instead of using two 8255 back-to-back ?
To be honest, I have no idea what mode 2 does. In the 40 years I use the 8255 for various projects, I only used it in modes 0. This design is simple to understand IMHO and that is worth something as well.
 
Yes, the Keep It Simple rule applies.

In this case, polling is probably faster even than interrupts, as neither machine would probably be operating concurrently - as the PC will.be waiting for the Z80 to ask it to do something, and the Z80 will then be waiting for the PC results.

You just require a simple 8-bit data port in one direction, another 8-bit data port in the other direction and some control signals to synchronise transfers in both directions.

Exactly as you have implemented.

Dave
 
Another option would be to use a dual port memory of a few kB and some signalling protocol.
Dual-port memory chips are relatively rare and expensive. It is easier and cheaper to just stop and isolate the Z80 while the PC plays busmaster.

To be honest, I have no idea what mode 2 does. In the 40 years I use the 8255 for various projects, I only used it in modes 0.
You might want to look that up. Mode 2 is basically a bus interface, while mode 0 is equivalent to a few glorified latches. You are leaving a lot of functionality on the table for no good reason.

You just require a simple 8-bit data port in one direction, another 8-bit data port in the other direction and some control signals to synchronise transfers in both directions.
In this case, the two 8255 are overkill. Two 74xx573 would suffice for Z80 status/control and do not require any software.
 
Back
Top