MEMIO emulator for Z80

Discussion in 'Embedded Systems and Microcontrollers' started by nsaspook, Oct 29, 2015.

  1. nsaspook

    Thread Starter AAC Fanatic!

    Aug 27, 2009
    2,913
    2,181
    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.

    [​IMG]

    Write the hardware emulator program for the PIC. (in progress)
    https://github.com/nsaspook/z80memio/tree/rom
    [​IMG]
    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.
    Code (C):
    1.  
    2. __sfr __at 0xff IoPort;
    3. volatile __at (0x0400) unsigned char chk;
    4.  
    5. void main(void)
    6. {
    7. unsigned char a=0;
    8.  
    9. while (1) {
    10.   chk=a++;
    11.   IoPort=chk;
    12.   };
    13. }
    14.  
    SDCC Assembly code output.
    [​IMG]

    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, finished the emulator code and write the real demo program.
     
  2. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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: Oct 29, 2015
  3. nsaspook

    Thread Starter AAC Fanatic!

    Aug 27, 2009
    2,913
    2,181
    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.
    [​IMG]
    [​IMG]
     
  4. nsaspook

    Thread Starter AAC Fanatic!

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

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

    MREQ (top trace): memory cycle access time (bottom trace is 0002 ram access)
    [​IMG]


    Normal timing: op-code fetch
    [​IMG]
    memory access:
    [​IMG]
     
    Last edited: Oct 30, 2015
  5. nsaspook

    Thread Starter AAC Fanatic!

    Aug 27, 2009
    2,913
    2,181

    No!

    Better!

    Code (C):
    1.  
    2.  
    3. //
    4. // Z80 to PIC18f46k22 MEMIO demo program
    5. //
    6. // setup the IO ports to send data
    7. __sfr __at 0x80 IoPort_data; // send display data text
    8. __sfr __at 0x81 IoPort_cmd; // send commands to program the display
    9. __sfr __at 0x01 IoPort_bit; // set or clear bit RC6 on the PIC
    10.  
    11. // MEMIO SRAM memory space
    12. volatile __at(0x0400) unsigned char ram_space;
    13.  
    14. // start the program
    15. static const char __at 0x00b0 z80_text0[] = " Shall we play a game? ";
    16. static const char __at 0x00d0 z80_text[] = " All work and no play makes Jack a dull boy ";
    17.  
    18. // display functions
    19. void config_display(void);
    20. void putc(unsigned char);
    21.  
    22. void main(void)
    23. {
    24.   unsigned char c, i = 0, *ram_ptr = &ram_space;
    25.   unsigned int a;
    26.  
    27.   // setup ram stack pointer
    28.   __asm;
    29.   ld sp,#0x04ff;
    30.   __endasm;
    31.  
    32.   config_display();
    33.  
    34.   // send the text string to the data port
    35.   while (1) {
    36.   for (i = 0; i < 80; i++) {
    37.   c = z80_text[i];
    38.   if (!c) break;
    39.   putc(c);
    40.   a = 0;
    41.   while (a++ < 1024);
    42.   }
    43.   };
    44. }
    45.  
    46. // turn off the cursor
    47. void config_display(void)
    48. {
    49.   IoPort_cmd = 0b00001100;
    50. }
    51.  
    52. // send one character
    53. void putc(unsigned char c)
    54. {
    55.   IoPort_data = c;
    56. }
    57.  
    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.
    [​IMG]

    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
    [​IMG]
     
    Last edited: Oct 31, 2015
  6. nsaspook

    Thread Starter AAC Fanatic!

    Aug 27, 2009
    2,913
    2,181
    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
     
  7. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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).png SBC v1 BASIC.png
     
    Last edited: Oct 31, 2015
    nsaspook likes this.
  8. nsaspook

    Thread Starter AAC Fanatic!

    Aug 27, 2009
    2,913
    2,181
    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
    [​IMG]
    The obsolete load-cell system case I plan to mount it in.


    Code (C):
    1.  
    2. //
    3. // Z80 to PIC18f46k22 MEMIO demo program
    4. //
    5. // setup the IO ports to send data
    6. __sfr __at 0x80 IoPort_data; // send display data text
    7. __sfr __at 0x81 IoPort_cmd; // send commands to program the display
    8. __sfr __at 0x01 IoPort_bit; // set or clear bit RC6 on the PIC
    9. __sfr __at 0x10 IoPort_rngl; // random number generator bits, low byte
    10. __sfr __at 0x11 IoPort_rngh; // random number generator bits, high byte
    11. __sfr __at 0x00 IoPort_dummy; // SPI dummy read
    12.  
    13. // MEMIO SRAM memory space
    14. volatile __at(0x0400) unsigned char ram_space;
    15.  
    16. // start the program
    17. static const char __at 0x00b0 z80_text0[] = " Shall we play a game? ";
    18. static const char __at 0x00d0 z80_text[] = " All work and no play makes Jack a dull boy ";
    19.  
    20. // display functions
    21. void config_display(void);
    22. void putc(unsigned char);
    23.  
    24. volatile static unsigned char __at(0x0400) b;
    25. volatile static unsigned char __at(0x0401) d;
    26. char z;
    27.  
    28. void main(void)
    29. {
    30.     unsigned char c, i = 0, *ram_ptr = &ram_space;
    31.     unsigned int a;
    32.  
    33.     // setup ram stack pointer
    34.     __asm;
    35.     ld sp,#0x04ff;
    36.         __endasm;
    37.  
    38.     config_display();
    39.  
    40.     // send the text string to the data port
    41.     while (1) {
    42.         b = IoPort_rngl;
    43.         for (i = 0; i < 80; i++) {
    44.             if (b < 128) {
    45.                 c = z80_text[i];
    46.             } else {
    47.                 c = z80_text0[i];
    48.             }
    49.             if (!c) break;
    50.             putc(c);
    51.             a = 2000;
    52.             if (IoPort_data < 200) a = 0;
    53.             while (a) a--;
    54.         }
    55.     };
    56. }
    57.  
    58. // turn off the cursor
    59.  
    60. void config_display(void)
    61. {
    62.     IoPort_cmd = 0b00001100;
    63. }
    64.  
    65. // send one character
    66.  
    67. void putc(unsigned char c)
    68. {
    69.     IoPort_data = c;
    70. }
    71.  
    72.  
     
    Last edited: Nov 5, 2015
Loading...