;***************************************************************
;* UBMON - UNIVERSAL BOOT/MONITOR          BY MARTIN EBERHARD  *
;*                                                             *
;* UBMON a 256-byte PROM monitor for use with the Altair 8800b *
;* Turnkey Module or the 88-PMC PROM card. UBMON is very       *
;* similar in operation to the Altair TURMON PROM monitor,     *
;* with the addition of 3 commands to boot from the various    *
;* Altair boot devices, including floppy disks, tapes, and     *
;* hard disk. UBMON provides the following functions:          *
;*                                                             *
;*  M xxxxxx  Memory Examine and Modify: Allows you to examine *
;*            and change the contents of memory starting at    *
;;*           address xxxxxx octal                             *
;*                                                             *
;*  D xxxxxx yyyyyy   Memory Dump: Dumps memory contents in    *
;*            Altair binary punch format from address xxxxxx   *
;*            to address yyyyyy, inclusive (octal addresses)   *
;*                                                             *
;*  J xxxxxx  Jump To Adddress xxxxxx octal                    *
;*                                                             *
;*  B         Boot from floppy: Requires the DBL, the MDBL, or *
;*            the CDBL PROM at address FF00h                   *
;*                                                             *
;*  T         Boot from Tape or Load Tape File: Requires the   *
;*            MBL PROM at address FE00h                        *
;*                                                             *
;*  L         Boot from Hard Disk: Requires a hard-disk PROM   *
;*            (e.g. HDBL) at address FC00h                     *
;*                                                             *
;* Differences compared to TURMON functionality:               *
;* 1) UBMON's 'D' command's leader is a string of 60 bytes of  *
;*    20h, followed by a string of 60 nulls, while TURMON      *
;*    punches a string of 60 bytes of 0Dh followed by a string *
;*    of 60 nulls.  Both of these allow MBL to load the file   *
;*    by fooling it to think it is skipping a checksum loader. *
;* 2) UBMON's 'D' command also follows the memory dump with a  *
;*    trailer of 60 nulls, while TURMON prints no trailer.     *
;*                                                             *
;* As with TURMON, the Terminal for UBMON is a 6850-based      *
;* serial port at address 020 octal (10h). An 88-2SIO, as well *
;* as the serial ports on the Turnkey Monitor and the 88-UIO,  *
;* are all suitable.                                           *
;*                                                             *
;* Also like TURMON, UBMON requres RAM for its stack. Several  *
;* assembly options support various memory and PROM board      *
;* configurations.                                             *
;*                                                             *
;* This monitor provides subroutine compatibility with TURMON  *
;* at the following entry points:                              *
;*                                                             *
;* Address  Name   Function                                    *
;*  FD00h  MONTOR  Cold-start entry to the monitor             *
;*  FD08h  ENTER   Warm start, does not init ACIA              *
;*                 Result in A. All regs except F preserved.   *
;*  FDF2h  XOUTCH  Print A. All regs except F preserved.       *
;*  FDCFh  XPRNT3  Print value in H in octal, followed by a    *
;*                 space. Trashes AF,BC,HL.                    *
;*  FDE3h  XSPACE  Print a SPACE. Trashes AF.                  *
;*  FDE8h  XINCH   Get, strip parity & echo a character        *
;***************************************************************

; REVISION HISTORY
;
; Ver. 1.00  12 SEP 2013 M.Eberhard
;   Created from MITS TURMON
;
; Ver. 1.01  16 SEP 2013 M. Eberhard
;   Hosed version. Released only for about 1 hour - hopefully
;   no distribution.
;
; Ver. 1.02  17 SEP 2013 M. Eberhard
;   Fixed. Improved comments. Squeeze code more, and add trailer
;   after 'D' command. Set leader and trailer to 60 bytes
;
; Ver. 1.03  17 AUG 2014 M. Eberhard
;   Punch 60 bytes of 20h before leader, so that file can also
;   be loaded by MITS's MBL loader PROM. This required some
;   further code compression.

