I`ve recently spent some time trying to find a way to improve my VGA display board in order to make it capable of displaying 80 column text for future CP/M implementation. It would theoretically be possible, but modifications would include at least a new firmware for Atmega328 and XC9536XL, and also a new, smaller font – something like 5×7. It might also be needed to swap the AVR chip for something larger, like Atmega644.Generally, it would be quite a big challenge. As soon as I started making some brief calculations for this, an interesting idea came into my head: wy not try something completely different? Long ago, I remember doing some research about a standard PC ISA VGA cards, and throwing away that idea very quickly, as I realized, that VGA need to be initialized via it`s BIOS routines, in 80×86 code of course. I heard , that VGA can sometimes even call some routines in PC BIOS, so it would be really a dreadful job to make it do just anything barely useful in Z80 enviroment. However, as a vintage PC collector, I own some MDA/Hercules clone cards and monitors.I was aware that MDA/Hercules are quite simple designs, and after some quick research on MDA card and ISA bus I was pretty shure I`m able to make a standard MDA clone work for my C-Z80 computer. It took just a few hours from nothing to displaying lines of text on a monochrome monitor connected to MDA/Hercules clone!

Two_monitors

 

Here is the card connected to the system:

The card itself is driven only by 8255 chip from my 8255/IDE interface card. Absolutely nothing more is required to make it work (except for some cables), also you don`t nedd to worry about synchronization of video memory access with actual video signals – the card does all this by itself (no snowing or jittery image). Here is a complete schematic of connections I made:

mda_connections

As you can see, the card needs 24 I/O lines and positive RESET signal, just exactly what 8255 offers. In fact, you can even let go 2 lines more – /IOR and /MEMR if you`re not planning to do any read operations from the card`s memory or I/O space. The video function of the card does not use any IRQs or DMAs, no external clock sources are needed,no data multiplexing or anything. Basically, the very minimum you need to do with the card to make it display text is:

  •  Setting the video memory or I/O address lines (12 lines in total for MDA, 15 for Hercules in full mode)
  •  Setting the data lines (values needed to be entered to I/O registers and pure video buffer data)
  • Generating negative pulses on /IOW and /MEMW to feed the card with the data.

For full functionality, /MEMR and /IOR have to be implemented for read operations, and also IO CH RDY signal have to be connected to Z80`s /WAIT line in order to implement a full synchronization for I/O and memory operations.

Below I attach a sample test code. It uses 8255 chip at address 88d0h for communicating with MDA card. I only use MDA functionality here, as it is the easiest option, and require less address lines (only 12 for 4K video buffer). The code initializes the card according to original IBM`s hardware reference manual for “Monochrome Display and Printer Adapter”. After this, it clears the screen, displays some text, waits for a moment and repeats the process.

;Some basic delay routines
DELAY_LONG:     .equ 0597h
DELAY:            .equ 0589h

; IO addresses and pin connections used:
; 
; 0ffd0h - 8255 base address in C-Z80 system
; 0ffd3h - 8255 control register
; 0ffd0h - PORT A (connected to lower address lines of MDA card A0 - A7)
; 0ffd1h - PORT B (connected to data lines of MDA card)
; 0ffd2h - PORT C (connected to upper addres lines of MDA card , and to control lines:
;                  C0=A8 PC1=A9, PC2=A10, PC3=A11, PC4=/MEMR PC5=/MEMW PC6=/IOR PC7=/IOW)
;The MDA card is also connected to +5V, GND and also positive RESET source.
;Card`s IRQ7, I/O CH RDY are not used here - leave unconnected
;Some signals are terminated:
;
;A12,A13,A14,A15,A18,AEN = GND
;A16,A17,A19 = +5V
;

; *** MAIN LOOP ***
    .org 0d000h
    
    start:
        
        call init_8255
        call port_setup
        call registers_init

    repeat:    
        
        call clear_screen
        
        ld ix,test_string1
        ld hl,0
        call line_write
        
        call DELAY_LONG
        call DELAY_LONG
        call DELAY_LONG
        call DELAY_LONG
        
        jp repeat

    ; *** SUBROUTINES ***    

    init_8255:
    ; Basic inititalisation of 8255 chip:
    ; All ports as output, mode 0, 
    ; port C = f0h (to set all control signals of MDA card to logic "1")

        ld bc,0ffd3h
        ld a,80h
        out (c),a
        ld bc,0ffd2h
        ld a,0f0h
        out (c),a
        ret

    port_setup:
    ; Seting up control port of 6845 CRT controller on MDA card (address 3b8h)
    ; According to "IBM Monochrome Display and Prnter Adapter" hardware reference manual
    ; this must be done before anything else.
    ; value "29h" means: 
    ; High resolution mode: ON, Video Enable: ON, Enable Blink: ON
     
        ld bc,0ffd2h
        ld a,0f3h
        out (c),a
        
        ld bc,0ffd0h
        ld a,0b8h
        out (c),a
        
        ld bc,0ffd1h
        ld a,29h
        out (c),a    
        call io_write
        ret


    registers_init:
    ; Bulk initialization of 6845 CRT controller`s registers
    ; 16 registers has to be initialised with fixed values
    ; recommended by IBM`s hadware manual for MDA card.
    ; Registers are selected by "Index Register" (3b4h)
    ; Values are entered to "Data Register" (3b5h)

        ld ix,init_data
        ld d,10h
        
    loop_regs:
        dec d
        ld a,(ix)
        ld e,a
        call reg_write
        inc ix
        ld a,0
        cp d
        jr nz,loop_regs
        ret


    reg_write:
    ; writing value to 6845 register.
    ;Entry:
    ;
    ; D - register number
    ; E - value
        
        ld bc,0ffd2h 
        ld a,0f3h
        out (c),a
        ld bc,0ffd0h 
        ld a,0b4h
        out (c),a
        ld bc,0ffd1h 
        ld a,d
        out (c),a    
        call io_write 
        ld bc,0ffd0h
        ld a,0b5h 
        out (c),a
        ld bc,0ffd1h; 
        ld a,e 
        out (c),a
        call io_write;
        ret


    io_write:
    ; generate short negative pulse on /IOW line of MDA card
        ld bc,0ffd3h
        ld a,0eh
        out (c),a
        call DELAY ;just in case I/O operations would take too long.
        ld a,0fh
        out (c),a
        ret

    mem_write:
    ; generate short negative pulse on /MEMW line of MDA card
        ld bc,0ffd3h
        ld a,0ah
        out (c),a
        ld a,0bh
        out (c),a
        ret


    line_write:
    ;Write ASCIIZ buffer to MDA card`s video buffer. Assuming "normal" attribute.
    ;Entry:
    ;
    ; HL = Video RAM position - even address, counted from 0
    ; IX = Start of ASCIIZ buffer of data
        ld bc,0ffd0h
        ld a,l
        out (c),a
        ld a,0f0h
        or h
        ld bc,0ffd2h
        out (c),a
        ld b,0
        ld a,(ix)
        cp b
        ret z
        ld bc,0ffd1h
        out (c),a
        call mem_write 
        inc hl
        ld bc,0ffd0h
        ld a,l
        out (c),a
        ld a,0f0h
        or h
        ld bc,0ffd2h
        out (c),a
        ld bc,0ffd1h
        ld a,07h    ;normal attribute
        out (c),a
        call mem_write;

        ; delay - just for nice looking printout on screen
        call DELAY
        call DELAY
        call DELAY
        call DELAY
        call DELAY
        call DELAY
        inc hl
        inc ix
        jp line_write

    clear_screen:
    ;Fills the 4K video RAM with zeros

        ld hl,0f9fh
        
    continue_clear_screen:
        ld bc,0ffd0h
        ld a,l
        out (c),a
        ld a,0f0h
        or h
        ld bc,0ffd2h
        out (c),a
        ld bc,0ffd1h
        ld a,0             ;attribute
        out (c),a
        call mem_write    
        dec hl
        ld bc,0ffd0h
        ld a,l
        out (c),a
        ld a,0f0h
        or h
        ld bc,0ffd2h
        out (c),a
        ld bc,0ffd1h
        ld a,0            ;empty char
        out (c),a
        call mem_write;    
        ld a,h
        or l 
        ret z
        jp continue_clear_screen;

    ;Fixed 6845 register values - taken from 
    ;"IBM Monochrome Display and Printer Adapter" 
    ;Hardware reference manual.
    init_data:
    .db 0,0,0,0,0ch,0bh,0dh,02h,19h,19h,06h,19h,0fh,52h,50h,61h    

    ;Example text to be displayed
    test_string1:
    .db "Hello! This is MDA display adapter interfacing Z80 CPU! "
    .db "This is a test program showing how it is possible to use "
    .db "a standard IBM MDA display adapter with Z80 CPU system. "
    .db "This is actually a very simple job, as MDA adapters (and also CGA) " 
    .db "have no BIOSes of their own - there is no need for emulating 80x86 code "
    .db "In order to set up the card`s chipset before regular use. Using MDA "
    .db "adapter is as simple as issuing few simple read/write operations to memory "
    .db "and I/O locations. It would be best to place the card`s interface directly "
    .db "into memory and I/O space of the Z80 CPU system - this would be rather easy, "
    .db "as there are just few I/O addressed in use , and 4KB of video RAM. However, "
    .db "for testing the MDA in my C-Z80 system, I used the most simple solution - "
    .db "8255 Programmable Peripheral Interface chip. "
    .db "This chip gives a total of 24 I/O lines, which is just enough to interface "
    .db "MDA card: we need 12 address lines, 8 bit data lines and 4 control signals. "
    .db "8255 can privide all of these. In addition we need a positive RESET signal - "
    .db "taken from 8255 itself, and of course +5V and GND lines. "
    .db "We assume that there is enough time for I/O and memory operations for MDA card "
    .db "So there is no checking of I/O CH RDY line for this simple test setup. "
    .db "It seems the display adapter has it`s own dual port access to video memory "
    .db "as there is no need for any sync while writing into the video buffer - "
    .db "there is no flickering or snowing while accessing the VRAM. " 
    .db "Of course this greatly simplifies the interface design. "
    .db "The video troughput is acceptable, even with rather complex drive trough "
    .db "8255, not directly from CPU. Making a real interface with latches and "
    .db "buffers will icrease the speed even more. I`ll be working on this "
    .db "solution in near future. When the proper interface is ready, the display "
    .db "subsystem of C-Z80 computer will be ready for CP/M implementation."
    .db 0

    .END

