;-------------------------------------------------------------------------------
;
;  BLOAD - BASIC program load/save for 4K Altair BASIC 3.2 and 4.0
;
;-------------------------------------------------------------------------
;
; Background information:
;
;  4K BASIC has no way to load or save programs other than through the
;  console serial port. Programs are "loaded" as a source file is received
;  through the console serial port because 4K BASIC thinks the program
;  is being typed in. A delay after each line of about 300ms is required
;  to give BASIC time to process the line just entered. At 110 or 300 baud,
;  this delay is typically achieved by punching a few NULL characters on
;  the paper tape after each CR/LF. At 9600 baud, even with a 300ms delay
;  after CR/LF, 4K BASIC will occasionally miss characters within a line.
;
;  Conversely, saving programs is a matter of using the LIST command
;  to list the program to the console device. Typically, the paper
;  tape punch on the Teletype is turned on just prior to pressing RETURN
;  for the LIST command which has the effect of punching the listed
;  program to the paper tape.
;
;--------------------------------------------------------------------------
;
; Program Operation:
;
;  This program allows 4K BASIC to load and save programs through a device
;  other than the console port and at high speed (e.g., 9600 baud) without
;  requiring delays or NULLs. Programs are saved and loaded in memory image
;  format similar to the CSAVE and CLOAD commands in 8K and Extended BASIC.
;
;  This program is typically located in PROM. To load or save a program,
;  stop the computer, examine the PROM address and depress RUN. If the
;  program space is empty (e.g., a NEW command has been executed), a BASIC
;  program is loaded. If the program space is not empty, the program in
;  memory is saved.
;
;--------------------------------------------------------------------------
;
;  Version Information
;
;  1.0	19-Jul-2013  M. DouglaS
;	Original
;
;  1.1	13-Aug-2016  M. Douglas
;	Add 88-PIO support. Include port initialization call prior to read
;	or write to allow hardware-specific initializaiton. Update header 
;       documentation and comments.
;
;  1.3	12-Apr-2018  M. Douglas
;	Add support for 2nd port on the 88-2SIO
;
;-------------------------------------------------------------------------------

;  *** Set PROM base address here ***

BASEADR	equ	0FC00h		;PROM location

; *** Select I/O port here ***

SIO01	equ	0		;SIO at 0/1
SIO67	equ	0		;SIO at 6/7 (e.g., ACR)	
S2IO10	equ	0		;2SIO at 10/11h
S2IO12	equ	1		;2SIO at 12/13h
PIO45	equ	0		;PIO at 4/5 (note, NOT 4PIO board)
SSMIO45	equ	0		;SSM IO/4 board, parallel port at 4/5

;-------------------------------------------------------------------

; I/O port defintions

 	if SIO67		;SIO at 6/7 (e.g., ACR)
IOSTAT	equ	006h		;address of i/o port status register
IODATA	equ	007h		;address of i/o port data register
RCVRDY	equ	001h		;mask bit for data received flag
XMTRDY	equ	080h		;mask bit for ready to transmit
FLAGPOL	equ	0FFh		;00=positive logic, FF=negative logic
	endif

 	if SIO01		;SIO at 0/1
IOSTAT	equ	000h		;address of i/o port status register
IODATA	equ	001h		;address of i/o port data register
RCVRDY	equ	001h		;mask bit for data received flag
XMTRDY	equ	080h		;mask bit for ready to transmit
FLAGPOL	equ	0FFh		;00=positive logic, FF=negative logic
	endif

 	if S2IO10		;2SIO at 10/11h
IOSTAT	equ	010h		;address of i/o port status register
IODATA	equ	011h		;address of i/o port data register
RCVRDY	equ	001h		;mask bit for data received flag
XMTRDY	equ	002h		;mask bit for ready to transmit
FLAGPOL	equ	000h		;00=positive logic, FF=negative logic
	endif

 	if S2IO12		;2SIO at 12/13h
IOSTAT	equ	012h		;address of i/o port status register
IODATA	equ	013h		;address of i/o port data register
RCVRDY	equ	001h		;mask bit for data received flag
XMTRDY	equ	002h		;mask bit for ready to transmit
FLAGPOL	equ	000h		;00=positive logic, FF=negative logic
	endif

 	if PIO45		;PIO at 4/5
IOSTAT	equ	004h		;address of i/o port status register
IODATA	equ	005h		;address of i/o port data register
RCVRDY	equ	002h		;mask bit for data received flag
XMTRDY	equ	001h		;mask bit for ready to transmit
FLAGPOL	equ	000h		;00=positive logic, FF=negative logic
	endif

 	if SSMIO45		;SSM IO/4 PIO at 4/5
