Close

6850 Emulation with a PSoC (Part 2)

A project log for 3-Chip Z80 Design

Combining a Z80 retro design with a modern PSoC CPU.

land-boards.comland-boards.com 10/20/2019 at 16:160 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:

///////////////////////////////////////////////////////////////////////////////
// uint8 checkSerialReceiverBusy(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

 We want the function for the M6850 to do the same thing so the calling function just works. But the bits are different. In the SIO the function looked at a single bit in the control register.

uint8 checkSerialReceiverBusy(void)
{
    if ((SIO_A_Ctrl2 & SIO_RTS) != SIO_RTS)
    {
        return(1);
    }
    return (SIO_A_RD0 & SIOA_CHAR_RDY);
}

In the M6850 we want to look at both bits and return the proper value. We made the $defines earlier for these values but since Grant is only using 2 let's also add a comment with that note to the #defines.

#define M6850_INT_RTS_MASK          0x60
#define M6850_RTS_LOW__INT_DIS      0x00
#define M6850_RTS_LOW__INT_EN       0x20    // Not used by Grant's 7-chip code
#define M6850_RTS_HI__INT_DIS       0x40
#define M6850_RTS_LOW__INT_DIS_BK   0x60    // Not used by Grant's 7-chip code

The RTS polarity always confuses me but it's easy enough to swap values if the result doesn't work. RTS is normally the handshake on the request to send out to this host but in this case the RTS is between the PSoC and the Z80. The function appears to a have a dual use since it also checks the receiver has a character flag so let's leave that as well. Here's what the code looks like when adapted for the M6850.

///////////////////////////////////////////////////////////////////////////////
// uint8 checkSerialReceiverBusy(void) - Check the Serial port receiver status
// Returns: 
//  0 if the port can take another character
//  1 if the port is busy and can't take another character

uint8 checkSerialReceiverBusy(void)
{
    if ((M6850_Ctrl & M6850_INT_RTS_MASK) != M6850_RTS_HI__INT_DIS)
    {
        return(1);
    }
    return (M6850_Status & SIO_CHAR_RDY);
}

The calling code uses the function as:

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

That removed the compile error for checkSerialReceiverBusy( ) but we still have an error for sendCharToZ80( ). Let's fix that next. For the SIO that looked like:

///////////////////////////////////////////////////////////////////////////////
// void sendCharToZ80(uint8 rxChar) - Send a character to the Z80 by placing it
// into the SIO_A_DataIn register and making the RxCharacterAvailable active.

void sendCharToZ80(uint8 rxChar)
{
    SIO_A_DataIn = rxChar;                          // Put the char into the buffer
    SIO_A_RD0 |= SIOA_CHAR_RDY;                     // Rx Character Available
    if ((SIO_A_WR1 & 0x18) != 0x00)                 // Only set IRQ if it is enabled from the WR1 bits
        IO_Ctrl_Reg_Write(IO_Ctrl_Reg_Read() | 0x04);   // Set IRQ* line
}

The equivalent code needs the SIO_A values changed and bits adjusted. We can also look at the control register bits to see if interrupts are enabled and take the proper action. Here's the appropriate changes for the M6850.

///////////////////////////////////////////////////////////////////////////////
// void sendCharToZ80(uint8 rxChar) - Send a character to the Z80 by placing it
// into the SIO_A_DataIn register and making the RxCharacterAvailable active.

void sendCharToZ80(uint8 rxChar)
{
    M6850_DataIn = rxChar;                                          // Put the char into the buffer
    M6850_Status |= SIO_CHAR_RDY;                                   // Rx Character Available
    if ((M6850_Ctrl & M6850_INT_RTS_MASK) != M6850_RTS_LOW__INT_EN) // Only set IRQ if it is enabled from the WR1 bits
        IO_Ctrl_Reg_Write(IO_Ctrl_Reg_Read() | 0x04);               // Set IRQ* line
}

We still get compile errors but they all relate to interrupts. The next log will take a look at interrupt handling for the M6850.

Discussions