Close
0%
0%

3-Chip Z80 Design

Combining a Z80 retro design with a modern PSoC CPU.

Similar projects worth following
A Z80 CPU, 512K SRAM and a Programmable System on a Chip make a functional retro-future CPU card

Using a Z80, 512KB SRAM and a Cypress Semiconductor Programmable System on a Chip (PSoC) to make a 3-chip Z80 retro-future computer All of the I/O is performed in the PSoC by emulation of Z80 peripherals.

The PSoC has the following advantages:

  • 5V I/O which lets the PSoC interface directly with the Z80
  • Programmable Logic array to implement Z80 glue logic and peripherals
  • 32-bit ARM core
  • PSoC can download the program to the SRAM so there's no need for EPROM

A three chip Z80 design schematic is something like:

This has three parts - a 512KB SRAM, the Z80 and the PSoC.

The oscillator is optional unless precise serial connections are required. This is not necessary in most applications since the PSoC has a USB interface.

The PSoC could easily emulate the control/status registers of the DART/SIO and CTC and other common Z80 peripherals. Additional parallel I/O could be easily added with an optional MCP23017 chip which would only need to connect to I2C pins on the PSoC (this is slower than the PIO, but probably still OK for most applications).

This is similar to the Z80-MBC2 board without requiring the complications of the ATMEGA interface. In that design, the ATMEGA interfaces to the CPU force feeding it instructions with boot sequences. And this is one less IC.

Booting Software Builds

In this design, the PSoC downloads the software to the SRAM and takes the Z80 out of reset. Hardware/software implementations which have both EPROM and RAM could be "emulated" by banking the SRAM. Control of upper RAM address bits allows a bank of the SRAM to work as an EEPROM. The program can be downloaded from the emulated SRAM into the SRAM itself.

Z80 Peripheral Emulation

The Z80 peripherals can be emulated - mostly in hardware. For instance, the DART/SIO would consist of data and control/status registers which are I/O mapped to the Z80 and memory mapped to the PSoC. An 8-bit mailbox interface can be used for all peripherals.

Counter/Timers could also easily be simulated in PSoC hardware.

