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>
1424 lines
62 KiB
NASM
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
|