Source Code from Cassette Tape

How to recover a Nascom Z80 assembly language source code listing from audio cassette tape

Written by Guy Fernando

Last modified November 2020


Introduction

Back in early 1980s I was very fortunate to own a Nascom-2 computer, with a whopping 8KB of static memory and Microsoft BASIC built in. These Z80A based computers sold as kits by Lucas Logic Ltd were popular with computer hobbyists.

All my Nascom programs at the time were stored on audio cassette tape. Back then a floppy disk interface including the extra RAM needed to run the CP/M operating system would have been far beyond my means.

Nascom tape overlay

In 1982 I was approached by a heating and ventilation company to design the electronics for a solid-state switcher unit to replace their mechanical rotating cam switch that were notoriously subject to wear and reliability problems. My design solution required the use of an EPROM to hold the cam switching sequence. So I set about finding an EPROM programmer.

At the time the cost of an EPROM programmer was around £2,500 GBP in today's money, this amount being far too costly for an impoverished teenager. I had no choice other than to design hardware and software for a EPROM programmer that could be interfaced and controlled by my Nascom computer. To cut a long story short, the EPROM programmer I designed worked and it led to many solid-state switches being manufactured for this heating and ventilation company (which still appears to be in business today).

Recently I stumbled across a long lost box containing the original hardware, audio cassette tape that was used to save the EPROM programmer software, and some accompanying pencil written documentation.

EPROM Programmer Board

Original Hardware

The board was built on veroboard, as it was the easiest and quickest way to prototype hardware back then. The 26-way molex connector at the top connects to the Nascom I/O port via a ribbon cable, feeding +5V and the Z80 PIO ports. An additional +30V is also required because early EPROMs required a high program voltage, this voltage can be adjusted using the multiturn trimpot. The white DIP switch in the top left is positioned depending on whether a 2716 (1KB) or 2732 (2KB) EPROM is being programmed.

Original Paper Schematic Diagram

The EPROM programmer schematic diagram drawn with paper, pencil and eraser. The Nascom PIO PORT-A is used to send data, for latching the address and data lines presented to the EPROM. The PIO PORT-B lines are used as control and status bits.

EPROM Programmer Schematic

EPROM Programmer Listing

Original Paper Assembly Language Listing

The EPROM programmer assembly language software listing was hand written, truly defining the meaning of a “hardcopy”. Although the BASIC language resident to the Nascom, I clearly opted implementing the EPROM programmer in Z80 machine code. The assembly language instructions were written on paper as shown, and finally hand assembled into Z80 machine code.

Having the original pencil and paper assembly language source, albeit a little messy and unclear in places, I really wanted to recover the exact source listing from the cassette tape to see how it compared.

Original Audio Cassette Tape

The C15 cassette recording of the EPROM programmer sounds like this:



I set about transferring the audio recorded on the cassette tape file over to a WAV file using Audacity. The audio was converted to mono, 8-bit resolution, and a 22050 kHz sampling rate WAV file, and saved as Eprom-Burner.wav.

The Nascom cassette interface uses the Kansas City standard. The DIP switches on my Nascom were set to allow the cassette interface to work at 1200 baud, 8 bits, no parity, and 1 stop-bit.

Audio Cassette Tape

Recovering the Assembly Language Listing

I used the Perl Kansas City Tape Decoder script to convert the WAV audio file to a binary file. If you’re running Windows and don’t have a Perl interpreter installed on your machine, install Strawberry Perl as it works well.

The following command will create the binary file Eprom-Burner.bin from the WAV audio file Eprom-Burner.wav. Different baud rates and frame parameters can be specified to allow for recorded audio with differing cassette interface settings.

 perl tape-read baud=1200 frame=8N1 Eprom-Burner.wav

The Nascom cassette interface prepends a 9 byte header, a check block of length 21 bytes every 256 bytes, and appends 11 byte footer to the binary file for error checking purposes. A binary editor must be used to strip these blocks otherwise the disassembler will generate garbage.

After the superfluous bytes have been stripped, the Z80 assembly source code can be reconstructed by disassembling the Eprom-Burner.bin. I used yazd the command line disassembler for Z80 machine code files.