IOSTAT	equ	004h		;address of i/o port status register
IODATA	equ	005h		;address of i/o port data register
RCVRDY	equ	002h		;mask bit for data received flag
XMTRDY	equ	001h		;mask bit for ready to transmit
FLAGPOL	equ	0FFh		;00=positive logic, FF=negative logic
	endif

; 4K BASIC Equates

pBASE32	equ	0165h		;pointer to start of program v3.2
vBASE32	equ	0167h		;pointer to start of variables v3.2
pbase40	equ	0175h		;pointer to start of program v4.0
vbase40	equ	016Fh		;pointer to start of variables v4.0

;  Misc Equates

SYNCCHR	equ	0D2h		;sync character (3 in a row written)
VER32	equ	32
VER40	equ	40

	org	BASEADR
;------------------------------------------------------------------------
;
;  Initialize - Figure out if we're running under ver 3.2 or 4.0 and 
;     load the appropriate program pointer into hl and de. The version
;     number is saved in c.
;
;------------------------------------------------------------------------

	call	portIni		;initalize I/O port

; If the MSB of pBASE32 is 0C or 0D, then assume we're running ver 3.2	

	mvi	c,VER32		;c = assume version 3.2
	lhld	pBASE32		;hl->program base for 3.2

	mov	a,h		;see if msb is 0C or 0D
	sui	0Ch		;msb = 0C?
	jz	isVer32

	dcr	a		;msb = 0D?
	jz	isVer32		;no, default to ver 3.2

	mvi	c,VER40		;c = ver 4.0
	lhld	pBase40		;hl->program base for 4.0

isVer32	mov	d,h		;de->program base address as well
	mov	e,l

;  Determine if we're loading or saving. If a NEW has been done
;     (e.g., no program present), do a program load. Otherwise, do a save. 

	mov	a,m		;if 1st two bytes are zero, no program present
	inx	h
	ora	m
	dcx	h		;restore program pointer
	jz	doLoad

;------------------------------------------------------------------------
;
;  Save program
;    Three sync bytes are written followed by the BASIC
;    program. The first two bytes of each line of code contain the
;    address of the next line of code. The program base address is
;    subtracted from each of these linked-list addresses so that the
;    program can be loaded later at what may possibly be a different
;    base address. 
;
;  On Entry
;    C = BASIC Version number
;
;------------------------------------------------------------------------
	mvi	b,SYNCCHR	;write 3 sync characters
	call	xmtChar
	call	xmtChar
	call	xmtChar

;  If ver 4.0, there is a leading zero that is not present in 3.2.
;     Don't send the leading zero.

	mov	a,c		;see if version 3.2 or 4.0
	cpi	VER32
	jz	xmtLine		;v3.2

	inx	h		;skip leading 0 for 4.0
	inx	d		;offsets ignore leading zero as well

;  xmtLine - First, send the linked-list word at the start of the line. If
;     the address is zero, we're done. Otherwise, subtract the program
;     base address (in de) to convert the linked-list addresses to relative
;     offsets.

xmtLine	mov	a,m		;see if the next link address is zero
	inx	h
	ora	m
	jz	xmtDone		;zero link = end of program

	dcx	h		;restore program pointer

	mov	a,m		;subtract lsbs first
	sub	e		;base address in de
	mov	b,a		;b = lsb of difference

	inx	h		;do msb
	mov	a,m
	sbb	d
	mov	c,a		;c = msb of difference

	call	xmtChar		;send lsb of relative link (from b)
	mov	b,c
	call	xmtChar		;send msb of relative link

;  Send 2 byte line number
		
	inx	h		;point to next byte (line number)
	mov	b,m
	call	xmtChar		;lsb of line number

	inx	h		;send msb of line number
	mov	b,m
	call	xmtChar

; xmtLoop - send the line of code after the line number. The line is 
;    terminated in memory by a zero.

	inx	h		;hl->1st byte of code for current line

xmtLoop	mov	b,m		;get next character and send it
	inx	h
	call	xmtChar
	jnz	xmtLoop		;loop until byte sent is zero

	jmp	xmtLine		;back to do the next line.

