MEMIO emulator for Z80

Thread Starter

nsaspook

Joined Aug 27, 2009
13,274
When I resurrected my old Z80 board (just the processor module) I needed to add memory and IO to make it useful. The old processor only runs at 2.6MHz so I decided to use a PIC18f46k22 that runs at 64MHz to act as the memory/IO system for the board to drive an SPI character display. The uC will emulate (at a fraction of the original hardware speed) the program ROM, data RAM and 8 bit data ports the Z80 will use to program and send data on the display. All Z80 programming will be in SDCC with the binary program file converted to a C array for the PIC to serve from flash. The Z80 ram memory will be supplied by the PICs internal SRAM in another C array.

The first step was to add the PIC, a few glue chips (mainly to gate out refresh commands from the PIC interrupts) to the original board then wire it and several debug/io pins to the Z80 circuit.



Write the hardware emulator program for the PIC. (in progress)
https://github.com/nsaspook/z80memio/tree/rom

Write some test code in C for the Z80 using the SDCC compiler with no C runtime starting at absolute code address 0x0000, ram at address 0x0400 using IO port 0xff.
C:
__sfr __at 0xff IoPort;
volatile __at (0x0400) unsigned char chk;

void main(void)
{
unsigned char a=0;

while (1) {
  chk=a++;
  IoPort=chk;
  };
}
SDCC Assembly code output.


Then take the IHX file from SDCC, convert that to binary and then convert that to the needed C array containing the hex codes of the program using a few simple programs and scripts from various sources.

The end result is the Z80 running the program stored on the PIC that's a simple up count variable 'a', stored and retrieved in the PICs memory then sent back to the PIC for transmission on the SPI port for capture on the o-scope.

The emulator is set to auto-step only a few instructions per second using memory WAIT states.
SPI data and clocks for IO address and IO value.

Next step, finish the emulator code and write the real demo program.
 
Last edited:

MMcLaren

Joined Feb 14, 2010
861
Way to go, nsa'. I'm planning a Z80 + PIC project, too. I hope to use the PIC for 'Reset', 'Clock', 'Loader', and 'I/O' (Serial and SPI). I'll use the following initial circuit to single step the Z80 while monitoring and characterizing all of the Z80 control signals.

Good luck on your project.

Cheerful regards, Mike

Z80 0b (small).png
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
13,274
It's working OK but having separate cpu clocks on my board makes the timing a little critical in places with WAIT (channel 0) clocking with RFSH (channel 6) and MREQ (channel 3). It needs a little more time on the bus analyzer at work to kill the little glitches.

Full speed operation.

 

Thread Starter

nsaspook