The following command will create an assembly language file Eprom-Burner.lst from the binary file Eprom-Burner.bin. As the EPROM programmer was written to be executed with an entry address of 4000h (i.e. not relocatable), the yazd command switches --addr and --entry must be specified as 0x4000.

 yazd --addr:0x4000 --entry:0x4000 -list Eprom-Burner.bin Eprom-Burner.lst
            
              
 ; EPROM PROGRAMMER FOR NASCOM II Z80 MICROCOMPUTER
 ; Guy Fernando (1982)
 ; The code has been recently disassmbled as shown here, but the
 ; instructions untouched since originally written back in 1982. 
 ;
 ; Display to Screen Program Title and Version Caption
 ;
        RST     0x28
        DB      0Ch
        DB      "Eprom Program Package"
        DB      0Dh
        DB      "I4CY 1982 Version 1.0"
        NOP
 ;
 ; Display to Screen Read and Write Commands
 ;
   NEW: RST     0x28
        DB      0Dh
        DB      0Dh
        DB      "Enter R to Read from eprom"
        DB      0Dh
        DB      "      W to Write to eprom"
        DB      0Dh
        DB      0Dh
        NOP
 ;
 ; Response from Keyboard to Determine Read or Write Choice
 ;
   OPT: RST     0x08
        CP      52h     ; 'R'
        JP      Z,RD
        CP      57h     ; 'W'
        JP      Z,WR
        LD      B,C
        JP      OPT
 ;
 ; Read From EPROM Mode Routine
 ;
    RD: RST     0x28
        DB      "In Read Mode"
        DB      0Dh
        DB      0Dh
        DB      "Enter Starting Address"
        DB      0Dh
        NOP
        CALL    ALPNU
        LD      (TAB+0),DE
        RST     0x28
        DB      0Dh
        DB      0Dh
        DB      "Enter Number of Bytes to be Read"
        DB      0Dh
        DB      "(ie.2716=0800 & 2732=1000)"
        DB      0Dh
        NOP
        CALL    ALPNU
        LD      HL,0000h
        LD      BC,CF07h
        OUT     (C),B      ; port B control to bit mode
        LD      B,40h
        OUT     (C),B      ; B0:B5=output, B6=input
 NEXTR: LD      BC,0F06h
        OUT     (C),B      ; port A control to output mode
        LD      C,04h      ; port A data
        OUT     (C),L      ; output L on port A
        LD      BC,0805h   ; port B data
        DEC     B
        EX      AF,AF
        OUT     (C),B      ; 00001000, data direction write
        LD      B,09h
        OUT     (C),B      ; 00001001, enable data bus
        LD      B,C
        LD      B,0Bh
        OUT     (C),B      ; 00001011, low address strobe
        LD      B,08h
        OUT     (C),B      ; 00001000, disable data bus
        LD      C,04h      ; port A data
        OUT     (C),H      ; output H reg on port A
        LD      H,C
        LD      BC,0905h   ; port B data
        OUT     (C),B      ; 00001001, enable data bus
        LD      B,0Dh
        OUT     (C),B      ; 00001101, high address strobe
        LD      B,08h
        OUT     (C),B      ; 00001000, disable data bus
        LD      BC,0004h   ; port A data
        OUT     (C),B      ; output 00h on port A
        LD      A,0FFh
        RST     0x38       ; ?
        OUT     (06h),A    ; port A control to bit mode
        OUT     (06h),A    ; A0:A7=input
        LD      BC,0005h   ; port B data
        OUT     (C),B      ; 00000000, data direction read
        LD      B,01h
        OUT     (C),B      ; 00000001, enable data bus
        IN      A,(04h)    ; read data on port A into A reg
        INC     B
        PUSH    BC
        PUSH    HL
        PUSH    AF
        PUSH    DE
        LD      A,E
        OR      D
        JP      Z,ENDP     ; quit when finished
        POP     DE
        DEC     DE
        LD      BC,(TAB+0)
        ADD     HL,BC
        POP     AF
        LD      (HL),A
        POP     HL
        POP     BC
        LD      B,00h
        OUT     (C),B      ; 00000000, disable data bus 
        INC     HL
        JP      NEXTR      ; next byte to read
 ;
 ; Table Variable Space
 ;
   TAB: DB      00h        ; First Memory Address
        DB      48h
        DB      00h        ; Last Memory Address
        DB      50h
        DB      00h        ; Number of Bytes
        DB      08h
        DB      00h        ; Starting Address of EPROM
        DB      00h
 ;
 ; Write to EPROM Mode Routine
 ;
    WR: RST     0x28
        DB      "In Write Mode?"
        DB      0Dh
        DB      0Dh
        DB      "Enter First Memory Address"
        DB      0Dh
        NOP
        CALL    ALPNU
        LD      (TAB+0),DE
        RST     0x28
        DB      0Dh
        DB      0Dh
        DB      "Enter Last Memory Address"
        DB      0Dh
        NOP
        CALL    ALPNU
        INC     DE
        LD      (TAB+2),DE
        LD      D,D
        LD      B,C
        LD      BC,(TAB+0)
        LD      D,B
        LD      B,C
        LD      HL,(TAB+2)
        SBC     HL,BC
        LD      (TAB+4),HL
        PUSH    HL
        DB      0EFh
        DB      0Dh
        DB      "Amount of bytes to be written="
        NOP
        POP     HL
        RST     0x18
        LD      H,(HL)
        RST     0x28
        LD      C,B
        DEC     C
        DEC     C
        DB      "Enter First Location in Eprom to"
        DB      0Dh
        DB      "be Blown"
        DB      0Dh
        NOP
        CALL    ALPNU
        LD      (TAB+6),DE
        LD      HL,(TAB+6)
        LD      DE,(TAB+4)
        LD      BC,CF07h
        OUT     (C),B      ; port B control to bit mode
        LD      B,40h
        OUT     (C),B      ; B0:B5=output, B6=input
 NEXTW: LD      BC,0F06h
        OUT     (C),B      ; port A control to output mode
        LD      C,04h      ; port A data
        OUT     (C),L      ; output L on port A
        LD      BC,0805h   ; port B data
        EX      AF,AF
        OUT     (C),B      ; 00001000, data direction write
        LD      B,09h
        ADD     HL,BC      ; ?
        OUT     (C),B      ; 00001001, enable data bus
        LD      B,0Bh
        OUT     (C),B      ; 00001011, low address strobe
        LD      B,08h
        EX      AF,AF
        OUT     (C),B      ; 00001000, disable data bus
        LD      C,04h      ; port A data
        OUT     (C),H      ; output H on port A
        LD      H,C
        LD      BC,0905h   ; port B data
        OUT     (C),B      ; 00001001, enable data bus
        LD      B,0Dh
        OUT     (C),B      ; 00001101, high address strobe
        LD      B,C        ; ?
        LD      B,08h
        OUT     (C),B      ; 00001000, disable data bus
        LD      BC,0004h   ; port A data
        OUT     (C),B      ; output 00h on port A
        LD      A,0FFh
        OUT     (06h),A    ; port A control to bit mode
        OUT     (06h),A    ; A0:A7=input
        LD      BC,0005h   ; port B data
        OUT     (C),B      ; 00000000, data direction read
        LD      B,01h
        OUT     (C),B      ; 00000001, enable data bus
        IN      A,(04h)    ; read data on port A into A reg
        PUSH    AF
        PUSH    DE
        LD      A,D
        OR      E
        JP      Z,BLOW     ; jump to blow EPROM fuse routine
        POP     DE
        POP     AF
        CP      0FFh
        JP      NZ,FAIL    ; indicate faulty EPROM
        INC     HL
        DEC     DE
        JP      NEXTW      ; next byte to write
  BLOW: LD      HL,(TAB+0)
        LD      BC,(TAB+4)
        LD      DE,(TAB+6)
  COMB: PUSH    BC
        LD      A,B
        OR      C
        JP      Z,ENDP     ; quit when finished
        LD      BC,0F06h
        OUT     (C),B      ; port A control to output mode
        LD      BC,CF07h
        OUT     (C),B      ; port B control to bit mode
        LD      B,40h
        OUT     (C),B      ; B0:B5=output, B6=input
        LD      C,04h
        OUT     (C),E      ; output E on port A
        LD      BC,0805h
        OUT     (C),B      ; 00001000, data direction write
        LD      B,09h
        OUT     (C),B      ; 00001001, enable data bus
        LD      B,0Bh
        OUT     (C),B      ; 00001011, low address strobe
        LD      B,08h
        OUT     (C),B      ; 00001000, disable data bus
        LD      C,04h      ; port A data
        OUT     (C),D      ; output D reg on port A
        LD      BC,0905h   ; port B data
        OUT     (C),B      ; 00001001, enable data bus
        LD      B,0Dh
        OUT     (C),B      ; 00001101, high address strobe
        LD      B,18h
        OUT     (C),B      ; 00011000, disable data bus, VPP on
        LD      BC,0004h   ; port A data
        OUT     (C),B      ; output 00h on port A
        LD      B,(HL)
        OUT     (C),B      ; output (HL) on port A
        LD      BC,1905h   ; port B data
        OUT     (C),B      ; 00011001, enable data bus
        LD      B,39h
        OUT     (C),B      ; 00111001, raise program pulse high
        NOP
        NOP
        NOP
        NOP
        NOP
        LD      B,19h
        OUT     (C),B      ; 00011001, drop program pulse low
        LD      B,15h
  WAIT: LD      A,0FFh     ; delay, instead of polling program pulse B6
        PUSH    AF
        PUSH    BC
        RST     0x38
        POP     BC
        POP     AF
        DEC     B
        LD      A,B
        CP      00h
        JP      NZ,WAIT
        POP     BC
        DEC     BC
        INC     DE
        INC     HL
        JP      COMB
 ;
 ; EPROM Faulty Error Message
 ;
  FAIL: RST     0x28
        DB      0Dh
        DB      0Dh
        DB      22h     ; '"'
        DB      "EPROM FAULTY"

        DB      22h     ; '"'
        NOP
        JP      ENDP
        LD      L,C
        LD      B,E
 ;
 ; HEX Alphanumeric to Binary Routine
 ;      Result returned in DE register
 ;
 ALPNU: LD      B,00h
    ST: RST     0x08
        CP      3Ah     ; ':'
        JP      P,ALPHA
        CP      30h     ; '0'
        JP      M,ALPHA
        SUB     30h     ; '0'
        JP      PRL
 ALPHA: CP      47h     ; 'G'
        JP      P,ST
        CP      41h     ; 'A'
        JP      M,ST
        SUB     37h     ; '7'
        JP      PRL
   PRL: PUSH    AF
        RST     0x18
        LD      A,D
        LD      A,B
        CP      00h
        JP      Z,RTA
        CP      01h
        JP      Z,RTB
        CP      02h
        JP      Z,RTC
        CP      03h
        JP      Z,RTD
   RTA: POP     AF
        RL      A
        RL      A
        RL      A
        RL      A
        LD      D,A
        JP      LAST
   RTB: POP     AF
        ADD     A,D
        LD      D,A
        JP      LAST
   RTC: POP     AF
        RL      A
        RL      A
        RL      A
        RL      A
        LD      E,A
        JP      LAST
   RTD: POP     AF
        ADD     A,E
        LD      E,A
        RET
  LAST: INC     B
        JP      ST
 ;
 ; End of the program
 ;
  ENDP: JP      NEW
            
          

Disassemblers in general are unable to generate meaningful labels or contextual comments to the listing because the binary files do not contain this information. Since I had the original paper assembler listing and other documentation, I was able to convert the numeric labels to meaningful labels and add comments. This resulted in the following listing where the assembly language instructions, which I am pleased to say matches quite closely with the original paper assembly language listing.

Running the EPROM Programmer in an Emulator

I used the nascon command line utility to convert the binary file Eprom-Burner.bin created above to the NAS file Eprom-Burner.nas. As the EPROM programmer was written to be executed at address 4000h, the nascon command switch –org must be specified accordingly.

 perl nascon Eprom-Burne.bin Eprom-Burner.nas -org 4000

There are a few Nascom emulators available online. I used the vnascom emulator despite its shortcoming of only being able to run with Windows XP or earlier operating systems. This didn't pose a problem for me as I have Windows XP as a VirtualBox virtual machine. Bear in mind also that as vnascom is legacy software it requires the input files to be saved as old style MSDOS 8.3 filenames.

The following command will run the emulator as a Nascom-2 machine hardware, load the NAS-SYS operating system, and run the EPROM programmer executing it at address 4000h. Note that I renamed Eprom-Burner.nas to eprom-~1.nas.

 vnascom mod=2 rom=nassys1.nas ram=eprom-~1.nas kbd=E4000

The output from the Nascom emulator displaying the EPROM programmer software as an animation. It seems to run as I remember.

The program when executed is self-explanatory and very simple to operate, as were many of the programs back in the day. There are only two modes, a 'Read Mode' which reads in what is stored on EPROM to Nascom RAM, and a 'Write Mode' which writes Nascom RAM out permanently to EPROM.

The NAS-SYS operating system is be used in conjunction with the EPROM programmer software, so as to facilitate the viewing of Nascom RAM after running 'Read Mode', or loading Nascom RAM prior to running 'Write Mode'.

Nascom Emulator

Conclusion

Having no assembler / text editor such as ZEAP at the time, assembly language instruction mnemonics were written with pencil and paper, then subsequently hand assembled prior to entering the hex Z80 machine code values directly into the Nascom. This meant getting the code right from the start, to avoid the mundane work an assembler would achieve instantly.

It is satisfying to now have the complete verbatim listing of the EPROM programmer, the first useful Z80 assembly language program I wrote for the Nascom. A follow up to this article as a future project would be to get the EPROM programmer working with a real Nascom computer.