; xmtDone - Send the last two bytes (they're both zero) and exit.

xmtDone	mov	b,a		;put zero in b (a came in zero)
	call	xmtChar
	call	xmtChar
	jmp	0		;back to BASIC

;------------------------------------------------------------------------
;
;  Load program. Wait for three sync bytes, then read the program
;     into memory. The linked-list of program line addresses is
;     converted from a relative offset to absolute address by adding
;     the program base address in de to the incoming offset. When
;     all bytes have been written, the variable base address in
;     BASIC is updated to point to the end of the program+1
;
;  On Entry
;     C = BASIC Version Number
;
;------------------------------------------------------------------------
doLoad	mvi	b,3		;need three sync bytes in a row

wtSync	call	rcvChar
	cpi	SYNCCHR
	jnz	doLoad		;start over

	dcr	b
	jnz	wtSync		;wait for 3 sync characters in a row

;  In ver 4.0, there is a leading zero that is not present in 3.2, but it
;     it was not written when saved. Put the leading 0 into program memory.

	push	b		;save version number (in c) for later
	mov	a,c		;see if version 3.2 or 4.0
	cpi	VER32
	jz	rcv1st		;v3.2, so no leading zero

	xra	a		;store a leading zero
	mov	m,a
	inx	h
	inx	d		;offsets ignore leading zero as well

rcv1st	call	rcvChar		;receive linked-list pointer into bc
	mov	c,a		;lsb
	call	rcvChar
	mov	b,a		;msb

;  rcvLine - bc contains the relative linked-list value. Add the program
;    base address in de to convert the offset into an absolute address and
;    store in program memory.

rcvLine	mov	a,c		;compute offset+pgm base, lsb first
	add	e		;add lsbs 1st
	mov	m,a		;store lsb in program memory

	inx	h		;add msbs
	mov	a,b
	adc	d
	mov	m,a		;store msb in program memory

; Receive the 2 byte line number
		
	inx	h		;lsb of line number
	call	rcvChar
	mov	m,a

	inx	h		;msb of line number
	call	rcvChar
	mov	m,a

; rcvLoop - loop receiving the characters of a line of code. BASIC terminates
;    the line with a zero.

	inx	h		;hl->1st byte of code for current line

rcvLoop	call	rcvChar		;get program byte
	mov	m,a		;store the byte
	inx	h
	jnz	rcvLoop

; Get the linked-list bytes for the next line in bc. If both zero, then
;    the program is completed.

	call	rcvChar		;receive linked-list bytes into bc
	mov	c,a		;lsb

	call	rcvChar
	mov	b,a		;msb
	ora	c		;both bytes zero?
	jnz	rcvLine		;no, do another line

; Write out the last two null bytes. 

	mov	m,a		;write last two zeros
	inx	h
	mov	m,a
	inx	h		;hl now points to varbase

; Write the new varBase based on the BASIC version
	
	pop	b		;get version back in c
	mov	a,c
	cpi	VER32
	jz	rVer32		;version 3.2

	shld	vBase40		;else, version 4.0
	jmp	0		;back to BASIC

rVer32	shld	vBASE32
	jmp	0

;------------------------------------------------------------------------
;
;  Port I/O
;
;  xmtChar - transmit character in B, return char in A and B
;  rcvChar - receive character and return in A
;  
;  On Exit, Z flag set if data byte = 0
;
;------------------------------------------------------------------------

; xmtChar - transmit character passed in b. Clobbers a

xmtChar	in	IOSTAT		;read status register
	xri	FLAGPOL		;force positive polarity
	ani	XMTRDY		;get xmit ready bit alone
	cpi	XMTRDY		
	jnz	xmtChar		;loop until ready

	mov	a,b
	out	IODATA		;transmit the character
	ora	a		;return with z bit set if data=zero

	ret

; rcvChar - receive and return character in a

rcvChar	in	IOSTAT		;read status register
	xri	FLAGPOL		;force positive polarity
	ani	RCVRDY		;get receiveready bit alone
	cpi	RCVRDY		
	jnz	rcvChar		;loop until ready

	in	IODATA		;read the character into a
	ora	a		;return with z bit set if data=zero

	ret
	
; portIni - do required hardware initialization of the I/O port. A read
;    of the data register is done to clear any possible garbage (especially
;    for SIO and PIO boards) and to reset handshake state to idle (PIO). 

portIni	equ	$

 if S2IO10 or S2IO12
	mvi	a,3		;reset ACIA
	out	IOSTAT
	mvi	a,11h		;/16, 2 stop bits
	out	IOSTAT
 endif

	in	IODATA		;clear possible garbage
	ret

	end