Here I attach some charts from logic analyzer:

There are /IOW/ /MEMW and IO CH RDY signals shown. On the first chart we can see a problem with 8255 itself – it seems, that transition between it`s operational modes causes some sort of noise on the /IOW and /MEMW lines, and therefore IO CH RDY line goes crazy. Before they finally settle down, some random data may be entered into card`s I/O and memory space. This sometimes leads to problems with initialisation – the card just display garbage on the screen. This can be fixed with more sophisticated circuit, or just by interfacing the card directly to Z80 bus. After the problematic first few microseconds, we can see the card`s initialisation process : one pulse for setting the 6845 control port, and 16 pairs of pulses for setting the required 16 registers of 6845. After this, a series of /MEMW pulses appear – the video buffer is being filled with data. You can see, that I/O pulses do not produce any wait states from the card (IO CH RDY stays high), so we are safe here. However, video memory access generates wait states from about 0.5 us to over 1.6 us (shown on later charts). This is not a problem for this circuit – with 8255 we can only make pulses as short as 4,6us, so we are also safe here – the card will easily deal with this data rate from the system itself. However, if we plan to interface the card in Z80 I/O and / or memory space, we need to take the IO CH RDY signal into account – a single /IOREQ pulse from Z80 @ 4Mhz is about 0,58us, so the charts clearly show, that a /WAIT signal has to be used when interfacing the card to Z80 system.
AS you can see, making an MDA/Hercules card to work for any 8-bit CPU is really easy. I can recommend some resources on the web that helped me during this experiment:

Very brief, and very helpful description of 8-bit ISA bus interfacing. Nothing more is really needed to start your own experiments with ISA bus:

http://ece.wpi.edu/~wrm/Courses/EE3803/Labs/isa/

Scanned PDF of IBM`s original Hardware Reference Manual for Monochrome Display and Printer adapter. Good manual, schematics included:

http://www.minuszerodegrees.net/oa/OA%20-%20IBM%20Monochrome%20Display%20and%20Printer%20Adapter.pdf

John Elliott`s page on MDA and Hercules hardware and programming tips. This document covers pretty much everything said in the previous one from IBM, but also explains many things in greater detail, and what`s more important: gives information about Hercules mode of monochrome clone cards, so it can be useful if someone wants to get more from the card than just plain MDA:

http://www.seasip.info/VintagePC/mda.html

I`m really happy that this setup actually works. MDA/Hercules display gives many new possibilities to an 8-bit system. However there are of course disadvantages: MDA/Hercules cards are not easy to come by, and can get quite expensive. For monitors – it`s even worse. There is some hope though: with simple passive adapter, a multiscan VGA monitor can be used , though they are also rare (NEC Multisync 3D for example). There can be also found some specialized LCD monitors and signal converters used as a replacement solutions for MDA CRTs in industrial machines. Last option is a good MDA/CGA clone card like ATI Small Wonder – it can output an MdA/Hercules display to a composite output.

In near future, I`m planning to interface the card directly to Z80 bus, and make a proper adapter for it.