The PCB would be less expensive (than my previous design) since it could easily be built in a board that is smaller (less than 100x100mm which is the sweet spot for PCB costs. PCBWay wants $14 for 10 boards that are larger than 100x100mm but only $5 for 10 boards which are less than 100x100mm or less.

  • 1 × Z80 - pick your own speed
  • 1 × PSOC5LP - Programamble System on a Chip - 100 pin TQFP
  • 1 × AS6C4008-55PCN 512KB SRAM

  • Compact Flash Emulation?

    land-boards.com2 days ago 0 comments

    I'd like to get something very difficult going next. Maybe Grant's Compact Flash code? I'd like to get it working with Grant's stock 9-Chip Z80 code. I'd really like to map it to an SDHC card instead of the more ancient Compact Flash. An SDHC card only needs a SPI bus connection for the physical connection.

    Physical Connections in Grant's Design

    Grant has the following connections in his 9-Chip Z80 design to connect to the CF from the Z80 bus.  

    Grant uses I/O addresses 0x10-0x1F and connects the Z80 lines A[2..0] directly to the CF. Makes me wish I had put the Z80 lines onto a connector - I'd only need a Chip Select now and that would be easy.  Grant generates a single Chip Select line when he is accessing the CF card that covers the 0x10-0x17 range. (The Front Panel address range (0x18-0x1F) is already out of the way so I have no conflicts with his code.)

    Grant hooks up other Z80 lines:

    • Power (5V) and Ground
    • A[0..2] - already mentioned
    • D[0..7]
    • IORD*
    • IOWR*
    • CS* - Chip select - asserted for the address range 0x10-0x17
    • RESET* 
    • BUSY_LED - Status LED (from the CF card) - Nice to have but, don't need it

    CF Mode?

    Grant's page describes the method of connection as:

    Additionally, the CF is set up as "IDE" mode so it only requires 3 address lines 
    and one chip select.
    

    This is why Compact Flash Drives can be connected to IDE connectors with purely passive connections.

    This page has some hints on the IDE mode. There's more information here.

    Grant has the following in his monitor.asm code:

    ; CF registers
    CF_DATA        .EQU    $10
    CF_FEATURES    .EQU    $11
    CF_ERROR    .EQU    $11
    CF_SECCOUNT    .EQU    $12
    CF_SECTOR    .EQU    $13
    CF_CYL_LOW    .EQU    $14
    CF_CYL_HI    .EQU    $15
    CF_HEAD        .EQU    $16
    CF_STATUS    .EQU    $17
    CF_COMMAND    .EQU    $17
    CF_LBA0        .EQU    $13
    CF_LBA1        .EQU    $14
    CF_LBA2        .EQU    $15
    CF_LBA3        .EQU    $16
    

    This seems pretty straightforward. Where there are two EQUs at the same address the first one is the write value and the second is the read value. I'm going to add support for this to the 9-Chip build. I will spare the reader the details - at least for the present moment - but I want to set it up to step through the code and watch what the Z80 tries to do when booting CP/M. This will be educational - for me at least.

    This will be very similar in steps to the M6850 emulation I completed in the most recent logs. If this looks promising I may pick it up here in the logs for this project.

    Added: A couple of quick observations.

    Grant's code in monitor.asm only does reads of particular low sectors. That's because it is loading CP/M. Once CP/M loads it must have it's own software for read/writing SD card sectors. That may complicate emulation because I may need to dump the code that is running . Fortunately Grant includes the CP/M code.

    The file cbios128 looks like it's the low level IO routines and it uses the same $EQU values as Grant's monitor.asm code. So it should be easy to pick out what it does from there.

  • 6850 Emulation with a PSoC (Part 4)

    land-boards.com2 days ago 0 comments

    In the last 3 logs we created an M6850 emulator based on the SIO that we already have. At the last step the program compiled correct. Let's try and get it to work. I'm sure I made mistakes when I did the previous changes. First I'm going to check the current build into GitHub with a note that it's a WIP.

    First, I've turned the Front Panel back on so that I can check the downloaded code. That is now as easy as changing the #undef to a #define for the Front Panel. Stepping through the first part of the code shows it loaded properly into the SRAM.

    However, pressing RUN on the Front Panel doesn't show any output on the USB port. So much for just up and running. I am probably missing some subtle part of Grant's implementation. Just to rule out breaking something fundamental I switched back to the 9-build code and it ran fine. So it's apparently something related to the M6850 emulator.

    Debugging I/O with the PSoC Debugger

    Debugging is really nice with the PSoc. You can set breakpoints and run the code. The Z80 waits around for the PSoC and since it's a CMOS part there's no problem with asserting WAIT* indefinitely. Assuming the problem relates to the I/O code let's set a breakpoint at the beginning of the handler - function HandleZ80IO( ). The debugger stops at the beginning of main and has to be told to Resume Execution (F5 is the shortcut). 

    Since I have the Front Panel code still emulated I have time to restart PuTTY before hitting the RUN button on the Front Panel. This probably isn't strictly required since the PSoC waits for the USB to come up before it does I/O anyway.

    F11 single steps through the PSoC code. The first transfer has ioCrtlRegVal with the value 0x1A. As you might expect that's a REGULAR_WRITE_CYCLE. The register ioZ80Addr shows that the Z80 is writing to address 0x80. Grant's INIT code shows this access first.

    INIT:
                   LD        HL,TEMPSTACK    ; Temp stack
                   LD        SP,HL           ; Set up a temporary stack
                   LD        HL,serBuf
                   LD        (serInPtr),HL
                   LD        (serRdPtr),HL
                   XOR       A               ;0 to accumulator
                   LD        (serBufUsed),A
                   LD        A,RTS_LOW
                   OUT       ($80),A         ; Initialise ACIA
    

     Earlier we saw 

    RTS_LOW         .EQU     096H
    

    Stepping through the code shows it calling M6850WriteCtrl( ) as it should and the variable M6850_Ctrl is being set to 0x96 (as it also should). We can also see a TBD we left there which says there may be more to do when the control register is written. After stepping through this code i hit F5 to run until the next I/O request.

    The next I/O transfer has ioCrtlRegVal with the value 0x1C which is a REGULAR_READ_CYCLE of the same location which should be a read of the status register.  This could be where the problem is since that is uninitialized at startup unless there's code added to set the initial values. The SIO code didn't need any initialization of the values. The value is 0x00 and may or may not be initialized by the CPU. That would indicate that there is no receiver character but it also indicates that the transmitter is busy sending a character and it will never get set to anything else. 

    My guess is that Grant's software will spin on reading this until it gets set to indicate it is empty. Let's add code to initialize the M6850 status register before the code runs. We are missing any handler at all for this bit so it will also need to be fixed in the transmit routine.  It looks as if this is the only bit that needs to be set since the other bits are all 0's when running.

    We handle handshake in sending to the PC by holding off deasserting WAIT* until the PSoC code is called to send the data out the serial port.

    We have to hit Stop Debugging to edit the program in PSOC Creator. The initialization code looks like:

    ///////////////////////////////////////////////////////////////////////////////
    // void initM6850StatusRegister(void) - Set the initial value of the status reg
        
    void initM6850StatusRegister(void)
    {
        M6850_Status = 0x2;
    }
    

    We need to call this code...

    Read more »

  • 6850 Emulation with a PSoC (Part 3)

    land-boards.com2 days ago 0 comments

    In this log we will look at interrupts for the M6850. [Edit: this is a helpful page to understand the three interrupt modes supported by the Z80. Grant's code uses Interrupt Mode 1]. We left the compilation broken for interrupts and we need to fix those by creating the code and handing the differences vs the SIO. We also don't know exactly how Grant is handling interrupts. We do know from Grant's source code that he's apparently using them for receive and not for transmit. One difference between the Z80 peripheral chips and M6800 family parts like the M6850 is that there is no interrupt vector produced for the M6850 type of part.

    The Z80 handles the lack of interrupt vector by having a special vector of 0xFF for devices which are not present. This seems to rely on the data bus floating high. We can probably do better and send out a 0xFF with a proper interrupt acknowledge cycle. Let's make this assumption and see if it works.

    The errors that the compiler is producing now are both related to missing functions/values since they are not yet implemented for the M6850.

    The SIO reads the value of the Interrupt vector from one of the control registers. As noted there's no such register in the M6850 so let's fix that. The error is coming out of the Z80IOHandle code:

        if ((ioCrtlRegVal & IACK_MASK) == IN_IACK_CYCLE)
        {
            SioReadIntRegB();
            return;
        }
    

    Set the #define to conditionally remove the SIO code when the SIO is not used. 

    The SIO code for the routine is:

    ///////////////////////////////////////////////////////////////////////////////
    // void SioReadIntRegB(void) - Read the Interrupt vector
    
    void SioReadIntRegB(void)
    {
        Z80_Data_In_Write(SIO_B_WR2);
        IO_Ctrl_Reg_Write(IO_Ctrl_Reg_Read() & 0xFB);   // Clear IRQ* line
        ackIO();
    }
    
    

    Z80_Data_In_Write(SIO_B_WR2) - returns the interrupt vector to the SIO. The next line clears the interrupt source. We still haven't set the interrupt anywhere so we will need to deal with that, but let's create a flag after we make the equivalent function for the M6850.

     is not defined but we need to replace it with an the proper bit for the M6850. According to the data sheet this is bit 7 of the status register.

    For the M6850 the code looks like:

    ///////////////////////////////////////////////////////////////////////////////
    // void M6850ReadIntReg(void) - Read the Interrupt vector
    
    void M6850ReadIntReg(void)
    {
        Z80_Data_In_Write(0xFF);
        IO_Ctrl_Reg_Write(IO_Ctrl_Reg_Read() & 0x7F);   // Clear IRQ* line
        ackIO();
    }
    

    After adding the function prototype to the Z80_Emul.h file the only compiler error is:

    So we are still missing an interrupt #define. That is used in a function in Z80_IO_Handle.c:

    void ackIO(void)
    {
        IO_Ctrl_Reg_Write(IO_Ctrl_Reg_Read() | CLR_IO_INT_BIT);
    }
    

    That is one of the #define values in Z80_SIO_Emul.c. It controls the interrupt hardware from the PSoC to the Z80, so it would be better added to the Z80_IO_Handle.h file. After doing that the PSoC compiles without error.

    As a rough measure the M6850 emulator file is 114 lines long as compared to the SIO emulator file which is 444 lines. This shows just how easy it is to create relatively simple peripherals in the PSoC. It's made even easier in this instance since we have a Serial UART emulator already in the SIO software.

    In the next log we will see what we missed and fix it so the code runs.

  • 6850 Emulation with a PSoC (Part 2)

    land-boards.com2 days ago 0 comments

    In the last log we got the basic functions in place to emulate a M6850 ACIA UART with the PSoC controller used on this card. We left a few details open but the code compiled without error so we've got a good place to fill in the details here. Hopefully, the serial transmit and receive functions will work as-is. 

    The status and control register handler needs some work. The receive part of the status register might just work since it's in the same bit and has the same meaning as the SIO.

    Hardware Handshake Handling

    In particular, we've been ignoring the differences between the SIO and M6850 where it comes to dealing with RTS. The SCC had a single bit and the M6850 has two bits. Grant's intmini.asm file has the answer for RTS values. These are possibly the hard coded values that Grant writes to the control register to toggle RTS on and off.

    RTS_HIGH        .EQU     0D6H
    RTS_LOW         .EQU     096H
    

     In fact, these are only different in the two relevant bits D6 and D5.

    RTS_HIGH sets D6..D5 to b'10.
    RTS_LOW sets D6..D5 to b'00.

    This correlates to RTS high and RTS low for the two cases . He sets Transmit Interrupt Disabled in both cases.  This means we don't probably need to generate interrupts for transmits. That's consistent with Grant's monitor code in his 9-chip design.

    This also means we don't need to have all that many side effects in the control register write routine. In fact the routine doesn't need to do anything other than set the value in the status register since it's being set in another routine. The RTS bit is in SIO_A_Ctrl2 so lLet's look at the routine which uses this value. That function is inside the Z80_SIO_emul.c file and for the SIO has:

    ///////////////////////////////////////////////////////////////////////////////
    // uint8 checkSIOReceiverBusy(void) - Check the SIO port A receiver status
    // Returns: 
    //  0 if the port can take another character
    //  1 if the port is busy and can't take another character
    
    uint8 checkSIOReceiverBusy(void)
    {
        if ((SIO_A_Ctrl2 & SIO_RTS) != SIO_RTS)
        {
            return(1);
        }
        return (SIO_A_RD0 & SIOA_CHAR_RDY);
    }
    

    That is called from main( ) as a function so we can replace that function directly with one for the M6850. Here's what it looks like for the SIO.

                    USB_To_Z80_RxBytes_count = USBUART_GetAll(buffer);
                    if ((USB_To_Z80_RxBytes_count == 1) & (checkSIOReceiverBusy() == 0))     // Input 1 character immediately
                    {
                        sendCharToZ80(buffer[0]);
                        USB_To_Z80_RxBytes_count = 0;
                    }
    

    I think it would be smart to replace the specific name of checkSIOReceiverBusy with a more generic value specific to this function. Also, we didn't get a compile error so the compiler must be including the SIO code and we want that removed. The find-replace in all uses is easy and produces this as the prototype:

    uint8 checkSerialReceiverBusy(void);

    Adding #ifdef  USING_SIO around the SIO code will remove it from conditional compilation and we should get a list of functions we need to create for the M6850 when we compile the code. After that we get a list of missing functions:

    Some of these are real but some are missing #includes. We expected the checkSIOReceiverBusy call to be missing but we need to do something with the other 3 functions. They are SIO specific and may or may not need to be replicated for the M6850. First, let's make sure that the M6850 is also conditionally included by bracketing the code with the appropriate #define. Once we do that the compiler warnings/error list surprisingly drops. This implies that there are implicit declarations from the SIO although they are indicated from the Z80_IO_Handle file. I will ignore them for now and look at them later. For the moment we need to deal with the function at hand. From the SIO we see that the function is like this:

    uint8 checkSerialReceiverBusy(void);
    

    Adding this as a stub function to the 6850 emulator .c and .h files should remove the compilation error.  The header for the function from the SIO emulator has the following:

    ////////////////////////////////////...
    Read more »

  • Emulating the 6850 With a PSoC

    land-boards.com2 days ago 0 comments

    This should be the final piece of work to get Grant's 7-chip Z80 design to run on this hardware.

    This is arguably the "hardest" part of the port since the 6850 emulator does not yet exist. I've gone through the front end details of creating Z80 peripheral chips in a lot of details for the PIO (although the series is not yet complete) so I will spare the details here. Here's the first log of that series (Z80 Peripheral Emulation Guide for the PSoC). Since the 6850 is more or less a very cut down version (not really a version) of the SIO we will start from that design, clone it, and change the functions that need changes to match. 

    The significant differences between the SIO and the 8650 include:

    FeatureSIO6850
    Ports21
    Command/Status InterfaceComplicated requiring a write then read or write cycleSimple with single control/status location
    InterruptsUses Interrupt AcknowledgeInterrupts but no Interrupt Acknowledge hardware

    Modify Z80_IO_Handle.c

    We've already put #define conditional selection in this file to remove the SIO so we just need to clone that for the 6850 so let's start by copying that code and deleting what we don't need. Also, we need to use the proper #define USING_6850 to select the code when there's a 6850 being emulated. That should all happen inside the HandleZ80IO() function. That looks like this:

    #ifdef USING_6850
            case M6850_D:
                if (ioCrtlRegVal == REGULAR_READ_CYCLE)             // regular read cycle
                {
                    M6850ReadData();
                    return;
                }
                else if (ioCrtlRegVal == REGULAR_WRITE_CYCLE)      // regular write cycle
                {
                    M6850WriteData();
                    return;
                }
                break;
            case  M6850_C:    // Control register
                if (ioCrtlRegVal == REGULAR_READ_CYCLE)             // regular read cycle
                {
                    M6850ReadStatus();
                    return;
                }
                if (ioCrtlRegVal == REGULAR_WRITE_CYCLE)      // regular write cycle
                {
                    M6850WriteCtrl();
                    return;
                }
                break;
    #endif

    Of course, as seen by four yellow exclamation boxes, we now broke the compilation since we've created a bunch of function calls to handle the Z80 access which don't yet exist. 

    Let's go create the four routines we need to handle these conditions. If we are careful enough the functions in main which call these handlers will still work. If we need to we can add some additional conditional compilations to handle them. In all likelihood we will have to modify the calling routines given that the SIO is interrupt driven for receive and this one may be different (yet to look at the details enough to see - it may be nearly the same). At the very least, interrupt acknowledge cycles will need to be different.

    Low Level 6850 Handler

    To do this, create two new source files in PSOC Creator (Z80_6850_Emul.c and .h). Liberally copy the pieces from the SIO equivalent and change what is necessary. Leave as much as possible the same to minimize changes to calling routines. Generally speaking, this means to replace the SIO_A with M6850_ in most places.

    There are two lines we have to pay particular attention to. These determine which bits are used for the calling routines to test receive character ready and set RTS. For the SIO, these lines are:

    #define SIOA_CHAR_RDY   0x1
    #define SIO_RTS 0x2
    

     It's a bit hard to read in the datasheet but here's the values of the registers in the 6850:

    The status register bit 0 is receiver data full. That's convenient since that's exactly what we have for the SIO so we won't change that #define value. But there's no bit which directly sets the RTS and we do need hardware handshake for flow control since the Z80 is much slower than a USB block transfer. The control register in the 6850 handles the RTS in a slightly different manner:

    We'll have to look closer at Grant's assembly code INTMINI for the answer on how these bits need to be set but for the moment let's just make a mask and define the bits for the four patterns. This will replace the RTS code above and may necessitate some changes in the main( ) calling code.

    The top of the .c file now looks like:

    #include <project.h>
    #include "Z80_6850_emul.h"
    #include "Z80_IO_Handle.h"...
    Read more »

  • Building the 7-Chip Z80 Software

    land-boards.com2 days ago 0 comments

    In the last log we added conditional compilation flags that allow us to use other software builds. We were left with two undone tasks:

    • Build the software
    • Add 6850 emulation

    Let's tackle building the software. Grant's folder is a bit confusing since there are 3 .HEX files and we only need one file to create the image. 

    BASIC.HEX is the BASIC interpreter. INTMINI is a short interrupt handler for serial I/Ot that replaces the monitor code from the 9-chip design (which had both interrupt handler and monitor plus CP/M loader). ROM.HEX is the file that would be loaded into a ROM in the "real" design. 

    There's a couple of ways to get what we need out of this. One would be to compile the code. I was able to use TASM with my Windows 10 machine in an earlier log by running TASM under DOSBOX. There's also a newer TASM version which allows it to be assembled under Windows 10. 

    An easier way would be to use srec_cat to convert the .hex file to a C-Array. That is described in detail in this log. The command line is:

    path_to_srecord\srec_cat ROM.hex -intel -o GS_7_CHIP_ROM.c -C-Array

     That worked and created a C file with the following at the top:

    /* http://srecord.sourceforge.net/ */
    const unsigned char eprom[] =
    {
    0xF3, 0xC3, 0xB8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x9F, 0x00, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x74, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    ...
    

     Chedking this code from the relevant Z80 Opcode chart:

    Opcode 0xF3 is DIS INT (Disable Interrupts) and 0xC3 is JMP (jump). Grant's INTMINI has this code at the top.

    ;------------------------------------------------------------------------------
    ; Reset
    
    RST00           DI                       ;Disable interrupts
                    JP       INIT            ;Initialize Hardware and go
    
    ;------------------------------------------------------------------------------
    ; TX a character over RS232 
    
                    .ORG     0008H
    RST08            JP      TXA
    
    

    So, it looks like the code matches just fine. Next, add a new file to the PSOC project: calling it GS_7_BASIC_ROM.c which we will place the newly generated code into.

    After dropping the file and making a few changes, the top of this file looks like:

    #include "Hardware_Config.h"
    
    #ifdef GRANT_7_CHIP_Z80
        
    const unsigned char gs7chip_basic_eeprom[] =
    {
    0xF3, 0xC3, 0xB8, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x9F, 0x00, 0xFF,
    

    We have the #define conditional compile that gets set by the Hardware_Config.h file. The fact that the new code is not greyed out indicates that it will be included in the next PSOC build that is done. The inserted #define requires a #endif at the end of the file to close it.

    #define EPROM_FINISH      0x00002000
    #define EPROM_LENGTH      0x00002000
    
    #endif
    
    /* [] END OF FILE */
    

    The array is renamed to be a name specific to this build. We could name all of the builds the same thing but it might be possible in the future to include multiple builds and select the build to run in some manner, so by pressing different front panel switches, so we are making this build specific to allow that possibility. This does mean that we need to add to the SRAM loading software for each specific build.

    That is done inside the function that loads the software in the ExtSRAM.c file.

    ////////////////////////////////////////////////////////////////////////////
    // void loadSRAM(void) - Load the SRAM on the card with the ROM code
    // Initial code build is monitor plus BASIC code
    
    void loadSRAM(void)
    {
        uint32 charCount;
        uint32 SRAMAddr = MONITOR_START;
        volatile uint8 dataVal;
        for (charCount = 0; charCount < MONITOR_LENGTH; charCount++)
        {
    #ifdef GRANT_9_CHIP_Z80
            dataVal = monitor_basic_eprom[charCount];
    #endif
    #ifdef GRANT_7_CHIP_Z80
            dataVal = gs7chip_basic_eeprom[charCount];
    #endif
            WriteExtSRAM(SRAMAddr,dataVal);
            SRAMAddr++;
        }
    }
    

    We also need to add the definition to the top of the ExtSRAM.c file:

    #ifdef GRANT_9_CHIP_Z80
    extern unsigned char monitor_basic_eprom[];
    #endif
    #ifdef GRANT_7_CHIP_Z80
    extern unsigned char gs7chip_basic_eeprom[];
    #endif
    

    At this point the software builds in PSOC Creator but if we run it there will be no...

    Read more »

  • Supporting Other Software Builds

    land-boards.com2 days ago 0 comments

    I want to be able to support any software build I find "out there". I started with Grant Searle's 9-chip Z80 design and got that working well.

    The next step is to make it easy to add other builds. I could just spawn a new PSoC project but that means any improvements to the basic code itself need to get propagated into the previous builds and that's painful.

    Instead, I added conditional compilation using #define, #ifdef to select the different options.

    Here's how it's layed out. I put all of the conditional directives into one single file, Hardware_Config.h. This file breaks into separate sections.

    The first section contains global hardware #defines. These map to the hardware used on the card itself. If there's no front panel it gets removed from the compilation. Same for the expansion MCP23017 I2C expander.

    // Global choices based on hardware used/not used
    // To include the function comment out the undef and use the include
    //#undef USING_FRONT_PANEL        // Assume no front panel
    #define USING_FRONT_PANEL     // Use front panel
    //#undef USING_EXP_MCCP23017      // Assume no MCP23017 I2C I/O expansion part
    #define USING_EXP_MCCP23017   // Use MCP23017 I2C I/O expansion part
    

     The next section is a set of #undef for the hardware that has been emulated. For now, this is the SIO which works and the PIO which is a work in progress. This is where new emulation would be added. They are specifically included (undoing then undef) if they are in the software build used.

    // These are the Z80 peripherals
    // Assume none of the supported I/O peripheral chips are used
    // They are included in the specific builds if the build software uses them
    #undef USING_PIO
    #undef USING_SIO
    

    The next section defines which software build is being used. So far, only Grant's 9-chip software is included. Only one image can be selected at a time. To add another build #undef the other builds and #define the new build.

    // Select the build here. 
    //  Only 1 build at a time is supported.
    //  All other builds are set to undef
    //#undef GRANT_9_CHIP_Z80
    #define GRANT_9_CHIP_Z80
    

     The next section is specific to the software build itself. This is the values for Grant's 9-chip design. Similar sections need to be added for new builds. This breaks down into two sections. The first is the memory map of the program and the second is the I/O space memory map.

    // defines for building Grant Searle's 9-chip Z80 design
    #ifdef GRANT_9_CHIP_Z80
        #define MONITOR_START       0x00000000      // EEPROM loads to address 0
        #define MONITOR_LENGTH      0x00004000      // 16K build
        // I/O Space Address Map follow
        #define USING_SIO
            #define SIOA_D              0x00
            #define SIOA_C              0x02
            #define SIOB_D              0x01
            #define SIOB_C              0x03
        #ifdef USING_FRONT_PANEL
            #define FR_PNL_IO_LO        0x18    // decimal 24
            #define FR_PNL_IO_LO_MID    0x19    // decimal 25
            #define FR_PNL_IO_HI_MID    0x1A    // decimal 26
            #define FR_PNL_IO_HI        0x1B    // decimal 27
        #endif
        #ifdef USING_EXP_MCCP23017
            #define PIOA_D              0x20
            #define PIOA_C              0x22
            #define PIOB_D              0x21
            #define PIOB_C              0x23
        #endif
    #endif
    

    Notice that the SIO uses a #define which undos the previous #undef. But, the Front Panel was a global value set at the start section.

    Adding in Support for Grant's 7-Chip Build

    Grant has a build with 7-chips. It uses less memory space for the program. Grant's 9-chip design has an 8K monitor and an 8K BASIC leaving 48K of RAM for programs. Grant's 7-chip design has an 8K ROM which just has BASIC (no monitor or CP/M support). 

    Let's make the changes/additions to the config file to support Grant's 7-Chip build.

    The first complication is that Grant's 7-Chip build uses a 6850 UART in place of the SIO. We don't currently have an emulator for the 6850, but let's ignore that for the moment. The 6850 UART is much simpler to emulate than the SIO chip since it only has a single control and single status register so it should be easy to emulate the part.

    For now, let's just find the address map for Grant's 7-chip design.  The...

    Read more »

  • Altair Front Panel Switches

    land-boards.com5 days ago 0 comments

    I am tempted to rename the Front Panel switches to match the Altair 8800 Front Panel.

    My current legend vs the Altair is:

    • INCAD matches EXAMINE/NEXT (down)
    • LDADR matches EXAMINE (up)
    • STINC matches DEPOSIT
    • I don't have a DEPOSIT/NEXT. This is used to fill memory with a constant
    • RUN matches RUN

    The Altair bunches keys in octal. I don't want that.

  • Z80 Peripheral Emulation (Part 4)

    land-boards.com6 days ago 0 comments

    Here's where we really need to dig deep into the data sheet. Again, I suggest um0081 for the nitty gritty on how the PIO works.

    The data says there are multiple modes for the part and that the part is largely used in interrupt driven applications. Of course this complicates the design for reasons previously mentioned. But to start with, is there a polled mode that can be implemented? Something that lets us blink a light or read a switch?

    Initialization

    One of the first things in the data sheet is what happens during power on initialization of the PIO. These registers are initialized as follows.

    These actions can be done by creating a function that gets called before the Z80 gets pulled out of reset. I didn't explicitly create an initialization function for the SIO since the BIOS (defined as the Basic Input Output or monitor) initializes the registers it needs. It is a good practice to have a function to initialize the values. In the case of the PIO, the initialization code should also initialize the MCP23017.  For instance, #2 above says that the GPIO pins should be set to high-impedance. For the MCP23017 parts this means setting them to inputs.

    Perhaps an obvious question

    Astute readers will note that the Front Panel uses the same MCP23017 parts. So we already have initialization code for the MCP23017. But there are important differences. One is that the MCP23017 is optional. Well, so is the Front Panel for that matter. The Front Panel has 8 bits as input (for the switches) and 8 bits of output (for the LEDs). This limits the drive current from the part. If all of the pins on a part were outputs then it would stress the drive current to drive 16 LEDs. So some values are "hard-coded" for the Front panels but need to be programmable for the MCP23017 that is used on the base board.

    Conditional Inclusion of Optional Hardware

    There are a lot of ways to deal with optional hardware. In the case of the external MCP23017 it's controlled by the I2C interface in the PSoC. Perhaps the PSoC could probe to see if the MCP23017 part is present? Something like an I2C port scan. That would allow the part to be installed or not installed. 

    Another way would be with #defines and conditional compilation. 

    A third way might be to create a new version of the PSoC project. Create Workspace Bundle under File is the way to do that. But that's painful since we'd end up with a lot of builds.

    For now, I'll just install a part and deal with this later.

  • Z80 Peripheral Emulation (Part 3)

    land-boards.com6 days ago 0 comments

    Probably one of the best references for the Z80 peripherals is um0081. um0081  describes the PIO starting on p 175. One of the more useful diagrams is the block diagram:

    This shows the internal structure that the PIO has. Most importantly it shows the specific registers which need to be created as data values in the low level code contained in Z80_PIO_emul (.c and .h files). since they are all 8 bits values (or less in a couple of cases) they can be represented as uint8 types. These look like:

    volatile uint8 PIO_Mask_Port_0;
    volatile uint8 PIO_Vector_Address_Port_0;       // Mode 2 interrupt vector
    volatile uint8 PIO_Interrupt_Vector_Port_0;
    volatile uint8 PIO_Output_Register_Port_0;
    
    volatile uint8 PIO_Mask_Port_1;
    volatile uint8 PIO_Vector_Address_Port_1;
    volatile uint8 PIO_Interrupt_Vector_Port_1;
    volatile uint8 PIO_Output_Register_Port_1;
    

    These values are defined as volatile since they could be changed by interrupt routines.  They get placed into the .c file.

    Digging further into the data sheet shows the bit fields of the registers and these are added to the .h file.

    #define PIO_OP_MODE_0       0x00 // Output
    #define PIO_OP_MODE_1       0x40 // Input
    #define PIO_OP_MODE_2       0x80 // Bidirectional
    #define PIO_OP_MODE_3       0xC0 // Control which bits are ins/outs
    #define PIO_OP_DIR          0x01 // 1=input, 0-output
    
    #define PIO_INT_EN_BIT      0x80 // 1=enable interrupts, 0-disable interrupts
    #define PIO_AND_OR_BIT      0x40 // 1=AND, 0=OR
    #define PIO_HIGH_LOW        0x20 // 1=monitor for high, 0=monitor for low
    #define PIO_MASK_FOLLOWS    0x10 // Define mask bits follow
    #define PIO_INT_CTL_WORD    0x07 // Signifies Interrupt Control Word
    #define PIO_MASK_BITS       0xFF // Bit mask values
    
    #define PIO_OP_MODE_MASK    0xC0 // Relevant bits
    #define PIO_OP_MODES_WORD   0x0F // Relevant bits
    

    We will probably tweek these values as we go along, but this is a good starting point.  In the previous log we created the following function stubs which should be put into the .h file as function prototypes.

    void PioReadDataA(void);
    void PioWriteDataA(void);
    void PioWriteCtrlA(void);
    void PioReadDataB(void);
    void PioWriteDataB(void);
    void PioWriteCtrlB(void);
    

    These also need to be added to the .c file and will be filled in as the functions are developed to handle each Z80 transfer. For now, just do something like this for each function:

    void PioReadDataA(void)
    {
        
    }
    

     The Z80_PIO_emul.h file should be added to the #includes at the top of the Z80_IO_Handle.c file so that it knows picks up the function prototypes.

    #include <project.h>
    #include "Z80_IO_Handle.h"
    #include "Z80_SIO_emul.h"
    #include "Z80_PIO_emul.h"
    

    The include file gets added along with the other interfaces.

    At this point the code compiles without error. Of course, we are missing the meat, but we have a solid skeleton for putting the meat onto.

    In the next part we'll start looking at the details of the lowest level functions. What should happen when the Z80 reads the Status of Port A or writes the data of Port B?

View all 61 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates