Files
RFS/asm/rfs_bank4.asm
Philip Smart dbfa22baa4 Fix SFD700 build failures caused by Glass assembler IF scope limitations
Glass Z80 assembler (v0.5.1) makes labels inside IF blocks invisible from
outside those blocks. This caused multiple build failures for the SFD700
target:

- JP _PRTMZF/_PRTDBG in jump table: replaced with DB 0C3H + DW encoding
  which resolves same-condition scope forward references via DW
- LD DE,MSG* across IF scopes: created LDDE macro (DB 011H + DW) that
  produces identical machine code to LD DE,nn for all build targets
- SFD700 command table (CMDTABLE2): moved after all INCLUDE statements
  so DW function references resolve as backward same-scope references
- Removed FD/FDDIR entry from SFD700 command table — FDDIR only exists
  for ROMDISK/picoZ80 builds (MZ-700 WD1773 FDC direct access)
- Fixed PRTSTR→PRINTMSG undefined symbol in rfs_bank7.asm
- Fixed BUILD_PICOZ80 comment (was incorrectly labelled as SFD700)

All three build targets (ROMDISK, SFD700, picoZ80) verified clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 22:55:26 +00:00

1424 lines
62 KiB
NASM

;--------------------------------------------------------------------------------------------------------
;-
;- Name: rfs_bank4.asm
;- Created: July 2019
;- Author(s): Philip Smart
;- Description: Sharp MZ series Rom Filing System.
;- This assembly language program is written to utilise the banked flashroms added with
;- the MZ-80A RFS hardware upgrade.
;-
;- Credits:
;- Copyright: (c) 2018-2026 Philip Smart <philip.smart@net2net.org>
;-
;- History: July 2019 - Merged 2 utilities to create this compilation.
;- May 2020 - Bank switch changes with release of v2 pcb with coded latch. The coded
;- latch adds additional instruction overhead as the control latches share
;- the same address space as the Flash RAMS thus the extra hardware to
;- only enable the control registers if a fixed number of reads is made
;- into the upper 8 bytes which normally wouldnt occur. Caveat - ensure
;- that no loop instruction is ever placed into EFF8H - EFFFH.
;- Aug 2023 - Updates to make RFS run under the SFD700 Floppy Disk Interface board.
;- UROM remains the same, a 2K paged ROM, MROM is located at F000 when
;- RFS is built for the SFD700.
;-
;--------------------------------------------------------------------------------------------------------
;- This source file is free software: you can redistribute it and-or modify
;- it under the terms of the GNU General Public License as published
;- by the Free Software Foundation, either version 3 of the License, or
;- (at your option) any later version.
;-
;- This source file is distributed in the hope that it will be useful,
;- but WITHOUT ANY WARRANTY; without even the implied warranty of
;- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;- GNU General Public License for more details.
;-
;- You should have received a copy of the GNU General Public License
;- along with this program. If not, see <http://www.gnu.org/licenses/>.
;--------------------------------------------------------------------------------------------------------
IF BUILD_SFD700 = 1
ORG 0E000H
ALIGN 0E300H
DB "BANK4"
ALIGN UROMADDR
ENDIF
;===========================================================
;
; USER ROM BANK 4 - CMT Controller utilities.
;
;===========================================================
ORG UROMADDR
;--------------------------------
; Common code spanning all banks.
;--------------------------------
NOP
HWSELROM2 ; Select the first ROM page.
;
; No mans land... this should have switched to Bank 0 and at this point there is a jump to 00000H.
JP 00000H ; This is for safety!!
;------------------------------------------------------------------------------------------
; Bank switching code, allows a call to code in another bank.
; This code is duplicated in each bank such that a bank switch doesnt affect logic flow.
;------------------------------------------------------------------------------------------
ALIGN_NOPS UROMBSTBL
;
BKSW4to0: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK0 ; Required bank to call.
JR BKSW4_0
BKSW4to1: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK1 ; Required bank to call.
JR BKSW4_0
BKSW4to2: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK2 ; Required bank to call.
JR BKSW4_0
BKSW4to3: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK3 ; Required bank to call.
JR BKSW4_0
BKSW4to4: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK4 ; Required bank to call.
JR BKSW4_0
BKSW4to5: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK5 ; Required bank to call.
JR BKSW4_0
BKSW4to6: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK6 ; Required bank to call.
JR BKSW4_0
BKSW4to7: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK7 ; Required bank to call.
JR BKSW4_0
BKSW4to8: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK8 ; Required bank to call.
JR BKSW4_0
BKSW4to9: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK9 ; Required bank to call.
JR BKSW4_0
BKSW4to10: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK10 ; Required bank to call.
JR BKSW4_0
BKSW4to11: PUSH AF
LD A, ROMBANK4 ; Calling bank (ie. us).
PUSH AF
LD A, ROMBANK11 ; Required bank to call.
;
BKSW4_0: PUSH HL ; Place function to call on stack
LD HL, BKSWRET4 ; Place bank switchers return address on stack.
EX (SP),HL
LD (TMPSTACKP),SP ; Save the stack pointer as some old code corrupts it.
BNKSWSEL
JP (HL) ; Jump to required function.
BKSWRET4: POP AF ; Get bank which called us.
BNKSWSELRET
POP AF
RET
;-------------------------------------------------------------------------------
; START OF CMT CONTROLLER FUNCTIONALITY
;-------------------------------------------------------------------------------
; CMT Utility to Load a program from tape.
;
; Three entry points:
; LOADTAPE = Load the first program shifting to lo memory if required and execute.
; LOADTAPENX = Load the first program and return without executing.
; LOADTAPECP = Load the first program to address 0x1200 and return.
;
LOADTAPECP: LD A,0FFH
LD (CMTAUTOEXEC),A
JR LOADTAPE2
LOADTAPENX: LD A,0FFH
JR LOADTAPE1
LOADTAPE: LD A,000H
LOADTAPE1: LD (CMTAUTOEXEC),A
XOR A
LOADTAPE2: LD (CMTCOPY),A ; Set cmt copy mode, 0xFF if we are copying.
LD A,0FFH ; If called interbank, set a result code in memory to detect success.
LD (RESULT),A
CALL ?RDI
JP C,?ERX2
LDDE MSGLOAD ; 'LOADING '
LD BC,NAME
LD HL,PRINTMSG
CALL BKSW4to6
XOR A
LD (CMTLOLOAD),A
LD HL,(DTADR) ; Common code, store load address in case we shift or manipulate loading.
LD (DTADRSTORE),HL
LD A,(CMTCOPY) ; If were copying we always load at 0x1200.
OR A
JR Z,LOADTAPE3
LD HL,01200H
LD (DTADR),HL
LOADTAPE3: LD HL,(DTADR) ; If were loading and the load address is below 0x1200, shift it to 0x1200 to load then move into correct location.
LD A,H
OR L
JR NZ,LOADTAPE4
LD A,0FFh
LD (CMTLOLOAD),A
LD HL,01200h
LD (DTADR),HL
LOADTAPE4: CALL ?RDD
JP C,?ERX2
LD HL,(DTADRSTORE) ; Restore the original load address into the CMT header.
LD (DTADR),HL
LD A,(CMTCOPY)
OR A
JR NZ,LOADTAPE6
LOADTAPE5: LD A,(CMTAUTOEXEC) ; Get back the auto execute flag.
OR A
JR NZ,LOADTAPE6 ; Dont execute..
LD A,(CMTLOLOAD)
CP 0FFh
JR Z,LOADTAPELM ; Execute at low memory?
LD BC,00100h
LD HL,(EXADR)
JP (HL)
LOADTAPELM: LD A,(MEMSW) ; Perform memory switch, mapping out ROM from $0000 to $C000
LD HL,01200h ; Shift the program down to RAM at $0000
LD DE,00000h
LD BC,(SIZE)
LDIR
LD BC,00100h
LD HL,(EXADR) ; Fetch exec address and run.
JP (HL)
LOADTAPE6: LDDE MSGCMTDATA
PUSH HL ; Load address as parameter 2.
LD HL,(EXADR)
PUSH HL ; Execution address as parameter 1.
LD BC,(SIZE) ; Size as BC parameter.
LD HL,PRINTMSG
CALL BKSW4to6
POP BC
POP BC ; Waste parameters.
XOR A ; Success.
LD (RESULT),A
RET
; SA1510 Routine to write a tape header. Copied into the RFS and modified to merge better
; with the RFS interface.
;
CMTWRI: ;DI
PUSH DE
PUSH BC
PUSH HL
LD D,0D7H
LD E,0CCH
LD HL,IBUFE
LD BC,00080H
CALL CKSUM
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
CALL MOTOR80A
ENDIF
IF BUILD_SFD700 = 1
IN A,(SFD700_MODE)
OR A
JR Z,CMTWRI80A
CALL MOTOR700
JR CMTWRI0
CMTWRI80A: CALL MOTOR80A
ENDIF
CMTWRI0: JR C,CMTWRI2
LD A,E
CP 0CCH
JR NZ,CMTWRI1
PUSH HL
PUSH DE
PUSH BC
LDDE MSGCMTWRITE
LD BC,NAME
LD HL,PRINTMSG
CALL BKSW4to6
POP BC
POP DE
POP HL
CMTWRI1: CALL GAP
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
CALL WTAPE80A
ENDIF
IF BUILD_SFD700 = 1
IN A,(SFD700_MODE)
OR A
JR Z,CMTWRI80A2
CALL WTAPE700
JR CMTWRI2
CMTWRI80A2: CALL WTAPE80A
ENDIF
CMTWRI2: POP HL
POP BC
POP DE
CALL MSTOP
PUSH AF
LD A,(TIMFG)
CP 0F0H
JR NZ,CMTWRI3
;EI
CMTWRI3: POP AF
RET
; Method to save an application stored in memory to a cassette in the CMT. The start, size and execution address are either given in BUFER via the
; command line and the a filename is prompted for and read, or alternatively all the data is passed into the function already set in the CMT header.
; The tape is then opened and the header + data are written out.
;
SAVECMT: LD A,0FFH ; Set SDCOPY to indicate this is a copy command and not a command line save.
JR SAVEX1
;
; Normal entry point, the cmdline contains XXXXYYYYZZZZ where XXXX=start, YYYY=size, ZZZZ=exec addr. A filenname is prompted for and read.
; The data is stored in the CMT header prior to writing out the header and data..
;
SAVEX: LD HL,GETCMTPARM ; Get the CMT parameters.
CALL BKSW4to3
LD A,C
OR A
RET NZ ; Exit if an error occurred.
XOR A
SAVEX1: LD (SDCOPY),A
LD A,0FFH
LD (RESULT),A ; For interbank calls, pass result via a memory variable. Assume failure unless updated.
LD A,OBJCD ; Set attribute: OBJ
LD (ATRB),A
CALL CMTWRI ; Commence header write. Header doesnt need updating for header write.
?ERX1: JP C,?ERX2
LD A,(SDCOPY)
OR A
JR Z,SAVEX2
LD DE,(DTADR)
LD A,D ; If copying and address is below 1000H, then data is held at 1200H so update header for write.
CP 001H
JR NC,SAVEX2
LD DE,01200H
LD (DTADR),DE
SAVEX2: CALL ?WRD ; data
JR C,?ERX1
LDDE MSGSAVEOK ; 'OK!'
LD HL,PRINTMSG
CALL BKSW4to6
LD A,0 ; Success.
LD (RESULT),A
RET
?ERX2: CP 002h
JR NZ,?ERX3
LD (RESULT),A ; Set break key pressed code.
RET Z
?ERX3: LDDE MSGE1 ; 'CHECK SUM ER.'
LD HL,PRINTMSG
CALL BKSW4to6
RET
; Method to verify that a tape write occurred free of error. After a write, the tape is read and compared with the memory that created it.
;
VRFYX: CALL ?VRFY
JP C,?ERX2
LDDE MSGOK ; 'OK!'
LD HL,PRINTMSG
CALL BKSW4to6
RET
; Method to toggle the audible key press sound, ie a beep when a key is pressed.
;
SGX: LD A,(SWRK)
RRA
CCF
RLA
LD (SWRK),A
RET
;-------------------------------------------------------------------------------
; END OF CMT CONTROLLER FUNCTIONALITY
;-------------------------------------------------------------------------------
;-------------------------------------------------------------------------------
; START OF MEMORY TEST FUNCTIONALITY
;-------------------------------------------------------------------------------
MEMTEST: LD B,240 ; Number of loops
LOOP: LD HL,MEMSTART ; Start of checked memory,
LD D,0CFh ; End memory check CF00
LOOP1: LD A,000h
CP L
JR NZ,LOOP1b
CALL PRTHL ; Print HL as 4digit hex.
LD A,0C4h ; Move cursor left.
LD E,004h ; 4 times.
LOOP1a: CALL DPCT
DEC E
JR NZ,LOOP1a
LOOP1b: INC HL
LD A,H
CP D ; Have we reached end of memory.
JR Z,LOOP3 ; Yes, exit.
LD A,(HL) ; Read memory location under test, ie. 0.
CPL ; Subtract, ie. FF - A, ie FF - 0 = FF.
LD (HL),A ; Write it back, ie. FF.
SUB (HL) ; Subtract written memory value from A, ie. should be 0.
JR NZ,LOOP2 ; Not zero, we have an error.
LD A,(HL) ; Reread memory location, ie. FF
CPL ; Subtract FF - FF
LD (HL),A ; Write 0
SUB (HL) ; Subtract 0
JR Z,LOOP1 ; Loop if the same, ie. 0
LOOP2: LD A,16h
CALL PRNT ; Print A
CALL PRTHX ; Print HL as 4 digit hex.
CALL PRNTS ; Print space.
XOR A
LD (HL),A
LD A,(HL) ; Get into A the failing bits.
CALL PRTHX ; Print A as 2 digit hex.
CALL PRNTS ; Print space.
LD A,0FFh ; Repeat but first load FF into memory
LD (HL),A
LD A,(HL)
CALL PRTHX ; Print A as 2 digit hex.
NOP
JR LOOP4
LOOP3: CALL PRTHL
LD DE,OKCHECK
CALL MSG ; Print check message in DE
LD A,B ; Print loop count.
CALL PRTHX
LD DE,OKMSG
CALL MSG ; Print ok message in DE
CALL NL
DEC B
JR NZ,LOOP
LD DE,DONEMSG
CALL MSG ; Print check message in DE
JP ST1X
LOOP4: LD B,09h
CALL PRNTS ; Print space.
XOR A ; Zero A
SCF ; Set Carry
LOOP5: PUSH AF ; Store A and Flags
LD (HL),A ; Store 0 to bad location.
LD A,(HL) ; Read back
CALL PRTHX ; Print A as 2 digit hex.
CALL PRNTS ; Print space
POP AF ; Get back A (ie. 0 + C)
RLA ; Rotate left A. Bit LSB becomes Carry (ie. 1 first instance), Carry becomes MSB
DJNZ LOOP5 ; Loop if not zero, ie. print out all bit locations written and read to memory to locate bad bit.
XOR A ; Zero A, clears flags.
LD A,80h
LD B,08h
LOOP6: PUSH AF ; Repeat above but AND memory location with original A (ie. 80)
LD C,A ; Basically walk through all the bits to find which one is stuck.
LD (HL),A
LD A,(HL)
AND C
NOP
JR Z,LOOP8 ; If zero then print out the bit number
NOP
NOP
LD A,C
CPL
LD (HL),A
LD A,(HL)
AND C
JR NZ,LOOP8 ; As above, if the compliment doesnt yield zero, print out the bit number.
LOOP7: POP AF
RRCA
NOP
DJNZ LOOP6
JP ST1X
LOOP8: CALL LETNL ; New line.
LD DE,BITMSG ; BIT message
CALL MSG ; Print message in DE
LD A,B
DEC A
CALL PRTHX ; Print A as 2 digit hex, ie. BIT number.
CALL LETNL ; New line
LD DE,BANKMSG ; BANK message
CALL MSG ; Print message in DE
LD A,H
CP 50h ; 'P'
JR NC,LOOP9 ; Work out bank number, 1, 2 or 3.
LD A,01h
JR LOOP11
LOOP9: CP 90h
JR NC,LOOP10
LD A,02h
JR LOOP11
LOOP10: LD A,03h
LOOP11: CALL PRTHX ; Print A as 2 digit hex, ie. BANK number.
JR LOOP7
DLY1S: PUSH AF
PUSH BC
LD C,10
L0324: CALL DLY12
DEC C
JR NZ,L0324
POP BC
POP AF
RET
;-------------------------------------------------------------------------------
; END OF MEMORY TEST FUNCTIONALITY
;-------------------------------------------------------------------------------
;-------------------------------------------------------------------------------
; START OF TIMER TEST FUNCTIONALITY
;-------------------------------------------------------------------------------
; Test the 8253 Timer, configure it as per the monitor and display the read back values.
TIMERTST: CALL NL
LD DE,MSG_TIMERTST
CALL MSG
CALL NL
LD DE,MSG_TIMERVAL
CALL MSG
LD A,01h
LD DE,8000h
CALL TIMERTST1
NDE: JP NDE
JP ST1X
TIMERTST1: ;DI
PUSH BC
PUSH DE
PUSH HL
LD (AMPM),A
LD A,0F0H
LD (TIMFG),A
ABCD: LD HL,0A8C0H
XOR A
SBC HL,DE
PUSH HL
INC HL
EX DE,HL
LD HL,CONTF ; Control Register
LD (HL),0B0H ; 10110000 Control Counter 2 10, Write 2 bytes 11, 000 Interrupt on Terminal Count, 0 16 bit binary
LD (HL),074H ; 01110100 Control Counter 1 01, Write 2 bytes 11, 010 Rate Generator, 0 16 bit binary
LD (HL),030H ; 00110100 Control Counter 1 01, Write 2 bytes 11, 010 interrupt on Terminal Count, 0 16 bit binary
LD HL,CONT2 ; Counter 2
LD (HL),E
LD (HL),D
LD HL,CONT1 ; Counter 1
LD (HL),00AH
LD (HL),000H
LD HL,CONT0 ; Counter 0
LD (HL),00CH
LD (HL),0C0H
; LD HL,CONT2 ; Counter 2
; LD C,(HL)
; LD A,(HL)
; CP D
; JP NZ,L0323H
; LD A,C
; CP E
; JP Z,CDEF
;
L0323H: PUSH AF
PUSH BC
PUSH DE
PUSH HL
;
LD HL,CONTF ; Control Register
LD (HL),080H
LD HL,CONT2 ; Counter 2
LD C,(HL)
LD A,(HL)
CALL PRTHX
LD A,C
CALL PRTHX
;
CALL PRNTS
;CALL DLY1S
;
LD HL,CONTF ; Control Register
LD (HL),040H
LD HL,CONT1 ; Counter 1
LD C,(HL)
LD A,(HL)
CALL PRTHX
LD A,C
CALL PRTHX
;
CALL PRNTS
;CALL DLY1S
;
LD HL,CONTF ; Control Register
LD (HL),000H
LD HL,CONT0 ; Counter 0
LD C,(HL)
LD A,(HL)
CALL PRTHX
LD A,C
CALL PRTHX
;
;CALL DLY1S
;
LD A,0C4h ; Move cursor left.
LD E,0Eh ; 4 times.
L0330: CALL DPCT
DEC E
JR NZ,L0330
;
; LD C,20
;L0324: CALL DLY12
; DEC C
; JR NZ,L0324
;
POP HL
POP DE
POP BC
POP AF
;
LD HL,CONT2 ; Counter 2
LD C,(HL)
LD A,(HL)
CP D
JP NZ,L0323H
LD A,C
CP E
JP NZ,L0323H
;
;
PUSH AF
PUSH BC
PUSH DE
PUSH HL
CALL NL
CALL NL
CALL NL
LD DE,MSG_TIMERVAL2
CALL MSG
POP HL
POP DE
POP BC
POP AF
;
CDEF: POP DE
LD HL,CONT1
LD (HL),00CH
LD (HL),07BH
INC HL
L0336H: PUSH AF
PUSH BC
PUSH DE
PUSH HL
;
LD HL,CONTF ; Control Register
LD (HL),080H
LD HL,CONT2 ; Counter 2
LD C,(HL)
LD A,(HL)
CALL PRTHX
LD A,C
CALL PRTHX
;
CALL PRNTS
CALL DLY1S
;
LD HL,CONTF ; Control Register
LD (HL),040H
LD HL,CONT1 ; Counter 1
LD C,(HL)
LD A,(HL)
CALL PRTHX
LD A,C
CALL PRTHX
;
CALL PRNTS
CALL DLY1S
;
LD HL,CONTF ; Control Register
LD (HL),000H
LD HL,CONT0 ; Counter 0
LD C,(HL)
LD A,(HL)
CALL PRTHX
LD A,C
CALL PRTHX
;
CALL DLY1S
;
LD A,0C4h ; Move cursor left.
LD E,0Eh ; 4 times.
L0340: CALL DPCT
DEC E
JR NZ,L0340
;
POP HL
POP DE
POP BC
POP AF
LD HL,CONT2 ; Counter 2
LD C,(HL)
LD A,(HL)
CP D
JR NZ,L0336H
LD A,C
CP E
JR NZ,L0336H
CALL NL
LD DE,MSG_TIMERVAL3
CALL MSG
POP HL
POP DE
POP BC
;EI
RET
;-------------------------------------------------------------------------------
; END OF TIMER TEST FUNCTIONALITY
;-------------------------------------------------------------------------------
;--------------------------------------
;
; Message table
;
;--------------------------------------
OKCHECK: DB ", CHECK: ", 0Dh
OKMSG: DB " OK.", 0Dh
DONEMSG: DB 11h
DB "RAM TEST COMPLETE.", 0Dh
BITMSG: DB " BIT: ", 0Dh
BANKMSG: DB " BANK: ", 0Dh
MSG_TIMERTST:
DB "8253 TIMER TEST", 0Dh, 00h
MSG_TIMERVAL:
DB "READ VALUE 1: ", 0Dh, 00h
MSG_TIMERVAL2:
DB "READ VALUE 2: ", 0Dh, 00h
MSG_TIMERVAL3:
DB "READ DONE.", 0Dh, 00h
IF BUILD_PICOZ80 = 1
;-------------------------------------------------------------------------------
; QD BOOT / QL LIST - Quick Disk boot and directory listing (picoZ80 only).
;
; QD - Load & execute first file from QD drive via SIO ports F4-F7H.
; QL - List directory of QD disk.
;
; Works with both PHYSICAL QD drives (MZ-1E14) and VIRTUAL QD drives
; (picoZ80 QDDrive.c emulator) since both use the same Z80 SIO/2 protocol.
;
; Based on disassembly of the MZ-1E14 QD Interface ROM.
; SIO port mapping: F4=ChA Data, F6=ChA Ctrl, F7=ChB Ctrl
; Protocol: SIO sync mode with sync chars 16H 16H, address mark A5H,
; block type + size(2) + data + CRC(3 trailing bytes).
; Block type 00=header (64-byte MZF header), 01/05=data.
;-------------------------------------------------------------------------------
;---------------------------------------------------------------
; QD command entry: boot first file from QD.
;---------------------------------------------------------------
QDBOOT: XOR A ; 0 = boot (auto-execute)
JR QDGO
;---------------------------------------------------------------
; QL command entry: list QD directory.
;---------------------------------------------------------------
QDLIST: LD A,001H ; 1 = list directory
QDGO: LD (RESULT),A ; Store mode: 0=boot, 1=list
; --- Initialize SIO and start QD motor ---
CALL QDINIT_STATE ; Zero out QD work area
CALL QDSTART_MOTOR ; SIO init + motor on + wait DCD
LD A,(RESULT)
OR A
JP NZ,QDGO_LIST ; Mode 1 = directory list
; --- QD BOOT: Read first header, then first data block ---
CALL QDREAD_HDR ; Read first header block into MZF area
JP C,QDERR_EXIT ; Error reading header
; Print "Loading <filename>"
LDDE MSGLOAD+1 ; Skip leading CR
LD BC,NAME
LD HL,PRINTMSG
CALL BKSW4to6
; Read the data block.
; QD header field convention (differs from CMT):
; DTADR = data size limit, EXADR = load address.
; If EXADR=0 and COMNT=0, relocate: load to 1200H, copy to 0000H after.
LD HL,(EXADR)
LD A,H
OR L
JR NZ,QDBOOT1 ; EXADR non-zero, use as load addr
LD HL,(COMNT)
LD A,H
OR L
JR NZ,QDBOOT1A ; COMNT non-zero, load to EXADR (=0)
; Both zero: load to 1200H, will relocate to 0000H after
LD A,0FFH
LD (QDRELOC),A
LD HL,01200H
JR QDBOOT1
QDBOOT1A: LD HL,(EXADR) ; Re-read EXADR (matches MZ-1E14 ROM)
QDBOOT1: LD (QDLOADADDR),HL
LD HL,(DTADR)
LD (QDBLKSIZE),HL ; Max bytes to read (QD: DTADR=size)
; Read data block using QD SVC (mode 3, sub-mode 1 = data block)
LD HL,00003H
LD (QDCMD),HL ; QDCMD=3 (read block), QDSUBCMD=0 (low byte)
LD A,001H
LD (QDSUBCMD),A ; Sub-mode 1 = data block (bit 0 = 1)
CALL QDSVC_MAIN ; Read data block with retry
JP C,QDERR_EXIT ; Error reading data
; Motor off
CALL QDMOTOR_OFF
; Display loaded file info (QD: DTADR=size, EXADR=load addr)
LDDE MSGCMTDATA
LD HL,(QDLOADADDR)
PUSH HL ; Load address for 0FEH
PUSH HL ; Exec address for 0FFH (same for QD)
LD BC,(DTADR) ; Size for 0FBH (QD: DTADR=size)
LD HL,PRINTMSG
CALL BKSW4to6
POP BC
POP BC
; Check if OBJ type for auto-execution
LD A,(ATRB)
CP OBJCD
RET NZ ; Not OBJ, just return
; Check relocation flag
LD A,(QDRELOC)
CP 0FFH
JR Z,QDBOOT_RELOC
; Execute from load address (EXADR in QD convention)
LD BC,00300H ; B=3 → $QD default device + auto-run
LD HL,(QDLOADADDR)
JP (HL)
QDBOOT_RELOC:
; Relocate from 1200H to 0000H and execute
OUT (MMIO0),A ; Page out ROM at E800H
LD HL,01200H
LD DE,00000H
LD BC,(DTADR) ; QD: DTADR = data size
LDIR
LD BC,00300H ; B=3 → $QD default device + auto-run
JP 00000H
; --- QD DIRECTORY LIST ---
QDGO_LIST: CALL QDREAD_DIR ; Read all headers, display directory
; Motor off (already done inside QDREAD_DIR on error/completion)
QDERR_EXIT: ; Common error/return handler — motor off
PUSH AF ; Save error code
CALL QDMOTOR_OFF
POP AF
RET NC ; No error, just return
; Display error message based on error code in A
LDDE MSGQDERR ; Default: "QD: Read error"
CP 028H
JR Z,QDERR_SHOW ; End of tape (file not found)
CP 032H
JR NZ,QDERR_SHOW2
LDDE MSGQDERR ; Not ready
JR QDERR_SHOW
QDERR_SHOW2:
CP 036H
JR NZ,QDERR_SHOW
LDDE MSGQDFMT ; Unformat / bad format
QDERR_SHOW: LD HL,PRINTMSG
CALL BKSW4to6
RET
;---------------------------------------------------------------
; QDINIT_STATE: Initialize QD work area variables to zero.
;---------------------------------------------------------------
QDINIT_STATE:
XOR A
LD (QDMOTOR),A
LD (QDWORK1),A
LD (QDWORK2),A
LD (QDADDRMARK),A
LD (QDEXPMARK),A
LD (QDRELOC),A
RET
;---------------------------------------------------------------
; QDSTART_MOTOR: Initialize SIO, turn motor on, wait for DCD.
; Combines QDSIO_INIT + QDINIT_FULL from MZ-1E14 ROM.
; Returns: CF=1 on error (not ready).
;---------------------------------------------------------------
QDSTART_MOTOR:
; Write SIO init table to Channel A control (OTIR)
LD HL,QDSIO_INITTBL
LD B,011 ; 11 bytes in init table
LD C,SIOA_CTRL
OTIR ; Output init table to ChA ctrl
; Motor on: ChB WR5 = 80H (RTS = motor on)
LD A,005H
LD (QDMOTOR),A ; Motor status = on
OUT (SIOB_CTRL),A ; ChB: register pointer = WR5
LD A,080H
OUT (SIOB_CTRL),A ; ChB WR5 = 80H (motor on)
; Wait for DCD on Channel A
QDSM_WDCD: LD A,010H
OUT (SIOA_CTRL),A ; ChA: reset ext/status
IN A,(SIOA_CTRL) ; Read ChA RR0
AND 008H ; DCD? (bit 3)
JR NZ,QDSM_HAVEDCD
; Check break key
CALL BRKEY
JR NZ,QDSM_WDCD ; Keep waiting unless break
; Break pressed or no DCD
SCF
RET
QDSM_HAVEDCD:
; Wait for sync detection on Channel B (tape motion detected)
LD A,010H
OUT (SIOB_CTRL),A ; ChB: reset ext/status
IN A,(SIOB_CTRL) ; Read ChB RR0
AND 008H ; DCD on ChB?
JR Z,QDSM_WDCD ; Not yet, keep waiting
; Short delay for tape to stabilize
LD BC,000E9H
CALL QDDELAY
; Count total blocks on disk by reading FNBLK
CALL QDHUNT_SYNC ; Enter hunt, wait sync, read type
; A = block type from FNBLK (should be the block count encoded)
; Actually the FNBLK has: sync + A5 + <block_count> + CRC
; After hunt, first byte read is the address mark (A5 consumed by hunt)
; then block type (= block count).
; For FNBLK the 'type' byte IS the total block count.
LD (QDTOTBLKS),A
INC A
LD (QDREMBLKS),A
; Read trailing CRC bytes
CALL QDREAD_TRAILING
; Set first-read flag
LD HL,QDFLAGS
SET 3,(HL)
XOR A
LD (QDEXPMARK),A ; Reset expected address mark
OR A ; Clear carry = success
RET
;---------------------------------------------------------------
; QDREAD_HDR: Read first header block from QD into MZF header area.
; Populates: ATRB, NAME, SIZE, DTADR, EXADR from the 64-byte header.
; Returns: CF=1 on error.
;---------------------------------------------------------------
QDREAD_HDR:
LD HL,00003H
LD (QDCMD),HL ; Mode 3 = read block
XOR A
LD (QDSUBCMD),A ; Sub-mode 0 = header block (bit 0 = 0)
LD HL,ATRB ; Load address = MZF header area
LD (QDLOADADDR),HL
LD HL,00040H ; Max 64 bytes (MZF header)
LD (QDBLKSIZE),HL
JP QDSVC_MAIN ; Read with retry, returns CF
;---------------------------------------------------------------
; QDREAD_DIR: Read all header blocks and display directory.
; Returns: CF=1 on error.
;---------------------------------------------------------------
QDREAD_DIR:
; Read headers into directory buffer at QDIRBUF (18 bytes per entry)
LD B,000H ; File counter
LD HL,QDIRBUF ; Directory buffer start
QDRD_LOOP: LD (QDLOADADDR),HL
PUSH HL
PUSH BC
LD HL,00003H
LD (QDCMD),HL ; Mode 3 = read block
XOR A
LD (QDSUBCMD),A ; Header block
LD HL,00040H
LD (QDBLKSIZE),HL ; 64 bytes max
CALL QDSVC_MAIN ; Read header
POP BC
POP HL
JR C,QDRD_DONE ; Error or end of tape (28H)
INC B ; Count this file
LD DE,00012H ; Next entry offset (18 bytes)
ADD HL,DE
JR QDRD_LOOP
QDRD_DONE: ; Motor off
PUSH BC
CALL QDMOTOR_OFF
POP BC
; Check if we found any files
XOR A
CP B
JR NC,QDRD_EMPTY ; No files found
; Display directory listing
CALL NL
LD HL,QDIRBUF
QDRD_SHOW: ; Print filename from header at (HL)+1 (offset 1 = NAME in MZF header)
PUSH HL
PUSH BC
INC HL ; Point to NAME field
EX DE,HL
; Print the filename (17 chars max)
LD B,FNSIZE
QDRD_PNAME: LD A,(DE)
OR A
JR Z,QDRD_PEND
CP CR
JR Z,QDRD_PEND
CALL PRNT ; Print one character
INC DE
DJNZ QDRD_PNAME
QDRD_PEND: CALL NL ; Newline after each entry
POP BC
POP HL
; Advance to next entry
LD DE,00012H
ADD HL,DE
DJNZ QDRD_SHOW
OR A ; Clear carry = success
RET
QDRD_EMPTY: ; No files on disk
OR A
RET
;---------------------------------------------------------------
; QDSVC_MAIN: QD block read service with retry (5 retries).
; Uses QDCMD/QDSUBCMD/QDLOADADDR/QDBLKSIZE.
; Returns: CF=1 on error, A=error code.
;---------------------------------------------------------------
QDSVC_MAIN: LD A,005H ; 5 retries
LD (QDRETRY),A
QDSVC_RETRY:
DI
CALL QDREAD_DISPATCH ; Attempt read
EI
RET NC ; Success
PUSH AF
CP 028H ; End of tape?
JR Z,QDSVC_RET ; Yes — not retryable
CALL QDMOTOR_OFF
POP AF
PUSH AF
CP 029H ; CRC/overrun error?
JR NZ,QDSVC_RET ; No — not retryable
; Retry on error 29H
LD HL,QDRETRY
DEC (HL)
JR Z,QDSVC_EXHAUST
POP AF
; Re-init for retry
CALL QDSTART_MOTOR
JR NC,QDSVC_RETRY
; Motor start failed on retry — return error
SCF
RET
QDSVC_EXHAUST:
XOR A
LD (QDADDRMARK),A ; Reset address mark counter
QDSVC_RET: POP AF
RET
;---------------------------------------------------------------
; QDREAD_DISPATCH: Read dispatcher based on QDCMD.
; Mode 3 = read block.
; Returns: CF=1 on error, A=error code.
;---------------------------------------------------------------
QDREAD_DISPATCH:
LD (QDSAVESP),SP ; Save SP for error recovery
LD A,(QDCMD)
CP 003H
JR Z,QDREAD_BLOCK
; Other modes not needed for RFS QD/QL
RET
;---------------------------------------------------------------
; QDREAD_BLOCK: Read one block from QD.
; Expects QDSUBCMD bit 0: 0=header, 1=data.
; Expects QDLOADADDR, QDBLKSIZE set.
; Returns: CF=1 on error.
;---------------------------------------------------------------
QDREAD_BLOCK:
; Ensure motor is on
LD A,(QDMOTOR)
OR A
CALL Z,QDSTART_MOTOR
RET C
; Find matching block
CALL QDREAD_FINDBLK
RET C ; Error finding block
; Read block size (2 bytes: lo, hi)
CALL QDREAD_BYTE
LD C,A ; Size low
CALL QDREAD_BYTE
LD B,A ; Size high
; Check block fits in buffer
LD HL,(QDBLKSIZE)
SBC HL,BC
JP C,QDERR_29_ENTRY ; Too big
; Read data bytes into QDLOADADDR
LD HL,(QDLOADADDR)
QDRD_DATA: CALL QDREAD_BYTE
LD (HL),A
INC HL
DEC BC
LD A,B
OR C
JR NZ,QDRD_DATA
; Read trailing CRC bytes
CALL QDREAD_TRAILING
; If data block (odd sub-mode), we're done
LD A,(QDSUBCMD)
BIT 0,A
JR NZ,QDRD_DONE2
; Header block — keep motor running for next read
OR A ; Clear carry
RET
QDRD_DONE2: OR A ; Clear carry
RET
;---------------------------------------------------------------
; QDREAD_FINDBLK: Scan blocks to find matching type.
; QDSUBCMD bit 0 determines: 0=header, 1=data.
; Returns: CF=1 on error (28H=end of tape).
;---------------------------------------------------------------
QDREAD_FINDBLK:
LD HL,QDREMBLKS
DEC (HL)
JR Z,QDRD_END ; No more blocks
; Enter hunt mode, sync, read block type
CALL QDHUNT_SYNC
LD C,A ; Save block type
; Check address mark sequence
LD A,(QDADDRMARK)
LD HL,QDEXPMARK
CP (HL)
JR NZ,QDRD_SKIP ; Not expected mark, skip
INC A
LD (QDADDRMARK),A ; Increment address mark
LD (HL),A
; Check if block type matches what we want
LD A,(QDSUBCMD)
XOR C ; Compare type bits
RRA ; Bit 0 to carry
RET NC ; Match found! (same parity)
; Wrong block type — skip its data
QDRD_SKIPDATA:
CALL QDREAD_BYTE ; Size low
LD C,A
CALL QDREAD_BYTE ; Size high
LD B,A
QDRD_SKIPLOOP:
CALL QDREAD_BYTE ; Discard data byte
DEC BC
LD A,B
OR C
JR NZ,QDRD_SKIPLOOP
CALL QDREAD_TRAILING ; Read CRC
JR QDREAD_FINDBLK ; Try next block
QDRD_SKIP: INC (HL) ; Adjust expected mark
JR QDRD_SKIPDATA ; Skip this block
QDRD_END: LD A,028H ; Error: end of tape
SCF
RET
;---------------------------------------------------------------
; QDHUNT_SYNC: Enter hunt mode, wait for sync, read block type.
; Returns: A = block type byte.
; On error: jumps to QDERR_xx via QDSAVESP.
;---------------------------------------------------------------
QDHUNT_SYNC:
; Re-init SIO for hunt (same as EF18 in MZ-1E14)
LD A,058H ; WR0: Channel reset + reset IUS
LD B,011 ; 11 bytes
LD HL,QDSIO_INITTBL
; Write register + OTIR: OUT (C),A then OTIR
LD C,SIOA_CTRL
OUT (C),A ; Write WR0 command
; Motor on via ChB
LD A,005H
OUT (SIOB_CTRL),A ; ChB: register pointer = WR5
LD A,080H
OUT (SIOB_CTRL),A ; ChB WR5 = 80H (motor on)
OTIR ; Output init table to ChA ctrl
; Delay: longer on first read, shorter on subsequent
LD HL,QDFLAGS
BIT 3,(HL)
LD BC,00003H ; Short delay
JR Z,QDHS_DLY
RES 3,(HL) ; Clear first-read flag
LD BC,000A0H ; Long delay
QDHS_DLY: CALL QDDELAY
; Set up hunt mode on Channel A
LD A,005H
OUT (SIOB_CTRL),A ; ChB: WR5
LD A,082H
OUT (SIOB_CTRL),A ; ChB WR5 = 82H (RTS + hunt enable?)
LD A,003H
OUT (SIOA_CTRL),A ; ChA: register = WR3
LD A,0D3H ; Rx enable, hunt, auto enables, 8-bit
OUT (SIOA_CTRL),A ; ChA WR3 = D3H (hunt mode ON)
; Wait for sync detected (RR0 bit 4 = 0 means sync found)
LD BC,02CC0H ; Timeout count
QDHS_WSYNC: LD A,010H
OUT (SIOA_CTRL),A ; ChA: reset ext/status
IN A,(SIOA_CTRL) ; Read ChA RR0
AND 010H ; Sync/hunt bit (bit 4)
JR Z,QDHS_FOUND ; Sync found! (bit cleared)
DEC BC
LD A,B
OR C
JR NZ,QDHS_WSYNC
JP QDERR_36_ENTRY ; Timeout: unformat error
QDHS_FOUND: ; Sync detected — clear hunt mode
LD A,003H
OUT (SIOA_CTRL),A ; ChA: WR3
LD A,0C3H ; Rx enable, NO hunt, auto enables, 8-bit
OUT (SIOA_CTRL),A ; ChA WR3 = C3H
; Wait for first Rx char available
LD B,09FH ; Timeout
QDHS_WRX: LD A,010H
OUT (SIOA_CTRL),A ; Reset ext/status
IN A,(SIOA_CTRL) ; RR0
AND 001H ; Rx char available?
JR NZ,QDHS_READY
DEC B
JR NZ,QDHS_WRX
JP QDERR_36_ENTRY ; Timeout
QDHS_READY: ; Set address mark check mode
LD A,003H
OUT (SIOA_CTRL),A ; ChA: WR3
LD A,0C9H ; Rx enable, addr mark check, 8-bit
OUT (SIOA_CTRL),A
; Read and discard address mark byte (A5H)
CALL QDREAD_BYTE
; Read block type byte
JP QDREAD_BYTE ; Returns A = block type
;---------------------------------------------------------------
; QDREAD_BYTE: Read one byte from QD via SIO Channel A.
; Returns: A = data byte read from port F4H.
; On error: jumps to QDERR_xx via QDSAVESP.
;---------------------------------------------------------------
QDREAD_BYTE:
PUSH BC
LD BC,08000H ; Timeout: ~32768 iterations
QDRD_BPOLL: LD A,010H
OUT (SIOA_CTRL),A ; Reset ext/status
IN A,(SIOA_CTRL) ; Read RR0
AND 008H ; DCD? (bit 3)
JR Z,QDRD_BDCD ; No DCD: not ready
IN A,(SIOA_CTRL) ; Read RR0 again
RLCA ; Bit 7 (break/abort) to carry
JR C,QDRD_BBRK ; Break detected
RRCA ; Restore
RRCA ; Bit 0 → carry (Rx available)
JR C,QDRD_BOK ; Data ready!
DEC BC ; Timeout counter
LD A,B
OR C
JR NZ,QDRD_BPOLL ; Keep polling
; Timeout — treat as end-of-tape
POP BC
JP QDERR_28_ENTRY ; Error 28H: end of tape
QDRD_BDCD: POP BC
JP QDERR_32_ENTRY ; Error 32H: no DCD
QDRD_BBRK: POP BC
JP QDERR_29_ENTRY ; Error 29H: break/CRC
QDRD_BOK: POP BC
IN A,(SIOA_DATA) ; Read data byte from F4H
OR A ; Set flags (clear carry)
RET
;---------------------------------------------------------------
; QDREAD_TRAILING: Read 3 trailing bytes (CRC), check for overrun.
; Returns: CF=1 on overrun error.
;---------------------------------------------------------------
QDREAD_TRAILING:
LD B,003H
QDRT_LOOP: CALL QDREAD_BYTE
DJNZ QDRT_LOOP
; Wait for Rx char to appear (checking for buffer clear)
QDRT_WAIT: IN A,(SIOA_CTRL)
RRCA ; Bit 0 (Rx avail) to carry
JR NC,QDRT_WAIT
; Check for Rx overrun in RR1
LD A,001H
OUT (SIOA_CTRL),A ; Register pointer = RR1
IN A,(SIOA_CTRL) ; Read RR1
AND 040H ; Rx overrun? (bit 6)
JR NZ,QDERR_29_ENTRY ; Overrun error
OR A ; Clear carry
RET
;---------------------------------------------------------------
; QDMOTOR_OFF: Turn off QD motor via SIO.
;---------------------------------------------------------------
QDMOTOR_OFF:
PUSH AF
; Channel A WR5: RTS off
LD A,005H
OUT (SIOA_CTRL),A ; ChA: register = WR5
LD A,060H
OUT (SIOA_CTRL),A ; ChA WR5 = 60H (DTR on, RTS off)
; Channel B WR5: motor off
LD A,005H
OUT (SIOB_CTRL),A ; ChB: register = WR5
XOR A
LD (QDMOTOR),A ; Motor status = off
OUT (SIOB_CTRL),A ; ChB WR5 = 00H
POP AF
RET
;---------------------------------------------------------------
; QDDELAY: Delay loop. BC = outer count, inner = 86H iterations.
;---------------------------------------------------------------
QDDELAY: PUSH AF
QDDELAY_O: LD A,086H
QDDELAY_I: DEC A
JR NZ,QDDELAY_I
DEC BC
LD A,B
OR C
JR NZ,QDDELAY_O
POP AF
RET
;---------------------------------------------------------------
; Error entry points: restore SP and return with carry set.
; These are jumped to from deep in the read routines.
;---------------------------------------------------------------
QDERR_28_ENTRY: ; End of tape / read timeout
LD A,028H
JR QDERR_RETURN
QDERR_29_ENTRY: ; CRC/overrun/break error
LD A,029H
JR QDERR_RETURN
QDERR_32_ENTRY: ; Not ready (no DCD)
LD A,032H
JR QDERR_RETURN
QDERR_36_ENTRY: ; Unformat (sync timeout)
LD A,036H
QDERR_RETURN:
LD SP,(QDSAVESP) ; Restore SP
SCF ; Set carry = error
RET
;---------------------------------------------------------------
; SIO init table: 11 bytes written to Channel A control port.
; Configures: channel reset, x1 clock sync mode, sync chars 16H.
;---------------------------------------------------------------
QDSIO_INITTBL:
DB 058H ; WR0: Channel reset + reset IUS
DB 004H ; WR0: register pointer = WR4
DB 010H ; WR4: x1 clock, 8-bit sync mode
DB 005H ; WR0: register pointer = WR5
DB 004H ; WR5: CRC-16
DB 003H ; WR0: register pointer = WR3
DB 0D0H ; WR3: Rx 8-bit, hunt mode, auto enables
DB 006H ; WR0: register pointer = WR6
DB 016H ; WR6: sync char 1 = 16H
DB 007H ; WR0: register pointer = WR7
DB 016H ; WR7: sync char 2 = 16H
ENDIF
;--------------------------------------
;
; Message table - Refer to bank 6 for
; all messages.
;
;--------------------------------------
; RomDisk, top 8 bytes are used by the control registers when enabled so dont use the space.
IF BUILD_ROMDISK+BUILD_PICOZ80 = 1
ALIGN 0EFF8h
ORG 0EFF8h
DB 0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh,0FFh
ENDIF
IF BUILD_SFD700 = 1
ALIGN 0F000H
ENDIF