	.LF NULL		; turn off screen output
;
;	Acorn monitor for Acorn System 6809 CPU card
;
;	ISSUE 1 October 2025
;
;	This program handles a memory mapped VDU, encoded keyboard,
;	cassette interface, parallel printer, and mini-floppy bootstrap.
; 
; Formatted for the SB-Assembler (https://www.sbprojects.net/sbasm/)
; (set tabs to 8 in your editor for correct formatting)
;
; Chris Oddy
;
	.CR	6809
;
				; ****** ASSEMBLY TIME VARIANTS ******
;
; This source can be used to build one of several variants of the Acorn 6809 Monitor:
;
; The original, as listed in the 'Acorn 6809 User's Manual' is intended to be used with the 40-column Teletext VDU board.
; In this arrangement the hardware (VIA, VDU and optional FDC) are located in low memory ($0800+) as in a 6502 based System Computer.
; The Monitor occupies 2K bytes at address $F000, it uses RAM in page zero for its workspace, RAM from $2000+ being available for user programs.
;
; For FLEX a different arrangement is required, the hardware is moved up to $E800+ and the Monitor's workspace moves to $E000.
; FLEX is then usually loaded from disk to $C000, $0000 onwards is then available for user programs.
;
; The original Acorn listing is identified as version V49 (dated Feb 1980), I assume this is actually V4.9.
; I also have what must be a later version with various small modifications throughout, though this doesn't seem to work ?
; I have called this version 4.95 because there is also a version 5.2 from Control Universal.
; This must be from after they took over the Eurocard business from Acorn so I'm assuming they may have started at version 5.0 !
;
; The Control Universal Version is quite different because it works with their versions of FDC and VDU boards which are not compatible
; with the original Acorn boards so for the moment at least it is not included in this source code.
; 
; A version, which I have called 4.92, is a modified version of the Acorn monitor which has had some small changes made to the boot loader to make it work correctly.
;
; In the following section you select the VERsion, BASE address, VDU type and BAUD rate for the build.
;
; To build the original Acorn Monitor as listed in the manual choose VER=490, VDU=40, BASE=$0000 and BAUD=205
; To build a version that will run Flex choose VER=492, VDU=80, BASE=$E000 and BAUD=205
;
				; *** Change the value assigned to VER to select the version you want: ***
;
VER	.EQ	495		; 490 for original Acorn version 4.9
				; 492 for Flex version
				; 495 for updated version 4.95 (though it doesn't seem to work ?)
				; 520 for Control Universal Version (not here yet !)
;
; You can then select a build for either the System or FLEX configurations by changing the BASE memory address:
;
BASE	.EQ	$E000		; $0000 with I/O in low memory for Acorn System computer setup
				; or $E000 with I/O in high memory for FLEX setup
;
; Each build can also be assembled for either the 40 or 80-column VDU Boards
;
; There are two configuration variants for the 40-column, either as original in the Acorn manual(40)
; or an alternative which matches System COS/DOS40B (41)
;
; The 80-column configuration matches System COS/DOS80B
;
VDU	.EQ	80		; 40 or 41 for 40-column Teletext VDU
				; 80 for 80-column VDU
;
; And finally you can also change the initial baud rate used for the cassette interface:
;
BAUD	.EQ	205		; 564 for 110 baud ($0234)
				; 205 for 300 baud ($00CD)
				; 48 for 1200 baud ($0030)
;
				; *** Check for assembler switch option errors ***
	.DO	VER=520
		.ER	F,That version is not yet avaliable !
		.FI
;
	.DO	VER<>490
	.DO	VER<>492
	.DO	VER<>495
		.ER	F,Invalid VER Option !
	.FI
	.FI
	.FI
;
	.DO	BASE<>$0000
	.DO	BASE<>$E000
		.ER	F,Invalid Base Option !
	.FI
	.FI
;
	.DO	VDU<>40
	.DO	VDU<>41
		.DO	VDU<>80
			.ER	F,Invalid VDU Option !
	.FI
	.FI
	.FI
;
				; ****** Select Assembly Output Files ******
;
	.DO	BASE=$0000
		.DO	VDU=40
				; Building 6809 Monitor for low memory and Teletext VDU original configuration
			.TF	Monitor40.bin,BIN
			.LF	Monitor40.list
		.FI
		.DO	VDU=41
				; Building 6809 Monitor for low memory and Teletext VDU alternative configuration
			.TF	Monitor41.bin,BIN
			.LF	Monitor41.list
		.FI
		.DO	VDU=80
				; Building 6809 Monitor for low memory and 80-Column VDU
			.TF	Monitor80.bin,BIN
			.LF	Monitor80.list
		.FI
	.FI
;	
	.DO	BASE=$E000
		.DO	VDU=40
				; Building 6809 Monitor for Flex and Teletext VDU original configuration
			.TF	Flex40.bin,BIN
			.LF	Flex40.list
		.FI
		.DO	VDU=41
				; Building 6809 Monitor for Flex and Teletext VDU alternative configuration
			.TF	Flex41.bin,BIN
			.LF	Flex41.list
		.FI
		.DO	VDU=80
				; Building 6809 Monitor for Flex memory and 80-Column VDU
			.TF	Flex80.bin,BIN
			.LF	Flex80.list
		.FI
	.FI
;
; ********************************************************
;
MPROM	.EQ	$F800			; Normal monitor position at top of memory
MONDP	.EQ	BASE+$0300/256		; Direct page for monitor
;
; VDU Configuration
ROWS	.EQ	25			; Number of rows on display.
COLS	.EQ	VDU&$F8			; Number of characters per row, 40 or 80
PSIZE	.EQ	VDU&$F8/10*256		; Size of display memory in total, $0400 (1K) or $0800 (2K)
PAGHI	.EQ	12			; Page address register, high byte
CURHI	.EQ	14			; Cursor address register, high byte
;
	.DO	VDU&$F8=40		; Location of memory that 40-Column VDU uses:
VPAGE	.EQ	BASE+$400		;	Standard $0400
					;	Flex $E400
;
					; Location of CRT controller on 40-column VDU card:
CRTC	.EQ	BASE+$0800		;	Standard $0800
	.FI				;	Flex $E800
;
	.DO	VDU=80			; Location of memory that 80-Column VDU uses:
VPAGE	.EQ	BASE+$1000		;	Standard $1000
					;	Flex $F000
;
					; Location of CRT controller on 80-column VDU card:
	.DO	BASE=$0000
CRTC	.EQ	$1840			;	Standard $1840
	.FI
	.DO	BASE=$E000
CRTC	.EQ	$E840			;	Flex $E840
	.FI
	.FI
;
WRPMSK	.EQ	PSIZE/256-1		; Mask to wrap address within PSIZE, 3 (40-column) or 7 (80-column)
;
DRIVE	.EQ	$40			; Drive to bootstrap from - Drive 0 $40, Drive 1 change to $80
FLOPY	.EQ	BASE+$0A00		; Location of floppy disc controller:
					;	Standard $0A00
					;	Flex $EA00
FDCC	.EQ	FLOPY			; Command register
FDCS	.EQ	FDCC			; Status register
FDCP	.EQ	FLOPY+1			; Parameter register
FDCR	.EQ	FDCP			; Result register
FDRST	.EQ	FLOPY+2			; Reset register
FDCD	.EQ	FLOPY+4			; Data register
;
KVIA	.EQ	BASE+$0980		; Location of Versatile Interface Adaptor
KORB	.EQ	KVIA+$0			; Output register B
KIRB	.EQ	KORB			; Input register B
KORA	.EQ	KVIA+$1			; Output register A
KIRA	.EQ	KORA			; Input register A
KDDRB	.EQ	KVIA+$2			; Data direction register B
KDDRA	.EQ	KVIA+$3			; Data direction register A
KT1CL	.EQ	KVIA+$4			; Timer 1 counter low
KT1CH	.EQ	KVIA+$5			; Timer 1 counter high
KT1LL	.EQ	KVIA+$6			; Timer 1 latch low
KT1LH	.EQ	KVIA+$7			; Timer 1 latch high
KT2CL	.EQ	KVIA+$8			; Timer 2 counter low
KT2CH	.EQ	KVIA+$9			; Timer 2 counter high
KSR	.EQ	KVIA+$A			; Shift register
KACR	.EQ	KVIA+$B			; Auxiliary control register
KPCR	.EQ	KVIA+$C			; Peripheral control register
KIFR	.EQ	KVIA+$D			; Interrupt flag register
KIER	.EQ	KVIA+$E			; Interrupt enable register
KORA2	.EQ	KVIA+$F			; Input/output register A without hand shake
;
INTDEL	.EQ	15*256			; Delay for single instruction trace is 15 cycles
T1IFLG	.EQ	%01000000		; Interrupt flag position for timer 1
CB1FLG	.EQ	%00010000		; Interrupt flag position for keyboard strobe on CB1
;
IKPCR	.EQ	%11101111		; Initial value for peripheral control register,
					;	bit 0		CA1 control from printer, positive edge interrupt,
					; 			    but not used in this monitor
					;	bits 1-3	CA2 control to strobe printer, normally high
					;	bit 4		CB1 control, negative edge keyboard interrupt
					;	bits 5-7	CB2 control, cassette output initially set high
;
IKIER	.EQ	%11010000		; Interrupt enable register control
					;	bit 4		CB1 keyboard interrupt enable
					;	bit 6		timer 1 interrupt enable
					;	bit 7		set interrupts enabled
					;	other enables not altered
;
PSTRB	.EQ	%00000010		; bit position in PCR of printer strobe, to toggle CA2
COPBIT	.EQ	%00100000		; bit position in PCR of cassette output, to toggle CB2
;
SWIOP	.EQ	$3F			; Software interrupt used for breakpoints
;
BUFLEN	.EQ	81+1			; Buffer up to 80 characters from keyboard
;
PROMPT	.EQ	'*'			; Monitor prompt
RUBCH	.EQ	$7F			; Keyboard code that does rubout operation
BSPACE	.EQ	$7F			; Code that backspaces VDU, also used to do rubout
LF	.EQ	$0A			; Linefeed
CR	.EQ	$0D			; Carriage return
SPACE	.EQ	$20			; Blank space
COMMA	.EQ	','			; The comma symbol
MINUS	.EQ	'-'			; The minus symbol
FFEED	.EQ	$0C			; Code used as clear screen command
SEMIC	.EQ	';'			; The semicolon symbol
;
IRQ	.EQ	$10
ZERO	.EQ	$04
;
;	Definitions of variables in page 03 / E3
;	----------------------------------------
;
ISTACK	.EQ	BASE+$035B		; Stack pointer starts here
RTAB1	.EQ	BASE+$035B		; RAM table 1 starts here
;
;	This table is copied from ROM on start-up
;
STACK	.EQ	BASE+$035B		; Position of stack pointer when empty
NTRACE	.EQ	BASE+$035D		; Number of instructions to trace before stopping
BSECHO	.EQ	BASE+$035F		; Character sent to backspace display
ECHOF	.EQ	BASE+$0360		; Keyboard buffer/echo control
					;	bits 0-5	don't care
					;	bit 6		echo console input to console output if set
					;	bit 7		buffer input lines, allow rubout if set
;
PFLAG	.EQ	BASE+$0361		; Printer control flag, echo console output to printer if nonzero
PNEW	.EQ	BASE+$0362		; This character not sent to printer
DELCNT	.EQ	BASE+$0363		; Non-zero delay count for cassette, controls baud rate
COPADR	.EQ	BASE+$0365		; Address for console output
CINADR	.EQ	BASE+$0367		; Address for console input
CASOPA	.EQ	BASE+$0369		; Address for cassette output
CASINA	.EQ	BASE+$036B		; Address for cassette input
PRINT1	.EQ	BASE+$036D		; Address of printer output routine
FUNCTI	.EQ	BASE+$036F		; Address of VDU function table
CMNDI	.EQ	BASE+$0371		; Address of monitor command table
IRQRTS	.EQ	BASE+$0373		; Address to go to on timer 1 interrupt
LINEPT	.EQ	BASE+$0375		; Address of memory input line, 0 if none
IRESV	.EQ	BASE+$0377		; Address of reserved vector routine
ISWI3	.EQ	BASE+$0379		; Address of SWI1 routine
ISWI2	.EQ	BASE+$037B		; Address of SWI2 routine
IFIRQ	.EQ	BASE+$037D		; Address of FIRQ routine
IIRQ	.EQ	BASE+$037F		; Address of IRQ routine
ISWI	.EQ	BASE+$0381		; Address of SWI routine
INMI	.EQ	BASE+$0383		; Address of NMI routine
OFFSET	.EQ	BASE+$0385		; Cassette load offset
;
;	General variables for monitor use
;
HEADST	.EQ	BASE+$0387		; Static head pointer into line buffer
HEADDY	.EQ	BASE+$0389		; Dynamic head pointer into line buffer
TAIL	.EQ	BASE+$038B		; Tail pointer into line buffer
MSTACK	.EQ	BASE+$038D		; stack saved whilst memory interpreting
CROW	.EQ	BASE+$038F		; current row of cursor on display
CCOL	.EQ	BASE+$0390		; current column of cursor on display
CPAGE	.EQ	BASE+$0391		; current start of display page in memory
MSAV	.EQ	BASE+$0393		; Saved address for memory command
GSAV	.EQ	BASE+$0395		; Saved address for go command
NAME	.EQ	BASE+$0397		; Saved name for cassette input output
CSSTRT	.EQ	BASE+$039D		; Saved cassette output start address
CSEND	.EQ	BASE+$039F		; Saved cassette output end address
ONLINE	.EQ	BASE+$03A1		; flag set to zero when find or in input line
LASTC	.EQ	BASE+$03A2		; Saved last character from input line current
CBREAK	.EQ	BASE+$03A3		; Address of breakpoint, $FFFF if none
NBREAK	.EQ	BASE+$03A5		; number of breakpoints to ignore before stopping user
CINST	.EQ	BASE+$03A7		; user instruction at breakpoint address
CTRACE	.EQ	BASE+$03A8		; number of instructions left to trace before stopping user
USRSTK	.EQ	BASE+$03AA		; Saved user-stack pointer when user halted
TEMP	.EQ	BASE+$03AC		; Temporary storage
BUFFER	.EQ	BASE+$03AE		; line input buffer
;		BASE+$03FF		; end of line input buffer
;
;	Memory Maps
;	-----------
;
;	Original Acorn
;	$0000-$03FF	Monitor System RAM (IC5,6)
;	$0400-$07FF	40-column Teletext video memory
;	$0800-$08FF	40-column Teletext 6845 CRTC
;;	$0900-$09FF	6522 VIA ($0980)
;		REGA	b6-b0	printer output
;			b7	printer busy input
;		REGB	b6-b0	keyboard ASCII input
;			b7	cassette input
;		CA1		input from printer acknowledge (not used)
;		CA2		output strobe to printer
;		CB1		input strobe from keyboard
;		CB2		output to cassette
;		Timer1		used to single step
;		Timer2		unused
;	$0A00-$0A7F	8271 floppy disk controller
;	$1000-$17FF	80-column VDU video memory
;	$1800-$187F	80-column 6845 CRTC ($1840)
;	$2000-$3FFF	User RAM (8K)
;	$2000-$BFFF	or User RAM (8K+32K)
;	$F800-$FFFF	Monitor EPROM (IC4)
;
;	Flex
;	$0000-$7FFF	User RAM (32K)
;	$0000-$BFFF	or User RAM (48K)
;	$C000-$DFFF	FLEX System RAM (loaded from Disk)
;	$E000-$E3FF	Monitor System RAM (IC5,6)
;	$E400-$E7FF	40-column Teletext Video memory
;	$E800-$E8FF	40-column Teletext 6845 CRTC
;	$E800-$E87F	80-column 6845 CRTC ($E840)
;	$E900-$E9FF	6522 VIA ($E980)
;		REGA	b6-b0	printer output
;			b7	printer busy input
;		REGB	b6-b0	keyboard ASCII input
;			b7	cassette input
;		CA1		input from printer acknowledge (not used)
;		CA2		output strobe to printer
;		CB1		input strobe from keyboard
;		CB2		output to cassette
;		Timer1		used to single step
;		Timer2		unused
;	$EA00-$EA7F	8271 floppy disk controller
;	$F000-$F7FF	80-column VDU video memory
;	$F800-$FFFF	Monitor EPROM (IC4)
;
	.OR	MPROM
;
				; ****** Hardware Reset Starts Here ******
;
RESET:	LDA	#MONDP		; setup direct page
	TFR	A,DP
	.DP	MONDP
	LDX	#PTAB1		; ROM table start
	LDU	#RTAB1		; RAM table start
RST1:	LDA	,X+		; copy ROM to RAM
	STA	,U+
	CMPX	#PTAB2		; until end of table
	BNE	RST1
	LDS	<STACK		; setup stack pointer
;
	.DO	VER<>495	; (all versions except 4.95)
	LDX	MPROM-2
	CMPX	#$A55A		; check for extra ROM
	BNE	STRT1		; not there
	JSR	[MPROM-4]	; else call it
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	.DB	$17,$06,$63	; 'LBSR	CONCHR-3'
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	NOP
	.FI
;
STRT1:	LDU	#BACK		; Put monitor return on to stack
	PSHS	U
	LDD	#$0C
STRT2:	PSHS	A		; Put dummy registers onto stack
	DECB
	BNE	STRT2
	STS	<USRSTK		; Save stack pointer
	LDX	#BUFFER		; Get start of buffer
	STX	<HEADDY		; and set up pointers
	STX	<HEADST
	STX	<TAIL
	LBSR	CRTCI		; Initialise CRT controller
	LBSR	VIAI		; and VIA chips
	CLR	<LASTC		; Set no saved character
	LBSR	BRKOUT		; Remove if exists
	LDX	#$FFFF		; then set none existing
;
	.DO	VER<>495	; (all versions except 4.95)
	STX	<CBREAK
	ANDCC	#$EF		; CLI: Allow interrupts	
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	JMP	MCASIN
	.DB	$EF		; Packing ?
	.FI
;
	.DP	0
MON:	LDA	#PROMPT		; Send prompt
	LDX	>LINEPT		; unless
	BNE	MON1		; memory input
	LBSR	CONOUT
;
MON1:	STA	>ONLINE		; Set no CR yet
PARSE:	LDA	#MONDP
	TFR	A,DP		; Set direct page
	.DP	MONDP
	LDX	<LINEPT		; See if mem input,
	BEQ	PARSEC		; if so,
	LDA	,X		; then see if null yet
	BEQ	MEND		; End if is null
PARSEC:	BSR	CONCHR		; Get input
	BEQ	MON		; prompt on CR
	LDX	<CMNDI		; else command table search
	LBSR	DISPCH
	BRA	PARSE
;
; Enter here for monitor to use memory input line (X) is start of line, ends with a null.
; multiple input lines are allowed, each line ends with carriage return.
;
; Exits with (a) zero if all ok, else (a) is $FF if found null too early,
; else (a) is character causing error.
;
	.DP	0
MEMUSE:	PSHS	DP
	STS	MSTACK		; Save stack for return.
	STX	LINEPT		; Save pointer in memory
	CLR	LASTC		; Set none saved
	BRA	MON		; and call monitor
CONCHR:	LDA	#CR		; Assume CR now
	TST	ONLINE		; and if found CR then
	BEQ	CON1		; is correct
	BSR	CONIN		; Get input
	CMPA	#CR		; If not CR then
	BNE	CON2		; done
CON1:	CLR	ONLINE		; Set found CR
CON2:	RTS
;
; Console input routine, gets character from keyboard buffer or memory it finds
; null in memory then returns to to caller with error $FF.
;
	.DP	MONDP
CONIN:	PSHS	DP,X
	LDA	#MONDP		; Set up
	TFR	A,DP		; direct page
	LDA	<LASTC		; Saved one?
	BEQ	CON5		; none saved
	CLR	<LASTC		; not saved anymore
	PULS	DP,X,PC
;
CON5:	LDX	<LINEPT		; see if mem input
	BEQ	CON3		; no, use buffer
	LDA	,X+		; Get men value
	BNE	CON4		; ok if not null
	DECA			; Set to $FF
MEND:	LDS	<MSTACK		; clear mem
	CLR	<LINEPT+0	; input
	CLR	<LINEPT+1
	PULS	DP,PC		; and return to caller
CON3:	JSR	[CINADR]	; Get console input
CON4:	STX	<LINEPT		; store new mem pointer
	PULS	DP,X,PC
;
; This routine prints the registers from the stack as pointed to by USRSTK,
; then prints USRSTK itself.  Also prints five bytes starting at (PC)
;
EXREG:	LDX	#TITLES		; Headings
	BSR	STRING		; printed first
	LDU	<USRSTK
	LDB	#$04		; ie, CC,A,B,DP.
EX1:	LDA	,U+		; Get data
	LBSR	OPARSP		; output as 2 hex digits
	DECB
	BNE	EX1		; until all 4 output
	LDB	#$04		; ie, X, Y, U, PC
EX2:	LDX	,U++		; get 2 bytes
	LBSR	OPXREG		; as 4 hex digits
	DECB
	BNE	EX2		; until all output
	LDX	<USRSTK		; then put out stack
	LBSR	OPXREG		; address.
	LDU	$0A,X		; Get user PC value
	LDX	#PCMESS		; Send
	BSR	STRING		; title
	LDB	#$05		; then 5 bytes
EX3:	LDA	,U+
	LBSR	OPARSP
	DECB
	BNE	EX3
;
; Output the string CR, LF to console
;
OPCRLF:	LDX	#SCRLF		; String address of CR, LF
;
; Output the string pointed to by X until a null,
; leaves X pointing to null+1, other registers intact
;
STRING:	PSHS	A		; Save A
STRNG1:	LDA	,X+		; Get data
	BEQ	EOTXT		; finish if null
	LBSR	CONOUT		; else output
	BRA	STRNG1		; and repeat.
EOTXT:	PULS	A,PC		; Get A back and return
;
; Calculate apparent address of cursor without allowing for memory wrap around, result in D.
;
CCOFST:
	.DO	VER<>495	; (all versions except 4.95)
	LDA	#COLS
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	LDA	<$01
	.FI
;	
	LDB	<CROW
	MUL
	ADDB	<CCOL
	ADCA	#0
	ADDD	<CPAGE
	RTS
;
; Calculate real address of cursor in memory space, result returned in X.
;
CCLOCN:	PSHS	D
	BSR	CCOFST		; Apparent address.
	ANDA	#WRPMSK		; Wrap around in 1K or 2K page
	ADDD	#VPAGE		; Add start of video page
	TFR	D,X
	PULS	D,PC		; Un-save and return
;
; Initialise Versatile Interface Adapter (VIA)
;
VIAI:	LDD	#IKPCR*256+IKIER
	LDX	#KVIA		; Point to VIA.
	STA	KPCR-KVIA,X	; Peripheral control register
	STB	KIER-KVIA,X	; Interrupt enable register
	LDA	#$7F		; Printer output (7 bits) plus
	STA	KDDRA-KVIA,X	; cassette input on a side
	LDA	KIFR-KVIA,X	; Cancel any interrupts present
	STA	KIFR-KVIA,X
	RTS
;
; Initialise CRT controller on VDU card, R or S version
;
CRTCI:
;
	.DO	VER<>495	; (all versions except 4.95)
	LDX	#CRTCSV		; Table for S version
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	LDX	#$E300
	.FI
;
	LDD	#PAGHI+1*256+$AA ; Test the page register
	STD	CRTC		; (low byte) for read/write ability
	CMPB	CRTC+1
	BEQ	CRTC11		; If so, then V version
	LEAX	$C,X 		; 'LEAX	CRTCRV-CRTCSV,X'	; else get R table instead
CRTC11:	LDA	#12-1		; Set up 12 registers
CRTC12:	LDB	A,X		; Get data and store it in correct register
	STD	CRTC
	DECA
	BPL	CRTC12		; Repeat until all 12 done
;
; Reset display to a blank page with cursor at top left of screen
;
CLRALL:	CLR	<CROW		; Row 0
	CLR	<CCOL		; Column 0
	CLR	<CPAGE+0	; Current page set
	CLR	<CPAGE+1	; to start of display memory
	LBSR	SETTOP		; Set page in CRTC chip
	LBSR	SETCUR		; and set cursor in CRTC chip
;
; Clear the display memory to all blanks
;
CLRSCN:	LDD	#SPACE*256+SPACE ; Two spaces
	LDX	#VPAGE		; Start of display memory
CLRS1:	STD	,X++		; Store two blanks
	CMPX	#VPAGE+PSIZE	; and repeat
	BNE	CLRS1		; until done all page
	RTS
;
; This routine used to put a character to the VDU, handling CR, LF, backspace and form feed.
; All registers are saved.
;
DISPLA:	PSHS	U,Y,X,DP,B,A,CC	; Save registers
	LDB	#MONDP
	TFR	B,DP
	LDX	<FUNCTI		; Get function table
	ORCC	#IRQ		; Stop interrupts since not re-entrant
	BSR	DISPCH		; Jump on function table
	PULS	U,Y,X,DP,B,A,CC,PC	; then restore and return
;
; This routine puts a character on display and moves cursor
;
SIMCHR:	BSR	CCLOCN		; Find location in memory
	STA	,X		; and store character
	INC	<CCOL		; move cursor across
	LDA	<CCOL		; then done if
;
	.DO	VER<>495	; (all versions except 4.95)
	CMPA	#COLS		; all columns
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	CMPA	<$01
	.FI
;
	BNE	SIM1		; not yet filled
	CLR	<CCOL		; Do CR
;
; Move cursor down one line, scroll display if required
;
DOLF:	INC	<CROW		; Down a row
	LDA	<CROW
;
	.DO	VER<>495	; (all versions except 4.95)
	CMPA	#ROWS		; Last row yet
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	CMPA	<$06
	.FI
;
	BNE	SIM1		; if not then done,
	DEC	<CROW		; else back up
	BSR	SCROLL		; and scroll instead
SIM1:	BRA	SETCUR		; then set new cursor
;
; Move cursor back erasing last character.
;
DORUB:	BSR	BSONE		; Back up cursor
	LDA	#SPACE
	BSR	DISPLA		; then blank last character,
	BSR	BSONE		; and back up again
LF98D:	BRA	SETCUR		; then set cursor up
;
; Back cursor up allowing line and row underflow.
;
BSONE:	DEC	<CCOL		; Left move cursor
	BPL	BS1		; No under flow
;
	.DO	VER<>495	; (all versions except 4.95)
	LDA	#COLS-1		; else set to
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	LDA	<$0C
	.FI
;
	STA	<CCOL		; right margin
	DEC	<CROW		; and up one row
	BPL	BS1		; No row underflow
	CLR	<CROW
	CLR	<CCOL
BS1:	RTS
;
; Dispatch routine looks up a character in a table,
; character in A, table address ,X
;
; Table format is:	First byte, number of entries 1 to 255
;
;	Repeat for each entry:	character to match with,
;				2-byte offset from start of this table
;				of routine to jump to if characters match
;
;	flag byte:  control if no match found:
;
;	positive: next word is offset of default routine
;	zero:     return to calling program
;	negative: next word is address of another table to search
;
DIS2:	LDD	,X		; Get address of new table,
	STD	2,S		; replace old on stack,
	PULS	D,X,U		; and begin again
DISPCH:	PSHS	D,X,U		; Save registers
	LDB	,X++		; Get length and move to offset.
DIS1:	CMPA	-1,X		; Compare characters
	BEQ	DIS3		; Found it, else,
	LEAX	3,X		; move down.
	DECB			; Repeat until
	BNE	DIS1		; done
	TST	-1,X		; Test flag byte.
	BEQ	DIS4		; Zero means return
	BMI	DIS2		; search new table, else
DIS3:	LDD	,X		; get offset and
	ADDD	2,S		; add start of table, then
	STD	4,S		; store in stack.
	PULS	D,X,PC		; Restore and go to routine
DIS4:	PULS	D,X,U,PC	; Return to caller
;
; Scroll the display up one line, leave cursor at same position on screen, leave registers intact
;
SCROLL:	LDD	<CPAGE		; Get start of page
	ADDD	#ROWS*COLS	; then move off end, $03E8 (40-column) or $07D0 (80-column)
	LDX	#VPAGE		; Actual memory address
	LDU	#SPACE*256+SPACE ; Double space
;
	.DO	VER<>495	; (all versions except 4.95)
	LDY	#COLS/2		; Number to blank
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	LDY	<$0D
	NOP
	.FI
;
SCR1:	ANDA	#WRPMSK		; Wrap around in 1K or 2K
	STU	D,X		; and put 2 blanks
	ADDD	#2		; Move up and
	LEAY	-1,Y		; repeat until
	BNE	SCR1		; line done
	SUBD	#ROWS*COLS	; Move to second line now, $03E8 (40-column) or $07D0 (80-column)
	ANDA	#WRPMSK		; wrap around and
	STD	<CPAGE		; set as new page
	BSR	SETCUR		; Put cursor back in position
;
; Put page address into CRT controller.
;
SETTOP:	LDX	<CPAGE		; Get page
	LDA	#PAGHI		; Point to page start register
	BRA	SETPAR		; then enter parameters
;
; Do a carriage return by setting column to 0
;
DOCR:	CLR	<CCOL
;
; Set cursor register in CRT controller.
;
SETCUR:	LBSR	CCOFST		; Get cursor address, no wrap around
	TFR	D,X		; into X
	LDA	#CURHI		; Point to cursor register
;
; Put 2 byte value into CRT controller, value in X, high byte register number in A
;
SETPAR:	PSHS	X		; Get high byte
	PULS	B		; Set register
	STD	CRTC		; and data move to low byte
	INCA			; Get low byte
	PULS	B		; Set register
	STD	CRTC		; and data.
	RTS
;
; This routine puts keyboard input into line buffer
; If no room then ignores character, else echoes to display
; If echo is switched on handles rubout unless line buffer is switched off
;
HAVCHR:	LDB	<ECHOF
	BPL	HAV3		; Buffer off, so no rubout
	CMPA	#RUBCH
	BEQ	BSP1		; Go do rubout
HAV3:	BSR	PUTCHR		; Put into buffer
	BEQ	PUT1		; No room, do not echo
	TSTB			; If buffer off, then no
	BPL	HAV4		; line feed on CR input
	CMPA	#CR
	BNE	KBECHO		; Not CR, so no
	BSR	KBECHO		; echo of a linefeed
	LDA	#LF
HAV4:	STX	<HEADST		; Move static pointer up
;
; Put character in A to console output if echo on
;
KBECHO:	LDB	<ECHOF		; See if echo on
	LSLB			; Need bit 6
	BPL	PUT1		; Not on if zero
;
; Console output routine, sends to printer also if switched on
;
	.DP	0
CONOUT:	TST	PFLAG		; If printer off then
	BEQ	CANOP1		; value is zero
	JSR	[PRINT1]	; else call printer
CANOP1:	JMP	[COPADR]	; then console output
	.DP	MONDP
BSP1:	LDX	<HEADDY		; End of line, if at start of line
	CMPX	<HEADST
	BEQ	PUT1		; then nothing to rubout
	CMPX	#BUFFER		; Do cyclic
	BNE	BSP2
	LEAX	BUFLEN,x	; decrement of pointer
BSP2:	LEAX	-1,x
	STX	<HEADDY		; Set new end of line
	LDA	<BSECHO		; and echo a backspace
	BRA	KBECHO
;
; Put character into buffer if room, return Z=0, else return Z=1
;
PUTCHR:	LDX	<HEADDY		; Get pointer
	BSR	BUMPU		; If no room, then
	CMPX	<TAIL		; pointers equal,
	BEQ	PUT1		; so done
	STA	,X		; Store it and
	STX	<HEADDY		; set new pointer
	ANDCC	#255-ZERO	; Clear Z flag
PUT1:	RTS
;
; Cyclic increment of buffer pointers.
;
BUMPU:	LEAX	1,X
	CMPX	#BUFFER+BUFLEN
	BNE	ANRTS
	LDX	#BUFFER
ANRTS:	RTS
;
; Get character from buffer, if none then clears interrupt
; mask and waits. all registers saved, including CC
;
	.DP	0
GETCHR:	PSHS	X,CC		; Save and
	BRA	GETCH1		; skip wait
GETCH2:	CWAI	#255-IRQ	; Wait for an interrupt
GETCH1:	LDX	TAIL		; Get tail pointer
	CMPX	HEADST		; If equals static head
	BEQ	GETCH2		; then no character
	BSR	BUMPU		; else move up
	LDA	,X		; and get it.
	STX	TAIL		; Set new tail
	PULS	CC,X,PC
;
; Output X register as four hex digits to console, all registers saved
;
	.DP	0
OPXREG:	PSHS	D
	TFR	X,D
	BSR	OPAREG
	TFR	B,A
	BSR	OPARSP
	PULS	D,PC
;
; Output a register as two hex digits to console, all registers saved except a
;
OPAREG:	PSHS	A
	LSRA
	LSRA
	LSRA
	LSRA
	BSR	HEXOUT		; Left nibble
	LDA	,S+		; Get byte back and
	ANDA	#15		; do right nibble
;
; Output A as a single hex digit.
;
HEXOUT:	ADDA	#'0'
	CMPA	#'9'
	BLS	HEX2
	ADDA	#'A'-'9'-1
HEX2:	BRA	CONOUT		; Output and return
;
; Output a hex digit followed by a space
;
OPARSP:	BSR	OPAREG
	LDA	#SPACE
	BRA	HEX2
;
; Printer routine, this interfaces to Anadex or Centronics parallel interface printers
;
	.DP	0
PRINT:	PSHS	D,U
	LDU	#KVIA		; Point to VIA
	CMPA	PNEW		; If spec'd symbol then
	BEQ	PEXIT		; do not send
PWAIT1:	LDB	KORA2-KVIA,U	; Check if busy
	BMI	PWAIT1		; If so then wait
	STA	KORA-KVIA,U	; Store data
	LDB	#IKPCR-PSTRB	; then low strobe
	STB	KPCR-KVIA,U	
	LDB	#IKPCR		; and high strobe
	STB	KPCR-KVIA,U
PEXIT:	PULS	PC,U,B,A	; Pull registers and return
;
; These tables are the decision tables for the memory examine and change function
;
	.DP	MONDP
MTABA:	.DB	MTABAE-MTABA/3-1	; number of entries
	.AS	'V'
	.DW	VADDR-MTABA	; Modify break address and memory
	.AS	'G'
	.DW	GADDR-MTABA	; Modify go address and memory
	.AS	'P'
	.DW	PADDR-MTABA	; Modify proceed address and memory
	.AS	'R'
	.DW	RADDR-MTABA	; Modify register locations
	.DB	SPACE
	.DW	SPACEA-MTABA
	.DB	COMMA
	.DW	COMMAA-MTABA
	.DB	SEMIC
	.DW	SEMICA-MTABA
	.DB	MINUS
	.DW	MINUSA-MTABA
	.DB	1
	.DW	NOTA-MTABA
MTABAE:	.EQ	$
;
MTABB:	.DB	MTABBE-MTABB/3-1
	.DB	SPACE
	.DW	SPACEB-MTABB
	.DB	COMMA
	.DW	COMMAB-MTABB
	.DB	SEMIC
	.DW	SEMICB-MTABB
	.DB	MINUS
	.DW	MINUSB-MTABB
	.DB	1
	.DW	NOTB-MTABB
MTABBE:	.EQ 	$
;
; Memory examine and change routine.
;
VADDR:	LBSR	BRKOUT		; Take out any break
	LDU	#CBREAK		; point to break address store
	BRA	ADDR1
GADDR:	LDU	#GSAV		; point to go address store
ADDR1:	LDY	,U		; Get initial value
	BRA	GDATA
PADDR:	LDU	<USRSTK
	LEAU	$0A,U		; Point to user PC
	BRA	ADDR1
RADDR:	LDY	<USRSTK		; Get address off 'CC' register
RADDR1:	LDU	#TEMP		; Dummy location for new value
	BRA	GDATA
MEM:	LDU	#MSAV		; Point to memory address store
	LDY	,U		; Get initial value
	CLRB			; Set status zero
SPACEA:	LBSR	CONCHR		; Get input
	BEQ	CRA		; No address given
	LDX	#MTABA		; Search address table
	LBRA	DISPCH
CRD:	TSTB			; If status
	BLE	CRA		; -1 or 0 then no change
	LEAY	$01,Y		; else up one
CRA:	TFR	Y,X		; Print out address
	LBSR	OPXREG
	LDA	,X		; then data
	LBSR	OPARSP
	LDB	#$01		; Set status +1
	STB	<ONLINE		; Allow next line
	BRA	GDATA		; and continue
NOTA:	STA	<LASTC		; Save for re-use
	BSR	NUMB		; Get number with
	BVS	MERR		; error if none
	TFR	D,Y		; is new address
DAT1:	CLRB			; Set status 0
COMMAA:	.EQ	$
SPACEB:	.EQ	$
GDATA:	LBSR	CONCHR		; Get input
	BEQ	CRD		; No data found CR
	LDX	#MTABAE		; else search data table
	LBRA	DISPCH
SEMICB:	TSTB			; Test status
	BGE	SEMICA		; If -1 then
	LEAY	-1,Y		; dec before
SEMICA:	STY	,U		; Save new address
	RTS			; and exit
MINUSA:	.EQ	$
MINUSB:	LEAY	-1,Y		; Back down 1,
	TSTB			; but if status
	BGE	MIND2		; is -1 then
	LEAY	-1,Y		; duck down 2
MIND2:	BRA	DAT1		; then continue
NOTB:	STA	<LASTC		; Save for re-use
	BSR	NUMB		; in number.
	BVS	MERR		; Should have number
	STB	,Y		; Store data then
	CMPB	,Y		; check it
	BEQ	COMMAB		; is ok,
	LDX	#MQRY		; else tell
	LBSR	STRING		; user.
COMMAB:	LEAY	1,Y		; Go up 1
	LDB	#$FF		; with status -1
	BRA	GDATA		; then continue
MERR:	LBSR	CONCHR		; Get wrong symbol
	LBRA	BADCMD		; and tell user
;
; Get hex number from input stream, allow leading spaces and stop on first non-hex character,
; return number in D, with V=0, if no number then do and V=1
;
	.DP	0
NUMB:	CLRA
	CLRB
	PSHS	D		; Put initial zero value
	BSR	GETHXS		; Get first non-blank as hex value
	BVS	NUMB1		; wasn't hex
NUMB3:	LDB	#$04
	ASLA			; Move to
	ASLA
	ASLA
	ASLA			; high nibble
NUMB2:	ASLA			; Rotate into
	ROL	1,S
;
	.DO	VER=490		; (original Acorn version 4.90 only)
	.DB	$69,$60		; 'ROL	0,S'
	.FI
;
	.DO	VER<>490	; (versions 492 and 495)
	.DB	$69,$E4		; 'ROL ,S'
	.FI
;
	DECB			; Do 4 times.
	BNE	NUMB2		; Get a hex digit from console
	BSR	GETHEX		; Was hex so use,
	BVC	NUMB3		; else finish with V clear
NUMB4:	ANDCC	#$FD		; CLV
NUMB1:	PULS	D,PC
;
; GETHXS - get a hex digit ignoring leading spaces
; GETHEX - get a hex digit both return value in A with V=0 else set V=1 if non-hex
;
GETHXS:	LBSR	CONCHR		; Get input.
	BEQ	GETH5		; On CR, no number
	CMPA	#SPACE		; If space
	BEQ	GETHXS		; then ignore
	BRA	GETH2		; Change to hex
;
GETHEX:	LBSR	CONCHR		; Get input
	BEQ	GETH5		; On CR, no number
GETH2:	CMPA	#'0'
	BCS	GETH1		; Illegal hex
	CMPA	#'9'
	BLS	GETH3		; Number hex
	CMPA	#'A'
	BCS	GETH1		; Illegal hex
	CMPA	#'F'
	BLS	GETH4		; Alpha hex
GETH1:	CMPA	#COMMA
	BEQ	GETH5		; Absorb comma
	STA	LASTC		; else re-use
GETH5:	ORCC	#$02		; Bad hex, SEV
	RTS
GETH4:	SUBA	#7		; Alpha offset
GETH3:	SUBA	#'0'		; Number offset
	RTS			; with V clear
;
; Resume user program using stack as stands
;
	.DP	MONDP
RESUME:	LEAS	2,S		; Strip return address
	BSR	NUMB		; Get number or zero
	STD	<NBREAK		; and set break ignore count
RES2:	BSR	BRKIN		; Insert breakpoints
		PULS	PC,U,Y,X,DP,B,A,CC	; and pull all user registers off stack
;
; Software interrupt handler, come here on breakpoint
; Either stops and displays registers or traces past breakpoint and resumes
;
SWIHAN:	LDA	#MONDP		; Set up
	TFR	A,DP		; direct page
	.DP	MONDP
	LDX	10,S		; back up
	LEAX	-1,X		; user
	STX	10,S		; program counter
	BSR	BRKOUT		; remove breakpoint
	LDX	<NBREAK		; Get count.
	BEQ	RES1		; stop if zero, else
	LEAX	-1,X		; decrement
	STX	<NBREAK		; and restore
	BSR	TUSER1		; Trace past break
	BRA	RES2		; then resume again
RES1:	STS	<USRSTK
	LBSR	EXREG		; display registers
	BRA	BACK1		; and stay in monitor
;
; Change number in X if one given in input stream, destroys D
;
NUMBX:	BSR	NUMB
	BVS	NUMBX1		; No number
	TFR	D,X
NUMBX1:	RTS
;
; User program returns here if RTS done.
;
BACK:	LEAS	-2,S		; Make room for new return address
	PSHS	CC,A,B,DP,X,Y,U,PC	; and save all user registers
	LDX	#BACK		; Set return address
	STX	12,S		; again
	LDB	#MONDP
	TFR	B,DP
	BSR	BRKOUT		; Remove breakpoints
	STS	<USRSTK		; Save user stack pointer
BACK1:	ANDCC	#$EF		; CLI: enable interrupts
	LBRA	PARSE		; and resume monitor functions
;
; Go to user program, optional address specified
; The stack pointer is reset, but the register contents are maintained as listed by the command

GOUSER:	CLR	<NBREAK+0	; Put zero in
	CLR	<NBREAK+1	; break count
	LDU	<ISTACK		; Get pointer
	LDX	#BACK		; return address,
	STX	,--U		; pushed first
	LDB	#11+2		; 12 registers plus return.
GO1:	LDA	B,S		; Get value and
	STA	,-U		; push it
	DECB
	BPL	GO1		; For all registers
	LEAS	2,U		; new stack, ignore return address
	LDX	<GSAV		; Saved address
	BSR	NUMBX		; Change if given
	STX	<GSAV		; and restore.
	STX	10,S		; Put as user PC
	BSR	BRKIN		; Insert breakpoint
	PULS	CC,A,B,DP,X,Y,U,PC	; and begin user program
;
; Change user breakpoint position.
;
BRKSET	BSR	BRKOUT		; Ensure old is out
	LDX	#$FFFF		; value for no break
	BSR	NUMBX		; change if given
	STX	<CBREAK		; and save.
	RTS
;
; Put breakpoint in if one exists.
;
BRKIN:	BSR	BRKTST		; See if exists
	BEQ	BRK1		; Already a SWI, so done
	STA	<CINST		; else save it
	LDA	#SWIOP
	STA	,X		; and insert a SWI instead
BRK1:	RTS
;
; Check break required, if not does RTS twice.
;
BRKTST:	LDX	<CBREAK		; Get address
	CMPX	#$FFFF		; $FFFF means none
	BEQ	BRK10		; not one
	LDA	,X		; Get instruction
	CMPA	#SWIOP		; see if SWI
	RTS			; then exit
BRK10:	PULS	PC,X		; Exit twice
;
; Called by SWI
; -------------
; Remove a breakpoint if one present in code
;
BRKOUT:	BSR	BRKTST		; See if exists
	BNE	BRK2		; Not SWI, leave alone
	LDA	<CINST		; Get saved instruction
	STA	,X		; and restore in code
BRK2:	RTS
;
; Trace one instruction of user code
;
TUSER1:	PULS	X		; Get return address
	STX	<IRQRTS		; and save it
	LDA	#255-IRQ	; Clear IRQ mask
	ANDA	,S		; in user
	STA	,S		; condition codes
	LDD	#INTDEL		; Delay before interrupt
	STD	KT1CL		; Set timer going,
	PULS	CC,A,B,DP,X,Y,U,PC	; start user program running
;
; Set number of instructions to trace on each command
;
TRACEN:	LBSR	NUMB		; Get 0 if no number
	STD	<NTRACE		; Save result.
TRACE1:	RTS
;
; Trace required number of instructions then display register contents and halt user
;
TRACE:	LDX	<NTRACE		; Get number to trace
	BEQ	TRACE1		; ignore command if zero
	LEAS	2,S		; strip return address
TRACE2:	STX	<CTRACE		; save number left
	BSR	TUSER1		; trace one instruction
	LDX	<CTRACE		; Get number left
	LEAX	-1,X		; and decrement
	BNE	TRACE2		; repeat if required
	LBRA	RES1		; else show registers and halt user
;
; Turn printer echo of console output on or off
;
PCNTL:	LBSR	CONCHR		; Get input
	BEQ	POFF		; if CR then off
	CMPA	#'+'		; if plus
	BEQ	PON		; then switch on
	STA	<LASTC		; Re-use if not +
POFF:	CLRA			; Switch off value
PON:	STA	<PFLAG		; Set flag
	RTS
;
; If get bad command, query it and ignore rest of line
;
BADCMD:	LDX	<LINEPT		; If memory input
	LBNE	MEND		; then exit
	LDX	#CQRY		; Output query
	LBSR	STRING		; message
	LBSR	CONOUT		; and character
	LBSR	OPCRLF		; followed by CR, LF
BAD1:	LBSR	CONCHR		; Get input and
	BNE	BAD1		; if not CR then ignore it
	RTS			; then carry on
;
; Cassette file load routine, this searches for named file followed by data
;
LOAD:	LBSR	NAMEIN		; Get file name
	LBSR	NUMB		; Get offset
	STD	<OFFSET		; and save
LOAD2:	LDX	#NAME		; Point to filename space
LOAD4:	LDA	#'0'		; Get name file
	BSR	GETHDR		; header.
	BNE	LOAD4		; Ignore others
	LDB	#$06		; Name length
	STB	<TEMP		; Save it
LOAD3:	BSR	CBIN1		; Get input character
	LDB	,X+		; and name character
	CMPB	#'?'		; If wildcard
	BEQ	LOAD1		; then matches
	CMPA	-1,X		; else compare
	BNE	LOAD2		; Wrong name
LOAD1:	DEC	<TEMP		; Count name length
	BNE	LOAD3		; Repeat for all 6
	BSR	CBIN1		; Ignore checksum byte
;
; Enter here for match without filename check
;
LOAD7:	LDA	#'1'		; Get data
	BSR	GETHDR		; header
	BNE	LOAD5		; Wrong header
	BSR	CBIN2		; Get start address
	TFR	D,X		; and save while
	BSR	CBIN2		; getting end address
		PSHS	D	; Put end on stack
LOAD6:	BSR	CBIN1		; Get data item
	STA	,X+		; and store it
	CMPX	,S		; if not done
	BLS	LOAD6		; then repeat
	BSR	CBIN1		; Get checksum byte
	PULS	X		; Get old end address
	LBSR	OPXREG		; Show address so far
	TFR	U,D		; check summed in U
	COMB			; lower byte only
	BEQ	LOAD7		; If ok, then repeat
LFCEA:	LDX	#LQRY		; else message
	LBSR	STRING		; output
	BRA	LKEYON
LOAD5:	CMPA	#'9'		; if not '9' then ignore
	BNE	LOAD7
LKEYON:	LDA	#CB1FLG+$80	; Turn on keyboard
	STA	KIER
	LBRA	OPCRLF		; then exit.
;
; Get a header from the tape, if the expected one then set zero status,
; else return non-zero status.  Initialises the checksum in U to 0
;
GETHDR:	PSHS	A		; Save character
	LDA	#CB1FLG		; Turn off keyboard
	STA	KIER
GETHD1:	BSR	CBIN1		; Get from tape
	CMPA	#'X'+$80	; and if not 'X' header marker
	BNE	GETHD1		; then try again
	BSR	CBIN1		; Get next character
	LDU	#$0		; Set up checksum
	CMPA	,S+		; and compare with required
	RTS			; then return
;
; Get 2 bytes and form a 16-bit value in D.  Add offset since is address
;
CBIN2:	BSR	CBIN1		; Get 1 byte
	TFR	A,B		; and save while
	BSR	CBIN1		; get second
	EXG	A,B		; Wrong order, swap over
	ADDD	<OFFSET		; Move by offset
	RTS
;
; Get 1 byte from tape, modifying checksum to suit
;
CBIN1:	JSR	[CASINA]	; Get byte then
	LEAU	A,U		; add to checksum
	RTS
;
; Software asynchronous transmitter, outputs value in A as a start bit, 8 data bits,
; 2 stop bits, at rate controlled by count in DELCNT
;
MCASOP:	PSHS	CC,D,X		; Saves all registers
	ORCC	#$50		; SEIF, Keep timing
	LDB	#11		; Total length
	PSHS	B		; Save on stack
	LDB	#IKPCR-COPBIT	; Low start bit
	COMA			; Want data inverted
MCAS01:	NOP			; Get timing
LFD31:	BRA	MCAS02		; constant
MCAS02:	DEC	,S		; Bits counter
	BMI	MCAS03		; All done
	STB	KPCR		; Put out, but
	BSR	CWAIT		; wait 1 bit time
	ANDB	#255-COPBIT	; Assume next is zero
	LSRA			; Get next bit
	BCS	MCAS01		; Do want zero
	ORB	#COPBIT		; else set bit
	BRA	MCAS02		; and loop round
MCAS03:	LEAS	1,S		; Remove counter
	PULS	CC,D,X,PC	; and return
;
; CWAIT waits for 1-bit time, destroys X
; HWAIT waits 1/2 bit time, also destroys X
;
	.DP	0
CWAIT:	BSR	HWAIT		; Do first half
HWAIT:	LDX	DELCNT		; Get count required
HW1:	LEAX	-1,X		; Decrement
	BNE	HW1		; while non-zero
	RTS
;
; Software asynchronous receiver, gets value into A
; Saves all other registers, only gets 1 stop bit
;
	.DP	MONDP
;
	.DO	VER<>495	; (all versions except 4.95)
MCASIN:	PSHS	B,X
	LDA	#$80		; Rotating counter
MCAS11:	LDB	KIRB		; Wait for start bit
LFD5A:	BMI	MCAS11
	BSR	HWAIT		; Wait 1/2 bit time
	LDB	KIRB		; Recheck
	.FI
;
	.DO	VER=495		; (version 4.95 only)
MCASIN:	STX	<$A3
	ANDCC	#$EF
MCAS11:	LDD	#MON
	PSHS	A,B
	JMP	BOOT
	ADCB	,X+
	.FI
;
	BMI	MCAS11		; start bit
MCAS12:	BSR	CWAIT		; Wait whole bit time
	LDB	KIRB		; and get input
	CMPX	,S		; waste time to
	CMPS	,S		; match loop delays
	LSLB			; Move bit to carry
	RORA			; then into byte
	BCC	MCAS12		; Repeat for 8 bits
	BSR	CWAIT		; Get into stop bit
	PULS	B,X,PC		; and done
;
; Routine gets name from input stream, up to 6 characters long
; No name leaves memory unaltered.  Any name is padded to 6 characters with spaces
;
NAMEIN:	LDX	#NAME+6
NAM2:	LBSR	CONCHR		; Get a character
	BEQ	NAM1		; no name
	CMPA	#SPACE		; if space
	BEQ	NAM2		; ignore
	CMPA	#COMMA
	BEQ	NAM1		; Null name
	LDB	#256-6		; Minus name length
NAM3:	STA	B,X		; Store a letter
	INCB			; and move up
	BEQ	NAM6		; Done 6 chars, exit
	LBSR	CONCHR		; Get next letter
	BEQ	NAM5		; On CR, pad name
	CMPA	#SPACE		; On space,
	BEQ	NAM4		; pad name
	CMPA	#COMMA		; If not comma
	BNE	NAM3		; then use
NAM5:	LDA	#SPACE		; padding
NAM4:	STA	B,X		; Pad until
	INCB			; end of
	BNE	NAM4		; name buffer
NAM1:	RTS
NAM6:	LBSR	CONCHR		; Get next input
	BEQ	NAM1		; leave if CR
	CMPA	#COMMA		; else
LFDA8:	BEQ	NAM1		; absorb comma
	STA	<LASTC		; else re-use
	RTS
;
; Save files on cassette, dumps name block, data blocks as required in 256 byte blocks maximum,
; then end file block, can also inhibit end of file block
;
SAVE:	LDX	<CSSTRT		; Modify start address
	LBSR	NUMBX		; if
	STX	<CSSTRT		; required
	LDX	<CSEND		; Modify end address
	LBSR	NUMBX		; if
	STX	<CSEND		; required
	BSR	NAMEIN		; and get name
	LDB	#'0'		; Output name,
	BSR	XHEAD		; header
	LDB	#6		; name length
	LEAX	-6,X		; Point to name,
	BSR	DATOUT		; output name,
	BSR	CHKOUT		; then checksum.
	LDX	<CSSTRT		; Get start address
SAV6:	PSHS	X		; Save start
	LDD	<CSEND		; and get end address
	SUBD	,S++		; Form length needed
	BLO	SAV2		; Done all output
	TSTA			; If <=256 then
	BEQ	SAV5		; leave alone
	LDD	#255		; also set to 256
SAV5:	LEAU	D,X		; Form end of block
	PSHS	D,X,U		; Put start/end on stack
	LDB	#'1'		; Put a data
	BSR	XHEAD		; header.
	LEAX	2,S		; Point to start/end
	LDB	#4		; Two words
	BSR	DATOUT		; and put start/end out
	PULS	D,X,U		; Get all back
	INCB			; Modify length
	BSR	DATOUT		; and send data bytes
	BSR	CHKOUT		; then checksum
	BRA	SAV6		; and repeat
SAV2:	LBSR	CONCHR		; Get input
	BEQ	SAV3		; On CR send of block
	CMPA	#'-'		; If EOF inhibit
	BEQ	RTS1		; then skip x9
	STA	<LASTC		; Re-use input
SAV3	LDB	#'9'		; Send EOF
;
; Routine to send header to block, header type in B.  Also initialises checksum in Y
;
XHEAD:	LDY	#0
XH1:	LEAY	-1,Y		; Loop
	BNE	XH1		; delay
	LDA	#'X'+$80	; Send an 'X' header marker
	BSR	CASOPI		; to cassette
	TFR	B,A		; Get type
	BRA	CASOPI		; and send
;
; Data output routine, sends B data bytes starting from X
; (B zero means 256 bytes), X moves up by B bytes
;
DATOUT:	LDA	,X+		; Get data
	LEAY	A,Y		; Modify checksum
	BSR	CASOPI		; Send data
	DECB			; Repeat until
	BNE	DATOUT		; zero count
RTS1:	RTS
;
; Send checksum to tape from Y, lower byte only
;
CHKOUT:	TFR	 Y,D		; Checksum to D
	TFR	 B,A		; then get low byte
	COMA			; Want result $FF
CASOPI:	JMP	[CASOPA]	; Send check byte
;
; Drive parameter specification
;
DISCIT:				; Setup Drive
				; For Shugart drive
	.DB	$35		; Command - Initialise 8271 (sent by CMDPAR)
	.DB	$04		; Number of Parameters - 4
	.DB	$0D		; Parameter 1 Specify Initialise (i.e. not Bad Sectors)
	.DB	$14		; Parameter 2 - Step Rate 40mS
	.DB	$05		; Parameter 3 - Head Settling Time 10mS
	.DB	$AA		; Parameter 4 - Head Unload Time/Load Time 80mS/80mS

;				; Drive Bad Tracks (sent by CMDPAR)
	.DB	$35		; Command - Load Bad Tracks
	.DB	$04		; Number of Parameters - 4
	.DB	DRIVE/$80*8+$10	; Parameter 1 - either $10 (Drive 0) or change to $18 (Drive 1)
	.DB	$FF		; Parameter 2 - Bad Track No. 1 - none
	.DB	$FF		; Parameter 3 - Bad Track No. 2 - none
	.DB	$FF		; Parameter 4 - Current Track - none
;
				; Mode Register Setup (sent by CMDPAR)
	.DB	$3A+DRIVE	; Command - Write Special Register
	.DB	$02		; Number of Parameters - 2
	.DB	$17		; Parameter 1 - DMA Mode Register
	.DB	$C1		; Parameter 2 - 
;
				; Load Head onto Disc, Start Motor (sent by CMDPAR)
	.DB	$3A+DRIVE	; Command - Write Special Register 
	.DB	$02		; Number of Parameters - 2
	.DB	$23		; Parameter 1 - Drive Control Output Register
	.DO	VER=490		; (version 4.90 only)
	.DB	$28+DRIVE	; Parameter 2 - Fault Reset, Load Head
	.FI
	.DO	VER<>490	; (versions 4.92 and 4.95)
	.DB	$08+DRIVE	; Parameter 2 - Load Head
	.FI
;
				; Query Drive Ready (sent by DRVRDY)
	.DB	$2C+DRIVE	; Command - Read Drive Status
	.DB	$00		; Number of Parameters - 0
;
;				; Seek to Track 0 (sent by CMDPAR)
	.DB	$29+DRIVE	; Command - Seek
	.DB	$01		; Number of Parameters - 1
	.DB	$00		; Parameter 1 - Track 0
;
;				; Query Drive Ready (sent by DRVRDY)
	.DB	$2C+DRIVE	; Command - Read Drive Status
	.DB	$00		; Number of Parameters - 0
;
;				; Read Sector 1/2 (sent by CMDPAR)
	.DB	$13+DRIVE	; Command - Read Data Multi-sector
	.DB	$03		; Number of Parameters - 3
	.DB	$00		; Parameter 1 - Logical Track Address 0

	.DO	VER=490		; (original Acorn version 4.90 only)
	.DB	$02		; Parameter 2 - Logical Sector Address 2
	.FI
	.DO	VER<>490	; (version 4.92 and 4.95)
	.DB	$01		; Parameter 2 - Logical Sector Address 1
	.FI
	.DB	$21		; Parameter 3 - Sector Size - 256 bytes/sector
				;		Number of Sectors - 1 sector of 256 bytes
;
	.DO	VER<>492	; (original Acorn version 4.90 and version 4.95)
;				; Read Starting at Sector 3 (sent by CMDPAR)
	.DB	$13+DRIVE	; Command -  Read Data Multi-sector
	.DB	$02		; Number of Parameters - 2 (should be 3 ?)
	.DB	$00		; Parameter 1 - Logical Track Address 0
	.DB	$03		; Parameter 2 - Logical Sector Address 3
				; Parameter 3 - (missing should be $21 ?)
	.FI
	.DO	VER=492		; (version 4.92 only)
	.DB	$FF,$FF,$FF,$FF	; packing
	.FI
;
; This routine bootstraps from a mini-floppy disc
; Reads Sector 1/2 to find where to put program
;
BOOT:	LDX	#ANRTI		; Set dummy interrupt routine
	STX	<INMI
	LDA	#FLOPY/256	; Set direct page to 8271 FDC
	TFR	A,DP
	.DP	FLOPY/256	; and tell assembler
	LDX	#DISCIT		; Point to Disk Info Tables
	BSR	CMDPAR		; Set drive parameters
	BSR	CMDPAR		; Set bad tracks
	BSR	CMDPAR		; Set mode register
	BSR	CMDPAR		; Load head, drive on
	BSR	DRVRDY		; Check drive ready
	BSR	CMDPAR		; Seek Track 0
	BSR	DRVRDY		; Check drive ready
	BSR	CMDPAR		; Read Sector 2 (490) or 1 (495)
;
	.DO	VER=490		; (original Acorn version 4.90 only)
	LDU	#$0000		; Put at $0000
	.FI
;
	.DO	VER<>490	; (versions 492 and 495)
	LDU	#$C000		; Put at $C000
	.FI
;
	TFR	U,Y		; and point to it
	BSR	TRNSFR		; Move disc data to memory
	BNE	DERR		; Non-zero means error
;
	.DO	VER=490		; (original Acorn version 4.90 only)
	LDD	#$FF42		; Check first two bytes are $FF42
	CMPD	,Y++
	BNE	DERR		; If not $FF42, no boot present
	BSR	CMDPAR		; Start read at sector 3
	LDU	,Y++		; Get address to put at
	LDA	,Y+		; and number of sectors
	LDY	,Y		; Start of program
	ADDA	#$20		; Add sector length value
BOOT1:	LDB	<FDCS		; Get FDC status and
	BITB	#$20		; if parameter register full
	BNE	BOOT1		; then wait
	STA	<FDCP		; Send number of sectors
	BSR	TRNSFR		; Move data to memory
	BNE	DERR		; Error if non-zero
	LBSR	NUMB		; Try get number
	BVC	RTS2		; Got one, stay in monitor
	LDS	STACK		; Reset stack and go to program
	JMP	,Y
	.FI
;
	.DO	VER<>490	; (version 4.92 and 4.95)
	LDS	STACK		; Reset stack and go to program
	JMP	$C000
	.FI
;
DERR:	LDX	#DQRY		; Query user on display with error number
	LBSR	STRING
	LBSR	OPAREG
	LBRA	OPCRLF		; Newline and exit
;
	.DO	VER=492		; (version 4.92 only)
	.DB	$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF	; Packing
	.DB	$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.DB	$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.DB	$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
	.DB	$FF,$FF,$FF
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	LDY	#CRTCSV
	LDX	#$E300
	LDB	#$0B
LFE85:	LDA	,Y+
	STA	,X+
	DECB
	BPL	LFE85
	LDA	<$01
	DECA
	STA	,X+
	CLRA
	LDB	<$01
	LSRB
	STD	,X++
	RTS
	.DB	$FF,$FF,$FF,$FF,$FF,$FF,$FF	; Packing
	.FI	
;
; This routine sends one command followed by a variable number of parameters, possibly none
; X points to command, next byte is number of parameters
; X left pointing after last parameter, destroys D
;
CMDPAR:	LDD	,X++		; Get command (A) and number of parameters (B)
CP1:	TST	<FDCS		; Test 8271 status and wait if busy
	BMI	CP1
	STA	<FDCC		; Send command
CP2:	DECB			; If no more parameters then exit
	BMI	RTS2
CP4:	LDA	<FDCS		; If parameter register full then wait
	BITA	#$20
	BNE	CP4
	LDA	,X+		; Get next parameter and send it
	STA	<FDCP
	BRA	CP2		; then repeat
;
; Test if drive ready, on entry X points to read drive status command sequence,
; on exit drive is ready and X points to next command sequence
;
DRVRDY:	TFR	X,U		; Save pointer
DR2:	TFR	U,X		; Restore pointer
	BSR	CMDPAR		; Ask for drive status
DR1:	LDA	<FDCS		; Wait until result ready
	BITA	#$10
	BEQ	DR1
	LDA	<FDCR		; Get result
	BITA	#$04		; Ready bit mask
	BEQ	DR2		; Not ready, wait
RTS2:	RTS
;
TRNSFR:	PSHS	CC		; Save CC while
	ORCC	#$50		; SEIF, set masks, disc is on NMI
	LDB	#$04		; Data available mask
	BRA	TRN2
TRN1:	LDA	<FDCD		; Get data
	STA	,U+		; and store it
TRN2:	CWAI	#$FF		; Wait for interrupt
	BITB	<FDCS		; Check data available
	BNE	TRN1
	PULS	CC		; Get interrupt masks back
	LDA	<FDCR		; then get result
	RTS			; and return Z bit accordingly
;
; Interrupt request handler, comes here on IRQ active
; Checks for timer 1 or keyboard interrupt, if neither then complains to user
;
IRQHAN:	LDA	#MONDP		; Set up
	TFR	A,DP		; direct page
	.DP	MONDP		; tell assembler
	LDX	#KVIA		; Point to VIA address
	LDA	KIFR-KVIA,X	; Get flag register
	BPL	UNUSED		; Not the VIA!
	ANDA	#T1IFLG		; Try timer 1
	BEQ	LFEF5		; Not timer 1
	STA	KIFR-KVIA,X	; Clear the interrupt
	JMP	[IRQRTS]
LFEF5:	LDA	KIFR-KVIA,X	; Get flags again
	ANDA	#CB1FLG		; Try for keyboard
	BEQ	UNUSED 		; Query user if not
	STA	KIFR-KVIA,X	; Clear the interrupt
;
	.DO	VER<>495	; (all versions except 4.95)
	.DB	$A6,$00		; 'LDA	KIRB-KVIA,X'	; and get a character
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	.DB	$A6,$84		; 'LDA ,X'
	.FI
;
	ANDA	#$7F		; stripping spare bit
	LBSR	HAVCHR		; Put into buffer
ANRTI:	RTI			; then leave IRQ level
;
; Come here if unused interrupts are active, so complain to user and stop processor
; since cannot clear an unknown interrupt
;
UNUSED:	LDX	#IERR		; Query user on display
	LBSR	STRING
FOREVR:	BRA	FOREVR		; and stop dead !
;
				; *** VDU Configurations ***
;
;	R0	Horizontal Total Register		horizontal frequency, the total number of characters displayed and non-displayed per line
;	R1	Horizontal Displayed Register		number of displayed characters per line
;	R2	Horizontal Sync Position Register	position (start) of the horizontal sync
;	R3	(Horizontal) Sync Width Register	bits 0-3: width (character clocks) of the horizontal sync pulse
;							bits 4-7: vertical sync pulse width in scan line times
;	R4	Vertical Total Register			determines the vertical frequency (in character line times-1)
;	R5	Vertical Total Adjust Register		adjustment to vertical frequency (in scan line times)
;	R6	Vertical Displayed Register		number of displayed character rows
;	R7	Vertical Sync Position Register		position of the vertical sync pulse (character row times)
;	R8	Interlace & Skew Register		bits 0,1 control the raster scan mode
;							bits 4,5 control DISPTMG skew
;							bits 6,7 control cursor skew
;	R9	Maximum Scan Line Address Register	number of scan lines per character row - 1 (including spacing)
;	R10	Cursor Start Register			bits 0-4 cursor start scan line
;							bits 5,6 control the Cursor Display Mode
;							bit 6 blink enable
;	R11	Cursor End Register			cursor end scan line
;
				; 40-Column Configurations
;
	.DO	VDU=40		; Original Acorn Monitor Configurations from Manual
;
				; For S Version 6845 CRT Controller (Interlaced)
CRTCSV:	.DB	$3F		; R0	64 characters per line (15,625kHz)
	.DB	COLS		; R1	40 displayed characters per line
	.DB	$34		; R2	(52) start of horizontal sync - 52uS
	.DB	$44		; R3	bits 0-3	(4) width of horizontal sync pulse - 4uS
				;	bits 4-7	(4) vertical sync pulse width - 256uS
	.DB	$1E		; R4	(30) vertical frequency - 15.625kHz (31x10x64uS)
	.DB	$02		; R5	(2) adjustment to vertical frequency (2x64uS) giving a total of 19.968mS i.e. 50Hz (actually 50.08Hz)
	.DB	ROWS		; R6	25 displayed character rows
	.DB	$1B		; R7	(27) position of vertical sync pulse - 17.28mS
	.DB	$03		; R8	bits 0,1	3 = Interlace Sync & Video Mode
				;	bits 4,5	0 = DISPTMG non-skew
				;	bits 6,7	0 = Cursor non-skew
	.DB	$12		; R9	(18) 20 scan lines/character (10 interlaced)
	.DB	$70		; R10	bits 0-4	(16) cursor start scan line - 16
				; 	bits 5,6	(1,1) blink enabled with frequency 1/32 of vertical field rate
	.DB	$13		; R11	bit 6		(19) underlined cursor
;
				; For R Version 6845 CRT Controller (Non-Interlaced)
CRTCRV:	.DB	$3F		; R0	64 characters per line (15,625kHz)
	.DB	COLS		; R1	40 displayed characters per line
	.DB	$34		; R2	(52) start of horizontal sync - 52uS
	.DB	$04		; R3	bits 0-3	(0) width of horizontal sync pulse - 0uS
				; 	bits 4-7	(4) vertical sync pulse width - 256uS
	.DB	$1E		; R4	(30) vertical frequency - 15.625kHz (31x10x64uS)
	.DB	$02		; R5	(2) adjustment to vertical frequency (2x64uS) giving a total of 19.968mS i.e. 50Hz (actually 50.08Hz)
	.DB	ROWS		; R6	25 displayed character rows
	.DB	$1B		; R7	(27) position of vertical sync pulse - 17.28mS
	.DB	$00		; R8	bits 0,1	0 = Non-interlace Mode
				;	bits 4,5	0 = DISPTMG non-skew
				;	bits 6,7	0 = Cursor non-skew
	.DB	$09		; R9	(9) 10 scan lines/character
	.DB	$68		; R10	bits 0-4	(8) cursor start scan line - 8
				; 	bits 5,6	(1,1) blink enabled with frequency 1/32 of vertical field rate
	.DB	$09		; R11	bit 6		(9) underlined cursor
;
	.FI
;
	.DO	VDU=41		; Alternative Configurations which match System COS/DOS40B
;
				; For S Version 6845 CRT Controller (Interlaced)
CRTCSV:	.DB	$3F		; R0	64 characters per line (15,625kHz)
	.DB	COLS		; R1	40 displayed characters per line
	.DB	$33		; R2	(51) start of horizontal sync - 51uS
	.DB	$44		; R3	bits 0-3	(4) width of horizontal sync pulse - 4uS
				; 	bits 4-7	(4) vertical sync pulse width - 256uS
	.DB	$1E		; R4	(30) vertical frequency - 15.625kHz (31x10x64uS)
	.DB	$02		; R5	(2) adjustment to vertical frequency (2x64uS) giving a total of 19.968mS i.e. 50Hz (actually 50.08Hz)
	.DB	ROWS		; R6	25 displayed character rows
	.DB	$1B		; R7	(27) position of vertical sync pulse - 17.28mS
	.DB	$03		; R8	bits 0,1	3 = Interlace Sync & Video Mode
				;	bits 4,5	0 = DISPTMG non-skew
				;	bits 6,7	0 = Cursor non-skew
	.DB	$12		; R9	(18) 20 scan lines/character (10 interlaced)
	.DB	$72		; R10	bits 0-4	(2) cursor start scan line - 
				; 	bits 5,6	(1,1) blink enabled with frequency 1/32 of vertical field rate
	.DB	$13		; R11	bit 6		(19) underlined cursor
;
				; For R Version 6845 CRT Controller (Non-Interlaced)
CRTCRV:	.DB	$3F		; R0	64 characters per line (15,625kHz)
	.DB	COLS		; R1	40 displayed characters per line
	.DB	$33		; R2	(51) start of horizontal sync - 51uS
	.DB	$04		; R3	bits 0-3	(0) width of horizontal sync pulse - 0uS
				; 	bits 4-7	(4) vertical sync pulse width - 256uS
	.DB	$1E		; R4	(30) vertical frequency - 15.625kHz (31x10x64uS)
	.DB	$02		; R5	(2) adjustment to vertical frequency (2x64uS) giving a total of 19.968mS i.e. 50Hz (actually 50.08Hz)
	.DB	ROWS		; R6	25 displayed character rows
	.DB	$1B		; R7	(27) position of vertical sync pulse - 17.28mS
	.DB	$00		; R8	bits 0,1	0 = Non-interlace Mode
				;	bits 4,5	0 = DISPTMG non-skew
				;	bits 6,7	0 = Cursor non-skew
	.DB	$09		; R9	(9) 10 scan lines/character
	.DB	$68		; R10	bits 0-4	(8) cursor start scan line - 8
				; 	bits 5,6	(1,1) blink enabled with frequency 1/32 of vertical field rate
	.DB	$09		; R11	bit 6		(9) underlined cursor
;
	.FI
;
				; 80-Column Configurations

	.DO	VDU=80		; These configurations match the '41' config  (taken from System COS/DOS80B)
;
				; For S Version 6845 CRT Controller (Interlaced)
CRTCSV:	.DB	$7F		; R0	128 characters per line (31,250kHz)
	.DB	COLS		; R1	40 displayed characters per line
	.DB	$60		; R2	(96) start of horizontal sync - 96uS
	.DB	$28		; R3	bits 0-3	(8) width of horizontal sync pulse - 4uS
				; 	bits 4-7	(2) vertical sync pulse width - 256uS
	.DB	$1E		; R4	(30) vertical frequency - 15.625kHz (31x10x64uS)
	.DB	$02		; R5	(2) adjustment to vertical frequency (2x64uS) giving a total of 19.968mS i.e. 50Hz (actually 50.08Hz)
	.DB	ROWS		; R6	25 displayed character rows
	.DB	$1B		; R7	(27) position of vertical sync pulse - 17.28mS
	.DB	$40		; R8	bits 0,1	0 = Non-interlace Mode
				;	bits 4,5	0 = DISPTMG non-skew
				;	bits 6,7	4 = Cursor two-character skew
	.DB	$09		; R9	(09) 20 scan lines/character (09 interlaced)
	.DB	$68		; R10	bits 0-4	(8) cursor start scan line - 
				; 	bits 5,6	(1,1) blink enabled with frequency 1/32 of vertcial field rate
	.DB	$09		; R11	bit 6		(09) underlined cursor
;
				; For R Version 6845 CRT Controller (Non-Interlaced)
CRTCRV:	.DB	$7F		; R0	128 characters per line (31,250kHz)
	.DB	COLS		; R1	40 displayed characters per line
	.DB	$60		; R2	(96) start of horizontal sync - 96uS
	.DB	$28		; R3	bits 0-3	(8) width of horizontal sync pulse - 4uS
				; 	bits 4-7	(2) vertical sync pulse width - 256uS
	.DB	$1E		; R4	(30) vertical frequency - 15.625kHz (31x10x64uS)
	.DB	$02		; R5	(2) adjustment to vertical frequency (2x64uS) giving a total of 19.968mS i.e. 50Hz (actually 50.08Hz)
	.DB	ROWS		; R6	25 displayed character rows
	.DB	$1B		; R7	(27) position of vertical sync pulse - 17.28mS
	.DB	$40		; R8	bit 0,1		0 = Non-interlace Mode
				;	bits 4,5	0 = DISPTMG non-skew
				;	bits 6,7	4 = Cursor two-character skew
	.DB	$09		; R9	(9) 10 scan lines/character
	.DB	$68		; R10	bits 0-4	(8) cursor start scan line - 8
				; 	bits 5,6	(1,1) blink enabled with frequency 1/32 of vertical field rate
	.DB	$09		; R11	bit 6		(9) underlined cursor
;
	.FI
;
; Set up table 1, copied directly to RAM at RTAB1
;
PTAB1	.DW	ISTACK		; Initial stack pointer
	.DW	0		; Trace initially off
	.DB	BSPACE		; Character echoes on rubout
	.DB	$FF		; Echo on, line buffer on
	.DB	0		; Printer off
	.DB	LF		; Do not send line feeds, use CR only to printer
	.DW	BAUD		; Initial baud rate for cassette
	.DW	DISPLA		; Console output
	.DW	GETCHR		; Console input
	.DW	MCASOP		; Cassette output
	.DW	MCASIN		; Cassette input
	.DW	PRINT		; Printer routine
	.DW	FUNCTS		; Display function table
	.DW	CMNDS		; Monitor command table
	.DW	UNUSED		; Initial timer 1 routine
	.DW	$0000		; No memory interpret
	.DW	UNUSED		; Reserved vector
	.DW	UNUSED		; SWI3
	.DW	UNUSED		; SWI2
	.DW	UNUSED		; FIRQ
	.DW	IRQHAN		; IRQ
	.DW	SWIHAN		; SWI
	.DW	UNUSED		; NMI
	.DW	$0000		; Initial load offset
PTAB2:	.EQ	$		; End of table
;
; This table contains the standard set of commands provided by the monitor
;
CMNDS:	.DB	CMNDE-CMNDS/3-1	; Number of entries
	.AS	'G'
	.DW	GOUSER-CMNDS	; Go to program
	.AS	'M'
	.DW	MEM-CMNDS	; Memory examine
	.AS	'R'
	.DW	EXREG-CMNDS	; Examine registers
	.AS	'P'
	.DW	RESUME-CMNDS	; Proceed after break
	.AS	'T'
	.DW	TRACEN-CMNDS	; Set trace number
	.AS	'S'
	.DW	SAVE-CMNDS	; Save on cassette
	.AS	'L'
	.DW	LOAD-CMNDS	; Load from cassette
	.AS	'V'
	.DW	BRKSET-CMNDS	; Set break address
	.AS	'D'
	.DW	BOOT-CMNDS	; Disc bootstrap
	.AS	'C'
	.DW	PCNTL-CMNDS	; Copy to printer
	.DB	SPACE
	.DW	ANRTS-CMNDS	; Ignore spaces
	.DB	COMMA
	.DW	ANRTS-CMNDS	; Ignore commas
	.AS	'.'
	.DW	TRACE-CMNDS	; Do trace operation
	.AS	'F'
	.DW	LOAD7-CMNDS	; Finish file load
	.DB	1
	.DW	BADCMD-CMNDS	; Default is query user
CMNDE:	.EQ	$
;
; This table contains the standard functions provided by the VDU control programs
;
FUNCTS:
	.DO	VER<>495	; (all versions except 4.95)
	.DB	FUNCTE-FUNCTS/3-1 ; Number of entries
;
	.DB	CR		; Carriage return
	.DW	DOCR-FUNCTS
;
	.DB	LF		; Linefeed
	.DW	DOLF-FUNCTS
;
	.DB	BSPACE		; Backspace
	.DW	DORUB-FUNCTS
;
	.DB	FFEED		; Form feed
	.DW	CRTCI-FUNCTS
;
	.DB	1
	.DW	SIMCHR-FUNCTS	; Default is display it
	.FI
;
	.DO	VER=495		; (version 4.95 only)
	.DB	FUNCTE-FUNCTS/3 ; Number of entries
;
	.DB	CR		; Carriage return
	.DW	DOCR-FUNCTS
;
	.DB	LF		; Linefeed
	.DW	DOLF-FUNCTS
;
	.DB	$08		; Backspace ??
	.DW	DORUB-FUNCTS
;
	.DB	$01		; Form feed
	.DW	$F9EA		; CRTCI-FUNCTS	; $F9EA ??
	.DB	1
	.DW	SIMCHR-FUNCTS	; Default is display it
	.FI
;
FUNCTE:	.EQ	$
;
; This is the list of strings used by the monitor
;
MQRY:	.AS	"Rom?"
SCRLF:	.DB	CR,LF,0
TITLES:	.AS	"CC  A  B DP    X    Y    U   PC    S"
	.DB	CR,LF,0
PCMESS:	.DB	CR,LF
	.AS	"PC]"
	.DB	0
CQRY:	.AS	"What is:"
	.DB	0
IERR:	.AS	'I'
LQRY:	.AS	'-'
DQRY:	.AS	"Err "
	.DB	0
;
; This is the set of indirect jumps to redirect the interrupt vector addresses
;
RESVI:	JMP	[IRESV]
SWI3I:	JMP	[ISWI3]
SWI2I:	JMP	[ISWI2]
FIRQI:	JMP	[IFIRQ]
IRQI:	JMP	[IIRQ]
SWII:	JMP	[ISWI]
NMII:	JMP	[INMI]
;
; The following hardware vectors reside in the top 16 bytes of memory
; when the monitor is in its standard position
;
	.DO	VER=490		; (original Acorn version 4.90 only)
	.DB	$00		; packing
	.EL			; (all other versions)
	.DB	$FF		; packing
	.FI
;
	.DW	RESVI
	.DW	SWI3I
	.DW	SWI2I
	.DW	FIRQI
	.DW	IRQI
	.DW	SWII
	.DW	NMII
	.DW	RESET