Joined Aug 27, 2009
13,274
Speed (slow) benchmarks for the test code loop.
Code:
  47 ;/sdd/PIC/z80/XideSDCC/untitled0.c:4: void main(void)
  48 ;   ---------------------------------
  49 ; Function main
  50 ; ---------------------------------
  0000  51 _main_start::
  0000  52 _main:
  53 ;/sdd/PIC/z80/XideSDCC/untitled0.c:8: while (1) {
  0000 16 00  [ 7]  54    ld   d,#0x00
  0002  55 00102$:
  56 ;/sdd/PIC/z80/XideSDCC/untitled0.c:9: chk=a++;
  0002 21 00 04  [10]  57    ld   hl,#_chk + 0
  0005 72  [ 7]  58    ld   (hl), d
  0006 14  [ 4]  59    inc   d
  60 ;/sdd/PIC/z80/XideSDCC/untitled0.c:10: IoPort=chk;
  0007 3A 00 04  [13]  61    ld   a,(#_chk + 0)
  000A D3 FF  [11]  62    out   (_IoPort),a
  000C 18 F4  [12]  63    jr   00102$
  000E  64 _main_end::
  65    .area _CODE
  66    .area _INITIALIZER
  67    .area _CABS (ABS)
IORQ spi output 000A: Total program loop execution time (top trace)
The bottom trace is from RAM access.



MREQ ram detail (bottom trace): Write 0002 to ram read 0007 command execution time.


MREQ (top trace): memory cycle access time (bottom trace is 0002 ram access)



Normal timing: op-code fetch

memory access:
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
13,274
No!
Better!

C:
//
// Z80 to PIC18f46k22 MEMIO demo program
//
// setup the IO ports to send data
__sfr __at 0x80 IoPort_data; // send display data text
__sfr __at 0x81 IoPort_cmd; // send commands to program the display
__sfr __at 0x01 IoPort_bit; // set or clear bit RC6 on the PIC

// MEMIO SRAM memory space
volatile __at(0x0400) unsigned char ram_space;

// start the program
static const char __at 0x00b0 z80_text0[] = " Shall we play a game? ";
static const char __at 0x00d0 z80_text[] = " All work and no play makes Jack a dull boy ";

// display functions
void config_display(void);
void putc(unsigned char);

void main(void)
{
  unsigned char c, i = 0, *ram_ptr = &ram_space;
  unsigned int a;

  // setup ram stack pointer
  __asm;
  ld sp,#0x04ff;
  __endasm;

  config_display();

  // send the text string to the data port
  while (1) {
  for (i = 0; i < 80; i++) {
  c = z80_text[i];
  if (!c) break;
  putc(c);
  a = 0;
  while (a++ < 1024);
  }
  };
}

// turn off the cursor
void config_display(void)
{
  IoPort_cmd = 0b00001100;
}

// send one character
void putc(unsigned char c)
{
  IoPort_data = c;
}
Z80 C program ASM listing for the SPI display. It's slowed down by a factor of 1024 in a delay loop.

The SPI (4MHz clock) display used is the EA DOGM163 with backlight module.
http://www.lcd-module.com/eng/pdf/doma/dog-me.pdf
Controller: http://www.lcd-module.de/eng/pdf/zubehoer/st7036.pdf

PIC response from Z80 IORQ_.
The debug IO address 0x80 (without display CS at FOSC/4) then the display character data with display CS at FOSC/16.


The SPI wiring (and the scope probe ground) parasitic inductance makes a nice negative voltage ring when the driver goes to low. Not too serious at these slow speeds but a series damping resistor (~30-50 ohm) from the PIC pin on each will make it better.
http://www.ti.com/lit/an/scba012a/scba012a.pdf
http://www.icd.com.au/articles/Terminations_PCBDesign-Oct2013.pdf
 

Attachments

Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
13,274
Way to go, nsa'. I'm planning a Z80 + PIC project, too. I hope to use the PIC for 'Reset', 'Clock', 'Loader', and 'I/O' (Serial and SPI). I'll use the following initial circuit to single step the Z80 while monitoring and characterizing all of the Z80 control signals.

Good luck on your project.

Cheerful regards, Mike
Good luck to you also. FYI the 46k22 has a few problems. (with workarounds) The serial on A2 parts is critical to know about.
http://ww1.microchip.com/downloads/en/DeviceDoc/80000514G.pdf
 

MMcLaren

Joined Feb 14, 2010
861
Thanks for the "heads up". I've been using the '46K22 or the '26K22 on other retro' projects, too.

Cheerful regards, Mike

Pocket 1802 Rev 3 (small).png Pocket 1802 Rev 3a (small).png Pocket 65C02 SBC20 (small).pngSBC v1 BASIC.png
 
Last edited:

Thread Starter

nsaspook

Joined Aug 27, 2009
13,274
Added a little I to the display I/O board with a 25K22 on the SPI bus. 6 digital inputs with 2 CTMU touch inputs for the Z80 to use. The last major thing left is to use the buffered DAC output to drive an analog meter. :D
https://github.com/nsaspook/z80memio/tree/rom/aux_spi

The obsolete load-cell system case I plan to mount it in.

C:
//
// Z80 to PIC18f46k22 MEMIO demo program
//
// setup the IO ports to send data
__sfr __at 0x80 IoPort_data; // send display data text
__sfr __at 0x81 IoPort_cmd; // send commands to program the display
__sfr __at 0x01 IoPort_bit; // set or clear bit RC6 on the PIC
__sfr __at 0x10 IoPort_rngl; // random number generator bits, low byte
__sfr __at 0x11 IoPort_rngh; // random number generator bits, high byte
__sfr __at 0x00 IoPort_dummy; // SPI dummy read

// MEMIO SRAM memory space
volatile __at(0x0400) unsigned char ram_space;

// start the program
static const char __at 0x00b0 z80_text0[] = " Shall we play a game? ";
static const char __at 0x00d0 z80_text[] = " All work and no play makes Jack a dull boy ";

// display functions
void config_display(void);
void putc(unsigned char);

volatile static unsigned char __at(0x0400) b;
volatile static unsigned char __at(0x0401) d;
char z;

void main(void)
{
    unsigned char c, i = 0, *ram_ptr = &ram_space;
    unsigned int a;

    // setup ram stack pointer
    __asm;
    ld sp,#0x04ff;
        __endasm;

    config_display();

    // send the text string to the data port
    while (1) {
        b = IoPort_rngl;
        for (i = 0; i < 80; i++) {
            if (b < 128) {
                c = z80_text[i];
            } else {
                c = z80_text0[i];
            }
            if (!c) break;
            putc(c);
            a = 2000;
            if (IoPort_data < 200) a = 0;
            while (a) a--;
        }
    };
}

// turn off the cursor

void config_display(void)
{
    IoPort_cmd = 0b00001100;
}

// send one character

void putc(unsigned char c)
{
    IoPort_data = c;
}
 
Last edited:
Top