; ******************************************************* ; File: DGloader.asm ; Version 2013/08/28 ; Greg Peterson ; ; The Digital Group - Z80 OS loader ; This is a listing of the contents of the EPROM on the board. ; This code loads the Z-80 OS on cassette tape into memory. ; ; Zilog mnemonics are used here, not Intel, not TDL. ; Many (most?) labels have been changed from cryptic terms like ; 'X1' to something more readable and reflective of what ; the code is actually doing. ; ; I translated the code from octal to hex, modularized it ; and heavily commented it. Hopefully my comments are ; accurate. I believe the original code was and assembled ; by hand by Dr. Robert Suding of The Digital Group, ; Denver, CO. ; ; I have placed a scan of the original source listings (in ; octal) with Herb J. I hand checked the results of assembling ; this code against the original listings, but did not check ; this code against the EPROM contents. So there is some room ; for error still. ; ; The Zilog assembler was used to assemble this code. The ; assembler runs under Windows and is free at zilog.com if ; you give them a small amount of personal info. It is a ; great tool, supports macros, importing, code libraries, ; and relocating code, so a linker comes with it. It doesn't ; actually support the Z-80, so tell it you are using an 80180 ; and don't use the extended instruction set. ; ; NOTE: THERE IS A SOFTWARE UART USED TO BIT-BANG THE SERIAL ; DATA STREAM INTO A REGISTER. THIS IS SOPHISTICATED CODE, ; DESIGNED TO TRACK FLUCTUATIONS IN TAPE SPEED AND ACCURATELY ; RECOVER DATA. IT IS HIGHLY DEPENDENT ON CPU SPEED TO TIME ; THE INDIVIDUAL DATA BIT CELLS OF THE SERIAL DATA STREAM. ; IT WILL NOT RUN ON MODERN PROCESSORS AT MODERN PROCESSOR ; SPEEDS WITHOUT SIGNIFICANT MODIFICATION. ; ; THE ORIGINAL DG Z-80 BOARD OPERATED WITH A 2.5 MHZ CRYSTAL. ; ; NOTES: ; 1. ASCII IN A DG SYSTEM HAS THE MSB SET (AS A STROBE BIT?) ; 2. THIS CODE RESIDES IN PROM FROM 0000H TO 00FFH, 256 BYTES. ; 3. THE OS LOADS FROM TAPE TO RAM FROM 0100H TO 07FFH. ; ; ******************************************************* ; ******************************************************* ; PORT DEFINITIONS CASPORT: EQU 01H ; PORT WHERE WE READ CASSETTE DATA TVPORT: EQU 00H ; PORT FOR VIDEO ; ******************************************************* ; External Locations OPMONITOR: EQU 0500H ; Z-80 OS entry point DEFAULTUSER: EQU 0600H ; User area for program developemnt DUEND: EQU 07FFH ; End of default user area ; ******************************************************* ; Public Locations - Handy bits of ROM code users may like to use. ; These addresses should never change. A jump table would have been ; a better choice. ; ; TVERASE: EQU 00E6H ; Erase the video monitor & home cursor ; TVPUTSP: EQU 00F8H ; Print a space on the Monitor ; TVPUTCH: EQU 00FAH ; Send A to the video monitor ; STARTREAD: EQU 0077H ; Read a cassette; set up TCB first ; ; ******************************************************* ; This Magic Number controls the tape read. It is called a ; Read Speed Constant, but it also is the # of times a bit ; cell is sampled to determine the value of a data bit. RSCONST: EQU 1FH ; READ SPEED CONSTANT, INITIAL VALUE ; ******************************************************* ; Lay out a "Tape control block". These storage locations ; control the reading of a cassette and are initialized ; in READSETUP: for the OS, or by the user for other tapes. ; Note that DG systems load code starting at 100H by default. ; This address is picked up at the start of the read, so ; changes made by overlaying this value have no effect. ; The end address is picked up for every byte read, and will ; reflect whatever value overlays it from the tape being read in. TCB: EQU 0117H ; EXTERNAL TAPE CONTROL BLOCK DATA ; VALUES HERE CONTROL THE TAPE LOAD TAPESPEED: EQU TCB ; lives here and is set up during init. STARTADDR: EQU TCB+1 ; Put code here ENDADDR: EQU STARTADDR + 2; Stop reading when you get here RESERVED: EQU STARTADDR + 4; A reserved byte, unused ? ; ******************************************************* ; EXTERNAL REFERENCES; THESE ARE WHERE WE GO IN THE OS TO ; HANDLE VECTORS INTO THE RESTART LOCATIONS IN PROM. RS1: EQU 0102H ; RESTART 1 RS2: EQU 0105H ; RESTART 2 RS3 EQU 0108H ; RESTART 3 RS4: EQU 010BH ; RESTART 4 RS5: EQU 010EH ; RESTART 5 RS6: EQU 0111H ; RESTART 6 RS7: EQU 0114H ; RESTART 7 NMIVEC: EQU 011DH ; NMI SIGNATURE: EQU 0100H ; OS SIGNATURE BYTES IN RAM TOS: EQU 0200H ; TOP OF USER STACK SP points to this ; ******************************************************* ; EPROM SITS AT 0000H IN MEMORY, A 1702A WITH 256 BYTES ; OF CODE, AND WE USE ALL OF IT. ORG 0000H ; ; ******************************************************* ; COMMAND. ; POWER ON RESTART COMES HERE ; Unused commands in the OS also come here by default. BEGIN: JP INITCHK ; ; ******************************************************* ; OPERATOR PROMPT TO LOAD CASSETTE, INTERSPERSED WITH THE VECTORS. ; STORAGE IS SO PRECIOUS WE PUT TEXT IN UNUSED AREAS BETWEEN VECTORS. OPMSG: ; OPERATOR MESSAGE DB 'R' + 80H ;'Read ' DB 'e' + 80H ; DB 'a' + 80H ; DB 'd' + 80H ; DB ' ' + 80H ; RESTART1: JP RS1 ; GO HANDLE THIS IN THE OS DB 'Z' + 80H ; 'Z-80 ' DB '-' + 80H ; DB '8' + 80H ; DB '0' + 80H ; DB ' ' + 80H ; RESTART2: JP RS2 ; GO HANDLE THIS IN THE OS DB 'I' + 80H ; 'INITI' DB 'N' + 80H ; DB 'I' + 80H ; DB 'T' + 80H ; DB 'I' + 80H ; RESTART3: JP RS3 ; GO HANDLE THIS IN THE OS DB 'A' + 80H ; 'ALIZE' DB 'L' + 80H ; DB 'I' + 80H ; DB 'Z' + 80H ; DB 'E' + 80H ; RESTART4: JP RS4 ; GO HANDLE THIS IN THE OS DB ' ' + 80H ; ' Cass' DB 'C' + 80H ; DB 'a' + 80H ; DB 's' + 80H ; DB 's' + 80H ; RESTART5: JP RS5 ; GO HANDLE THIS IN THE OS DB 'e' + 80H ; 'ette ' DB 't' + 80H ; DB 't' + 80H ; DB 'e' + 80H ; DB ' ' + 80H ; RESTART6: JP RS6 ; GO HANDLE THIS IN THE OS ; ********************************************* ; TUCK THE HL SIGNATURE POINTER LOAD IN HERE. INITCHK: LD HL,SIGNATURE ; LOAD HL - POINT TO OS SIGNATURE JR SIGCHK ; GO CHECK IT ; ********************************************** ; THIS RESTART HAS OPCODE 0FFH AND IS COMMONLY USED ; FOR DEBUGGING. THE OS SOFTWARE IS SET UP TO DO THIS. ORG 0038H ; RESTART7: JP RS7 ; GO HANDLE THIS IN THE OS ; *********************************************** ; CHECK FOR SIGNATURE BYTES 123 OCTAL & 123 OCTAL, ; AT 0100H AND 0101H.IF PRESENT, THE Z-80 OS IS ; PRESENT AND HOPEFULLY INTACT. ; ENTER WITH HL POINTING TO THE 1ST SIGNATURE BYTE LOCATION. SIGCHK: LD A,(HL) ; Mem => A CP 53H ; '123' OCTAL ? JR NZ,07H ; NO, OS NOT PRESENT INC L ; INC L LD A,(HL) ; MEM => A CP 53H ; '123' OCTAL ? JP Z,OPMONITOR ; YES, GO EXECUTE OS ; ************************************************** ; OS NOT LOADED, READ IT IN FROM CASSETTE TO 0100H ; THE Z-80 STACK GROWS DOWN IN MEMORY (TOWARDS 0000H) ; AS ITEMS ARE PUSHED ONTO IT. SET STACK POINTER UP HIGH. POINTERSET: LD SP,TOS ; STACK AT TOP PAGE 1 ; Note: We now have a stack & can do calls & returns WRMESSAGE: CALL TVERASE ; CLEAR TV ; POINT HL TO THE OPERATOR PROMPT MESSAGE ; "Read Z-80 Initialize Cassette" LD HL,OPMSG ; HL POINTS TO 1st block LD B,06H ; 6 BLOCKS OF CHARACTERS TO PRINT BLOCKLP: LD C,05H ; 5 CHARACTERS PER BLOCK CHARLP: LD A,(HL) ; FETCH CHAR CALL TVPUTCH ; PRINT IT INC L ; NEXT CHARACTER DEC C ; CHAR COUNTER -= 1 JR NZ,CHARLP ; ; END OF BLOCK PROCESSING INC L ; BUMP POINTER OVER RESTART JUMP INC L ; A 3 BYTE INSTRUCTION INC L ; DEC B ; NEXT BLOCK OF 5 LETTERS JR NZ,BLOCKLP ; LOOP & SEND LD HL,TCB ; POINT TO TAPE CONTROL BLOCK JR READSETUP ; JUMP OVER NMI VECTOR ; ******************************************** ; NMI VECTOR MUST GO HERE ORG 0066H ; NMI: JP NMIVEC ; GO HANDLE THIS IN THE OS ; ******************************************** ; SET UP PARAMETERS IN MEMORY THAT CONTROL THE TAPE READ ; SET SPEED CONSTANT, START & STOP ADDRESSES ; ; NOTE: These constants reside in RAM and will be overlayed ; as the OS loads. The OS constants should be the same as these. ; The start & end addresses are read once before the load begins, ; and therefore can change with impunity. The speed constant ; is read for every bit processed by INTEGRATE. The speed ; constant read from the tape will overwrite the value set ; up here and affect subsequent data loading. I find this ; worrisome, but obviously this approach works. READSETUP: LD (HL),RSCONST ; STORE READ SPEED CONSTANT INC L ; SET START ADDRESS TO 0100H LD (HL),00H ; OS STARTS HERE INC L ; MS BYTE OF ADDRESS LD (HL),01H ; INC L ; SET END ADDRESS TO 07FFH LD (HL),0FFH ; OS ENDS HERE INC L ; MS BYTE OF ADDRESS LD (HL),07H ; ; ********************************************** ; COMMAND. ; The READ command from the OS comes here to service the ; READ (tape) command. ; ; READ THE OS ON CASSETTE TAPE INTO MEMORY AT START ADDRESS ; FOR EACH BYTE READ AND STORED SUCCESSFULLY, ECHO THE LS ; DIGIT OF THE OCTAL ADDRESS TO THE SCREEN, SO IT COUNTS ; O TO 7 AND REPEATS. IF THE STORE FAILS, PRINT A '.'. STARTREAD: CASTOMEM: LD DE,(STARTADDR) ; START ADDR => DE READLP: CALL BYTERD ; READ 1 DATA BYTE FROM TAPE TO H LD A,H ; DATA BYTE => A LD (DE),A ; STORE A TO DE MEMORY POINTER LD A,(DE) ; GET IT BACK CP H ; DID THE BYTE WRITE INTO MEMORY? JR Z,GOODPUT ; YES ; CAN'T STORE DATA TO RAM, PRINT A '.' ON THE CRT BADPUT: LD A,0AEH ; A <= '.' + 80H JR SHOWPUT ; ; DATA SAFELY STORED AWAY. PRINT LS DIGIT OF OCTAL ADDRESS ON CRT. ; SO THE DISPLAY WILL CYCLE 0 .. 7 AND REPEAT AS GOOD DATA IS LOADED. GOODPUT: LD A,D ; A <= ADDRESS LSB AND 07H ; MASK OUT LS DIGIT OF OCTAL OR 0B0H ; MAKE ASCII OUT OF IT ; DISPLAY RESULTS ON CRT. SHOWPUT: CALL TVPUTCH ; PRINT CHAR ; DONE LOADING ? 16 BIT COMPARE OF MEMORY ADDRESSES LD HL,(ENDADDR) ; HL <= LOAD TO HERE INC HL ; INCLUDE END ADDR BYTE TOO INC DE ; LOADING HERE, += 1 SBC HL,DE ; HL <= HL - DE & FLAGS JR NZ,READLP ; LOAD NOT DONE YET, LOOP ; LOADING IS COMPLETE. GO RUN THE OS. JP OPMONITOR ; OP MONITOR ; ******************************************************* ; READ ONE DATA BYTE FROM TAPE & RETURN IT IN H. ; ; We begin by searching for a valid start bit of the 1st ; data byte. A long mark tone on the tape preceeds the ; 1st byte. ; ; Three samples of 0 is considered a valid start bit. ; We sample out the start bit, but ignore the value. ; INTEGRATE is called 8 times, once for each data bit, ; constructing the data byte bit by bit in register H, ; which is returned to the calling program. ; ; L = BIT COUNTER ; B = NOISE COUNTER ; H = DATA BYTE READ IN ; A = working register ; BYTERD: LD L,08H ; L <= 8 bits in a byte ; IT TAKES 3 SAMPLES OF THE START BIT AT 0 ; THAT AGREE BEFORE WE BELIEVE A START BIT ; HAS REALLY OCCURED. NOISY: LD B,03H ; NOISE COUNTER TO B STARTBITLP: IN A,(CASPORT) ; CASSETTE DATA ON BIT 0 BIT 0,A ; START BIT FOUND ? JR NZ,NOISY ; NO, WAIT FOR IT STARTMAYBE: DJNZ STARTBITLP ; COUNT 3 SAMPLES OF ZERO ; FALL THRU TO PROCESS START HAVESTART: ; START BIT IN PROGRESS CALL INTEGRATESTART ; Read the start bit LD H,00H ; Stomp on start bit BITLP: CALL INTEGRATE ; CALL FOR EACH BIT DEC L ; BITCOUNT -= 1 JR NZ,BITLP ; NOT DONE? LOOP FOR 8 BITS ; DONE WITH THIS BYTE, WRAP UP; we just ignore ; the 2 stop bits RET ; ; *************************************************** ; BUILDS DATA BYTE IN REG H, ONE BIT PER CALL. ; ; THIS CODE IS FINELY TUNED AND CRITICALLY DEPENDENT ; ON PROCESSOR SPEED TO GET THE TIMING OF EACH BIT ; CELL RIGHT. ; ; "TAPE SPEED" IS ALSO THE # of times this code samples ; each bit cell to decide if it is a zero or a one. ; ; The Speed Constant defaults to 1FH which ; means each data bit cell will be sampled 31 times by ; this code. Ideally, all 31 samples would be a 1 for a ; data bit value of 1, and 0 for a 0. If not, the ; preponderance of 1's or 0's sampled decides what bit ; value is assembled into the byte in H. ; ; Note: for 1100 baud, bit cell time is 0.909 ms. ; ; H = DATA BYTE assembled here, 1 bit per call ; B = TAPE SPEED CONSTANT AKA SAMPLE COUNT ; C = INTEGRATION CONSTANT, STARTS AT 80H ; L = bit counter from calling routine INTEGRATESTART: ; THIS ENTRY FOR START BIT LD A,(TAPESPEED) ; Decrease: Compensate FOR NOISE TEST SUB A,06H ; FOR Z80 @ 2.5 MHZ: LESS 6 JR SAMPLE ; Enter loop to read data bit INTEGRATE: LD A,(TAPESPEED) ; CASSETTE SPEED CONSTANT LD B,A ; Speed const lives in B LD C,80H ; INTEGRATION CONSTANT START VAL DATABITLP: IN A,(CASPORT) ; READ DATA BIT 0,A ; SERIAL DATA IS IN BIT 0. JP NZ,GOTA1 ; BRANCH IF A 1 GOTA0: ; 0 BIT READ INC C ; INTEGRATE += 1 JP SAMPLE ; Note: C GROWS for each 1 sample GOTA1: ; 1 BIT READ DEC C ; INTEGRATE -= 1 ; Note: C SHRINKS for each 0 sample JP SAMPLE ; EQUALIZE PATHS THRU CODE SAMPLE: ; TIME OUT DATA BIT CELL DJNZ DATABITLP ; SPEED CONSTANT -= 1 ; CHECK MSB OF INTEGRATION CONSTANT, WHICH STARTED AT 80H ; If > 80 now, more 0 samples than 1 samples were found CKICONST: BIT 7,C ; C >= STARTING VALUE: means 0's won? JP NZ,VOTE0 ; YES, vote a 0 ; ELSE fall thru & vote a 1 ; Integration constant has declined, ; more 1 samples than 0 samples. VOTE1: LD A,01H ; A <= a 1 bit JP BUILDBYTE ; Assemble bits in H ; INTEGRATION CONSTANT HAS ADVANCED, 0 VOTES HAVE WON VOTE0: LD A,00H ; A <= a 0 bit JP BUILDBYTE ; EQUALIZE PATHS THRU CODE BUILDBYTE: ADD A,H ; INSERT BIT IN A INTO BYTE IN H RRCA ; ROTATE FOR NEXT BIT ; Note: RRCA means lsb comes 1st in the data stream, right ; after the start bit LD H,A ; SAVE CURRENT BITS IN H RET ; EXIT INTEGRATE / INTEGRATESTART DB 00H ; <= DON'T KNOW WHY THIS IS HERE ; Does not appear to be used anywhere ; ************************************************** ; ERASE TV SCREEN ; PRINT 512 SPACES TO CLEAR CRT. CURSOR MOVED TO TOP LEFT CORNER. ; A, B, & C ARE CLEARED. ; ; I think a 256 byte clear loop that is called twice might be ; a better solution than the nested loops used here, but this ; is how Dr. Bob chose to solve the problem, so we leave it alone. TVERASE: LD A,0FFH ; RESET COMMAND CALL TVPUTCH ; LD B,00H ; 256 LOOPS TVELP1: LD C,02H ; 2 BYTES / LOOP TVELP2: CALL TVPUTSP ; ERASE 1 DEC C ; JR NZ,TVELP2 ; ERASE 2 DJNZ TVELP1 ; ERASE 512 X 2 RET ; ; ******************************************** ; PRINT A SPACE ON THE CRT TVPUTSP: LD A, ' ' + 80H ; ' ', A SPACE ; ******************************************** ; PRINT A CHARACTER ON THE CRT. ; CHARACTER PASSED IN A, A RETURNS CLEARED ; DATA STROBE IS ON BIT 7. TVPUTCH: OUT (TVPORT),A ; SEND CHAR XOR A ; CLEAR STROBE ON BIT 7 ? OUT (TVPORT),A ; RET ; EXIT ; NOTE: RETURN INSTRUCTION ABOVE IS AT 00FFH. ; EVERY BYTE OF ROM IS USED. END