;***************************************************************
;* NOTES                                                       *
;*                                                             *
;* The assembler will not check for overlapping code. If you   *
;* make any changes, be sure to check that your new code does  *
;* not overlap fixed-location subroutines near the end.        *
;*                                                             *
;* Forcing this code to fit into one 256-byte PROM required    *
;* making some assumptions about the address of this code, as  *
;* well as the addresses of the  DBL, MBL, and HDBL PROMS. if  *
;* you change the address of any of the PROM programs, then    *
;* this program will require some changes. Fortunately, the    *
;* addresses of these PROMs haven't changed in 38 years.       *
;*                                                             *
;* Apologies for the somewhat convoluted code. Squeezing an    *
;* additional 3 commands (on top of TURMON's 3 commands) into  *
;* a 256-byte PROM space, while also keeping the externally-   *
;* accessible subroutines at their historical addresses        *
;* required some twisted code. Hopefully my comments will help *
;* explain how it works. Be careful if you change this code.   *
;*                                                -M. Eberhard * 
;***************************************************************
;-------------------------------------
;Stack address options
; (All but one must be commented out)
;-------------------------------------
STACK	equ	0FC00h		;UBMONt Turnkey board 1K RAM
;STACK	equ	0F800h		;UBMONp Last RAM before 88-PMC
;STACK	equ	0E000h		;UBMONb RAM before 8K Bytesaver

; Program Equates

LDRLEN	equ	60		;'D' cmd leader/trailer length
PROMPT	equ	'.'		;Command prompt
ERRMSG	equ	'?'		;1-byte error message

; Terminal port equates - same for 88-2SIO port 0, Turnkey
; Module, and 88-UIO (all based on the Motorola 6850 ACIA)

ACCTRL	equ	10h		;ACIA Control output port
ACSTAT	equ	10h		;ACIA Status input port
ACTXD	equ	11h		;ACIA TX Data register
ACRXD	equ	11h		;ACIA RX Data register

ACRST	equ	00000011b	;Master reset
ACRDF	equ	00000001b	;RX Data register full
ACTDE	equ	00000010b	;TX Data register empty
ACINIT	equ	00010001b	;/16, 8bit, No Parity, 2Stops

; Altair File Equate

LBSYNC	equ	3CH	;Altair file Load block synch chr

; Fixed locations in UBMON, for TURMON compatibility
; MONTOR must be FD00h, because FDh is also -3.

MONTOR	equ	0FD00h		;Execution beginning
XPRNT3	equ	0FDCFh		;Print h in octal on Terminal
XSPACE	equ	0FDE3h		;Print a space on the Terminal
XINCH	equ	0FDE8h		;Get Terminal chr in A
XOUTCH	equ	0FDF2h		;Print A on Terminal

; Code enrty points in external PROMs. The low address byte of
; these all must be 00. FBOOT must be 100h greater than TBOOT.

HBOOT	equ	0FC00h		;HDBL boot ROM
TBOOT	equ	0FE00h		;MBL multi-tape boot ROM
FBOOT	equ	0FF00h		;DBL or MDBL floppy boot ROM

; ASCII characters

CR	equ	0Dh
LF	equ	0Ah

;*****************
;* Start of Code *
;*****************
	ORG	MONTOR

;--------------------------
; Initialize Terminal ACIA
;--------------------------
	mvi	a,ACRST
	out	ACCTRL
	mvi	a,ACINIT
	out	ACCTRL

;===Main Loop=========================================
;Get Command a command and dispatch.
; This portion of the code is particulary
; convoluted, to keep it short. Here we assume:
;    high(MAIN)=FDh
;    high(TBOOT)=FEh
;    low(HBOOT)=low(TBOOT)=low(FBOOT)=0
;    high(FBOOT)=high(TBOOT)+1
;    low(EXTCMD)<=FDh
;=====================================================
MAIN:	lxi	sp,STACK	;create stack
	lxi	b,MAIN		;create return address
	push	b		;..also set b=high(EXTCMD)

; Print prompt on a new line

	call	PCRLF		;d=0ah, e=0dh
	mvi	a,PROMPT
	call	PRINTA

	lxi	h,HBOOT		;Entry address for HDBL PROM
				;also l=0 for GETADR

; Get user input and dispatch command
; On Entry:
;  b = high(EXTCMD) = FDh = -3
;  d = 0Ah (10)
;  e = 0Dh (13)
; hl = HBOOT = FC00h
;  l = 0

	call	GETCHR		;Get user command

	sui	'J'		;Jump command?
	jz	DOJCMD

	add	b		;(b=FDh=-3) 'M' command?
				;c<FDh, and l=0 here
	cz	GETADR		;M: get hl=addr from user, set Z
	jz	DOMCMD		;M: do M command

; The next 3 commands all go through EXTCMD. Code is
; shorter if we use conditional returns, rather than
; conditional jumps.
; On Entry:
;  a = user input - 'J' - 3 = user input - 'M'
;  b = high(EXTCMD)
;  d = 0Ah (10)
; hl = HBOOT = FC00h
;  l = 0

	mvi	c,EXTCMD AND 0FFh ;bc=EXTCMD
	push	b		;prepare for conditional returns

	inr	a		;'L' command?
				;hl=HBOOT here
	rz			;L: jump to EXTCMD

;  a = user input - 'J' - 3 + 1 = user input - 'L'
;  d = 10
;  l = 0

	mvi	h,FBOOT/256	;entry address for DBL/MDBL PROM
	add	d		;d=10 = -('B'-'L')
				;Boot from floppy cmd?

	rz			;B: jump to EXTCMD

;  a = user input - 'J' - 3 + 1 + 10 = user input - 'B'
; hl = FBOOT = FF00
;  l = 0

	dcr	h		;hl=TBOOT: address for MBL PROM
	cpi	('T'-'B')	;boot from tape cmd?
	rz			;T: jump to EXTCMD

	pop	b		;done with EXTCMD commands

;  a = user input - 'J' - 3 + 1 + 10 = user input - 'B'
; hl = TBOOT = FE00h
;  h = FEh = -2
;  l = 0

	add	h		;'D' Dump memory cmd?
	rnz			;anything else is invalid

;===Command Routine===============================
; Process D (dump memory) Command
; Punch specified address range as
; an Altair-style data file
; on entry:
;   c=low(EXTCMD) < 0FEh 
;   l=0
;=================================================

; Get the start & end addresses from the user

	mov	e,l		;e=0 too, for 2nd GETADR
	call	GETADR		;Get start addr, print space
	xchg			;de=start address
	call	GETADR		;Get hl=end address
				;returns with a=20h

	inx	h		;hl = one past end address

; Punch a pre-leader so that MITS's MBL can load this file
; a=20h here.

	mov	b,a		;punch 20h as the pre-leader
	call	LEADER		;returns b=0

; Punch null leader

	xra	a		;Punch null leader
	call	LEADER		;returns with b=0

; Loop to punch all the requested data
; (b=0 here, both on initial entry and upon looping)

; Compute b=data byte count of the next block, max=255

NXTBLK:	dcr	b		;b=FFh=255

	mov	a,l		;compute least sig byte
	sub	e
	mov	c,a		;save least sig byte
	mov	a,h		;compute most sig byte
	sbb	d		;>255 bytes remaining?
	jnz	BLKSIZ		;y: then do 255 bytes
	mov	b,c		;n: byte count = lsb
BLKSIZ:

; Punch the the block header info:
; sync chr, byte count, & 2-byte load address
;  b = block size
; de = starting memory address for block data
; hl = last address of file + 1

	push	d		;save load address

	mvi	e,LBSYNC	;Punch load-block sync chr
	mov	d,b		;and block byte count	
	call	PRNTDE

	pop	d		;restore load address
	call	PRNTDE		;Punch de=load address
				;ends with a=d
	add	e		;a=checksum of the address

; Punch b bytes of block data, computing checksum as we go
;  a = checksum so far
;  b = block size
; de = starting memory address for block data
; hl = last address of file + 1

BDATLP:	mov	c,a		;temp save checksum
	ldax	d		;get memory data
	call	PRINTA		;...and punch it

	add	c		;update checksum

	inx	d		;Next address
	dcr	b		;Loop 'til done with block data
	jnz	BDATLP		;ends with b=0

; a = block checksum
; b = 0

	call	PRINTA		;Punch the block checksum

; Continue until all the data has been punched
;  b = 0
; de = next address to punch
; hl = last address of file + 1
; Test for de<hl, meaning there are more bytes to punch

	mov	a,e		;compute de-hl
	sub	l
	mov	a,d
	sbb	h		;set carry if hl>de
				;ends with hl=de so a=0

	jc	NXTBLK		;Y: Do another block

; Fall into LEADER (with a=0) to punch the trailer

;---Subroutine---------------------
; Punch a leader
; On Entry:
;   a = leader character
; On exit:
;   b=0
;   all other registers preserved
;----------------------------------
LEADER:	mvi	b,LDRLEN	;leader length

LEADLP:	call	PRINTA
	dcr	b
	jnz	LEADLP		;ends with b=0

	ret

;===Command Routine==================================
; Process External Command (B,L,T), only if the PROM
; for that command appears to exist
; Note: this assumes ROM - i.e. writing does
; not effect contents.
; On entry:
;   hl=start address of external command
; On branch to external command:
;   Top of Stack = MAIN (for potential return)
;====================================================
EXTCMD:	inr	m		;Does location = FF?
	rz			;Y:ignore command

; Fall into DOJCMD, with Z cleared

;===Command Routine==================================
; Process J (jump) Command
; On Entry:
;   C=low(EXTCMD) << 0FFh 
;   l=0
;   Z flag set
;====================================================
DOJCMD:	cz	GETADR		;Get jump address if J
	pchl			;and go there
	
;===Command Routine==================================
; Process M (Memory examine and edit) Command
; On Entry at (DOMCMD):
;   hl = start address from GETADR
;====================================================
MMODLP:	inx	h		;loop for next address

DOMCMD:	call	PCRLF		;on a new line

	push	h		;save memory address

; Print address (in hl) and data (at memory address hl)

	call	PADRDA		;sets l=0 too

; Get user data, carry clear if space (means don't change data)

	call	GETDAT		;l=user data, carry means none
	mov	a,l		;a=8-bit user data

; Prepare to use conditional returns, recover memory address

	lxi	h,MMODLP	;addr for conditional returns
	xthl			;..recover hl=memory address
	rnc			;no carry means user typed space

; Modify memory, and test it

	mov	m,a		;write new data
	cmp	m		;Verify write
	rz			;Next address if okay

; Fall into BADINP to report memory-write failure

;---Subroutine Abort----------------------------
; Bad input from user or memory write failure.
; Print error message and start over. There is
; junk on the stack, which MAIN will repair.
;-----------------------------------------------
BADINP:	mvi	a,ERRMSG	;Error message
	call	PRINTA
	jmp	MAIN

;---Subroutine--------------------
; Print Carriage Return/Line Feed
; On Exit:
;   d=0Ah
;   e=0Dh
; trashes a
;---------------------------------
PCRLF:	lxi	d,LF*256+CR	;load de with CR LF

; Fall into PRNTDE

;---Subroutine---------------
; Print E then D
; On Exit:
;   a=d
;----------------------------
PRNTDE:	mov	a,e
	call	PRINTA
	mov	a,d
	jmp	PRINTA

;---Subroutine----------------------------------------------
; Get 3 or 6 octal digits from user, and then print a space.
;
; Entry at GETADR gets a 6-digit address
; Entry at GETDAT gets a 3-digit data value
; A space typed at any time terminates the get
; with all upper digits defaulting to 0.
; On Entry:
;  c<FEH so that entry at GETADR will not bump B
;  l=0
; On Exit:
;   a = 20h (space character)
;   l = 8-bit binary value of input
;  hl = 16-bit binary value of input
;   b trashed.
;   c incremented only for entry at GETADR.
;   Z flag set
;   Carry flag cleared if user typed a space
;-----------------------------------------------------------
GETADR:	db	06	;Entry here: '0606 mvi b,06'
			;            '03   inx b' (increments c)

GETDAT:	db	06	;Entry here: '0603 mvi b,03'
	db	03	

	mov	h,l		;hl=0 init value for <6 digits

GETNXT:	call	GETCHR		;get a digit, Z set if space
	rz			;return with carry clear if space

	sui	'0'		;subtract ASCII offset
	cpi	8		;valid octal digit?
	jnc	BADINP		;N: error	

	dad	h		;shift new value into hl
	dad	h
	dad	h
	ora	l		;install new digit
	mov	l,a

	dcr	b		;more digits to get?
	jnz	GETNXT		;Y: go get it.

	stc			;carry set: complete input
	jmp	PSPACE		;follow with a space
				;preserve psw

;---Subroutine--------------------------------------------
; Print address in hl, a space, and then the data at (hl)
; On Exit:
;   a=20h (space)
;   b=0
;   c=memory data
;   h=memory data
;   l=0
;---------------------------------------------------------
PADRDA:	mov	c,m		;remember memory data

; Print address in hl as 6 octal digits

	mvi	b,6		;6 digits in PR6OCT
	xra	a		;initial value for PR6OCT
	call	PR6OCT		;Print HL in octal
				;returns with h=c

; Fall into PR3OCT to Print data in h=c as 3 octal digits

;**Fixed-Location Externally Accessable Subroutine**************
;
; Print octal digits followed by a space
;
; Entry at PR3OCT will print 3 octal digits from h.
; The first digit is from the 2-bit value in the bits 7:6 of c.
; Entry at PR6OCT will print 6 octal digits from hl.
; the first digit is from the 1-bit value in the bit 7 of h.
; Subsequent digits are 3-bit values in decending order from hl.
;
; On Entry at PR3OCT:
;   h = value to print
;
; On Entry at PR6OCT:
;   b = 6 (digit count)
;   a = 0
;   hl = value to print
;
; On Exit:
;   a=20h (space)
;   b=0
;   h=c
;   l=0
;***************************************************************
	org	XPRNT3

; Entry for 8-bit value: 1st digit shifts only twice

PR3OCT:	xra	a		;initial value
	mvi	b,3		;3 digits

OCTLUP:	dad	h		;2nd and 3rd shifts
	ral

; Entry for 16-bit value: 1st digit shifts only once

PR6OCT:	dad	h
	ral

	adi	'0'		;Convert to ASCII

	call	PRINTA		;Print digit
	xra	a		;start new digit

	dad	h		;1st shift for next digit
	ral

	dcr	b		;More digits?
	jnz	OCTLUP		;N: the next digit is 3 bits

	mov	h,c		;get memory data
				;(this is doen here to use
				;..a memory location.)
; Fall into PSPACE

;**Fixed-Location Externally Accessable Subroutine**************
;
; Print a space
;
; On Exit:
;   a = space = 20h
;   all other registers preserved
;***************************************************************
	org	XSPACE

PSPACE:	mvi	a,' '
	jmp	PRINTA

;**Fixed-Location Externally Accessable Subroutine**************
;
; Get & echo a character from the Terminal
;
; On Exit:
;    a = received character, with parity stripped
;    Z is set if the character is a space
;***************************************************************
	org	XINCH

GETCHR:	in	ACSTAT	;Wait for chr available
	rrc		;test bit 0=S2DS1
	jnc	GETCHR

	in	ACRXD	;Get the chr
	ani	7Fh	;strip parity

;  Fall through to XOUTCH to echo

;**Fixed-Location Externally Accessable Subroutine**************
;
; Print A on the Terminal
;
; (The cpi below is here to make to do the routine the right
; length, doing a test that we need for GETCHR anyway.)
; On Exit:
;    Z set if printed chr is a space
;    All other registers preserved
;***************************************************************
	org	XOUTCH

	cpi	' '	;is it a space?

; Fall into PRINTA to print

;---Subroutine---------------
; Print A on the Terminal
; All registers preserved
;----------------------------
PRINTA:	push	psw	;temp save chr

PAWAIT:	in	ACSTAT	;Wait for ACIA TX to be ready
	ani	ACTDE
	jz	PAWAIT

	pop	psw	;recover chr, flags
	out	ACTXD	;send chr

	ret		;with chr in a

	END
