;
;=======================================================================
; HDIAG Diagmostic ROM
;=======================================================================
;
; HDIAG is a framework for a diagnotic environment intended to be
; suitable for all systems supported by RomWBW.  RomWBW expects hardware
; to be fully functional making it difficult to use it to initially
; check out a system.  HDIAG is explicitly constructed to be as simple
; as possible.
;
; There is only a single variant of HDIAG that is built.  HDIAG is
; designed to detect the environment it is operating under at startup
; and dynamically adapt to it.
;
; HDIAG can be assembled to boot in one of 2 modes (rom or application)
; as described below.  When compiled, you must define exactly one of the
; following macros:
;
; - ROMBOOT: Boot from a rom bank
;
;   When ROMBOOT is defined, the file is assembled to be imbedded at the
;   start of a rom assuming that the cpu will start execution at address
;   0.
;
; - APPBOOT: Boot as a CP/M style application file
;
;   When APPBOOT is defined, the file is assembled as a CP/M application
;   assuming that it will be loaded at 100h by the CP/M (or compatible)
;   OS.
;
	.module	MAIN
;
#include "hdiag.inc"
#include "z180.inc"
; 
;=======================================================================
; Page Zero Definition
;=======================================================================
;
; Generic page zero setup.  Only applies to ROMBOOT startup mode.
;
#ifdef ROMBOOT 
;
	.org	$0000
;
	jp	_start		; rst $00: jump to boot code
	.fill	($08-$)
	ret				; rst $08
	.fill	($10-$)
	ret				; rst $10
	.fill	($18-$)
	ret				; rst $18
	.fill	($20-$)
	ret				; rst $20
	.fill	($28-$)
	ret				; rst $28
	.fill	($30-$)
	ret				; rst $30
	.fill	($38-$)
	reti				; h/w int return
	.fill	($66-$)
	retn				; h/w nmi return
	.fill	($100-$)		; pad remainder of page zero
;
#else
	.org	$0100
;
#endif
; 
;=======================================================================
; Startup
;=======================================================================
;
; Before transitioning to RAM, we need to determine the memory
; manager to use because some platforms will not have any RAM mapped
; to the upper 32K of CPU address space until the memory manager
; is initialized.
;
_start:
;
; Discover CPU Type and Memory Manager
;
; Some of this code is derived from UNA by John Coffman
;
; CPU and memory manager constants are defined in hdiag.inc.
;
	di			; no interrupts allowed
;
	ld	a,$80
	out	($0D),a
;
	; Use D for memory manager, and E for CPU Type
	ld	de,0		; assume unknown for both
;
; Start with CPU type detection
;
	; Check for Z80
	inc	e		; not sure how to do it, just assume it
;
	; Test for Z180 using mlt
	ex	de,hl		; save DE to HL for test
	ld	de,$0506	; 5 x 6
	mlt	de		; de = 30 if Z180
	ld	a,e		; check if multiply happened
	ex	de,hl		; restore DE now
	cp	30
	jr	nz,_tryZ280	; if != 30, not a Z180, try Z280
	inc	e		; Z80180 or better
;
#ifdef APPBOOT
;
	; Reset Z180 internal register base to zero no matter where
	; it might have previously been mapped.
	xor	a
	out0	($7F),a
	out0	($BF),a
	out0	($FF),a
;	
#endif
;
	; Test for older S-class (rev K)
	in0	a,(z180_ccr)	; supposedly only on s-class
	inc	a		; FF -> 0
	jr	z,_z180res	; if zero, pre-S, HD61480 or equiv
	inc	e		; Z8S180 rev K (SL1960) or better
;
	; Test for newer S-class (rev N)
	; On older S-class, asci time constant reg does not exist
	; and will always read back as $FF
	out0	(z180_astc1l),d	; d = 0 at this point
	in0	a,(z180_astc1l)	; asci time constant reg
	inc	a		; FF -> 0
	jr	z,_z180res	; if zero, rev-K
	inc	e		; otherwise Z8S180 rev N w/ asci brg
	jr	_z180res	; go to Z180 reset
;
_tryZ280:
	; Test for Z280 per Zilog doc
	ld	a,$40		; initialize the operand
	.db	$cb,$37		; this instruction will set the s flag
				; on the Z80 cpu and clear the s flag
				; on the Z280 mpu.
	jp	m,_z80res	; if not Z280, we are Z80
	ld	l,hd_cpu_z280	; we are Z280
	jr	_z280res	; handle Z280 initialization
;
_z80res:
	ld	a,$01
	out	(0),a
	; Reset Z80 here (is there anything?)
	jr	_cpu1
;
_z180res:
;
	; Reset z180 registers here
	; Set CPU speed to oscillator x 1
	xor	a
	out0	(z180_cmr),a
	ld	a,$80
	out0	(z180_ccr),a
	; Set default wait states
	ld	a,$%00000100	; mem wait=0, i/o wait=+1
	out0	(z180_dcntl),a	
;
#ifdef ROMBOOT
	; Setup Z180 MMU
	; Keep ROM page zero in lower 32K!!!
	ld	a,$80		; Common Base @ 32K, Bank Base @ 0K
	out0	(z180_cbar),a
	xor	a		; Physical address zero
	out0	(z180_cbr),a	; ... for Common Base
	out0	(z180_bbr),a	; ... and Bank Base
#else
	xor	a		; Physical address zero
	out0	(z180_cbr),a	; ... for Common Base
#endif
;	
	jr	_cpu1
;
_z280res:
	; Reset Z280 registers here
	; Make sure memmgr is reset to defaults!
	jr	_cpu1
;
; At this point, we should have the cpu type in the L register.
; Now determine the memory manager.  In general, we just attempt to
; enable the different memory managers and keep testing to see if
; a RAM bank has appeared in the common area.
;
_cpu1:
	ld	a,$02
	out	($0D),a
;
	; Reset Zeta 2 memory manager (in case it exists)
#ifdef ROMBOOT
	xor	a			; disable value
	out	($7C),a			; write it
	xor	a			; 16K ROM page 0
	out	($78),a
	inc	a			; 16K ROM page 1
	out	($79),a
#endif
	ld	a,2			; 16K ROM page 2
	out	($7A),a
	inc	a			; 16K ROM page 3
	out	($7B),a
;
	; Reset N8 supplemental memory manager
	; *** Need to implement this ***
;
	ld	a,$03
	out	($0D),a
;
	; There is no way to disable the common (himem) RAM bank
	; under the SBC memory manager.  Here, we just see if there
	; is RAM in himem and, if so, assume the SBC memory manager.
	; If not, we continue on to test the other possible memory
	; managers.
	inc	d			; assume SBC memory manager
	ld	ix,$FFFF		; point to himem
	ld	a,$A5			; an unlikely bit pattern
	ld	(ix),a			; write the value
	cp	(ix+0)			; check value written
	jr	z,_cpu2		; SBC memory manager, we are done!
;
	ld	a,$04
	out	($0D),a
;
	; Now test for Zeta 2 memory manager
	; Start by initializing and enabling the page registers
	inc	d			; assume Zeta 2 memory manager
#ifdef ROMBOOT
	xor	a			; ROM page 0
	out	($78),a
	inc	a			; ROM page 1
	out	($79),a
#endif
	ld	a,$20			; first RAM page
	out	($7A),a
	inc	a			; second RAM page
	out	($7B),a
	ld	a,1			; enable paging
	out	($7C),a
;
	ld	a,$05
	out	($0D),a
;
	; Test himem RAM again
	ld	ix,$FFFF		; point to himem
	ld	a,$A5			; an unlikely bit pattern
	ld	(ix),a			; write the value
	cp	(ix+0)			; check value written
	jr	z,_cpu2		; Zeta 2 memory manager, we are done!
;
	ld	a,$06
	out	($0D),a
;
	; If neither SBC nor Zeta 2, then we assume the memory
	; manager is the native memory manager onboard the CPU
	ld	a,e		; get cpu type
	cp	hd_cpu_z280	; Z280?
	jr	z,_z280init	; handle it
	or	a		; Z80?
	jr	nz,_z180init	; if no, go handle Z180
;
	; If we get here, we are stuck.  We believe we are a Z80
	; but both of the Z80 memory manager tests failed.
_halt:
	ld	a,$07
	out	($0D),a
;
	ld	hl,_str_halt
	call	prtstr
	halt			; give up
;
_z180init:
	; Initialize Z180 memory manager
	; Put first RAM page into himem (commmon)
	ld	a,$80
	out0	(z180_cbr),a
;
	ld	d,hd_mm_z180
	jr	_cpu2

_N8init:
	; Initialize N8 memory manager
	ld	d,hd_mm_n8
	jr	_cpu2
	
_z280init:
	; Initialize Z280 memory manager
	ld	d,hd_mm_z280
	jr	_cpu2
;
_cpu2:
	ld	a,$08
	out	($0D),a
;
	ex	de,hl			; cpu/memmgr values to HL
	ld	sp,hl			; and stash SP reg
;
; Transition to upper memory (omit page zero)
;
	ld	hl,$0000+$100
	ld	de,$8000+$100
	ld	bc,$8000-$100
	ldir
	jp	_start2
;
	.org	$ + $8000
;
; 
;=======================================================================
; Post-relocation Startup
;=======================================================================
;
_start2:
;
	ld	a,$09
	out	($0D),a
;
; Copy FF page image to real location.  Use a decrementing copy
; just in case page image is within $100 bytes of $FF00.  Very
; unlikely, but just to be safe.
;
	ld	hl,_ffimg+$FF		; Start at end of image
	ld	de,$FFFF		; To top of RAM
	ld	bc,$100			; Copy 1 page
	lddr				; Execute
;
; Recover cpu/memmgr codes stashed in SP and
; save them in FF page
;
	ld	(_cpu),sp
;
; Now we establish a real stack (finally!)
;
	ld	sp,$FF00		; Stack just below FF page
;
; Probe and initialize serial port console driver.  We just go
; through the options stopping at the first one that works.  The
; order of polling below is intended to find the most reasonable
; console port.
;
;
	ld	a,$0A
	out	($0D),a
;
;
; This should be table driven!!!
;
	; Z280 UART
	ld	ix,ser_z2u
	call	jpix
	jr	z,_start3
	; ASCI
	ld	ix,ser_asci
	call	jpix
	jr	z,_start3
	; UART
	ld	ix,ser_uart
	call	jpix
	jr	z,_start3
	; ACIA
	ld	ix,ser_acia
	call	jpix
	jr	z,_start3
	; SIO
	ld	ix,ser_sio
	call	jpix
	jr	z,_start3
;
	; Ugh, nothing worked
	ld	a,$0C
	out	($0D),a
	halt
;
;
;
_start3:
;
	ld	a,$0D
	out	($0D),a
;
; Copy selected console serial driver vector table into place
;
	push	ix
	pop	hl
	ld	de,_jptbl
	ld	bc,5*3
	ldir
;
;
;
	; Setup memory manager
;
	; Map RAM page 0 to lower 32K
;
	; Setup zero page in lower 32K
;
;
;
_restart:
;
	ld	hl,_str_banner
	call	prtstr
;
	; Print CPU model
	ld	de,_str_cputag
	ld	hl,_str_cpu
	ld	a,(hd_cpu)
	call	prtstrtbl
;
	; Print memory manager
	ld	de,_str_mmtag
	ld	hl,_str_mm
	ld	a,(hd_mmgr)
	call	prtstrtbl
;
	call	cin
	jp	_restart
;
;
;
	jp	_halt
;
;=======================================================================
; Helper functions
;=======================================================================
;
#include "util.asm"
;
;=======================================================================
; Console I/O modules
;=======================================================================
;
; Include all serial drivers
;
	.module UART
ser_uart	.equ	$
#include "uart.asm"
;
	.module ASCI
ser_asci	.equ	$
#include "asci.asm"
;
	.module ACIA
ser_acia	.equ	$
#include "acia.asm"
;
	.module SIO
ser_sio		.equ	$
#include "sio.asm"
;
	.module Z2U
ser_z2u		.equ	$
#include "z2u.asm"
;
	.module MAIN
;
;=======================================================================
; Internal variables and literals
;=======================================================================
;
_str_banner	.db	"\r\n\r\nHDIAG v0.90",0
_str_cputag	.db	"\r\nCPU Model: ",0
_str_mmtag	.db	"\r\nMemory Manager: ",0
_str_halt	.db	"\r\n\r\n*** System HALTed ***",0
;
_str_cpu:
	.dw	_str_unknown
	.dw	_str_cpuz80
	.dw	_str_cpuz180
	.dw	_str_cpuz180K
	.dw	_str_cpuz180N
	.dw	_str_cpuz280
;
_str_unknown	.db	"Unknown",0
_str_cpuz80	.db	"Z80",0
_str_cpuz180	.db	"Z80180",0
_str_cpuz180K	.db	"Z8S180-K",0
_str_cpuz180N	.db	"Z8S180-N",0
_str_cpuz280	.db	"Z80280",0
;
_str_mm:
	.dw	_str_unknown
	.dw	_str_mmsbc
	.dw	_str_mmz2
	.dw	_str_mmz180
	.dw	_str_mmn8
	.dw	_str_mmz280
;
_str_mmsbc	.db	"SBC/MBC",0
_str_mmz2	.db	"Zeta2/RCBUS",0
_str_mmz180	.db	"Z180 Native",0
_str_mmn8	.db	"Z180 Native (N8)",0
_str_mmz280	.db	"Z280 Native",0
;
;=======================================================================
; Top page of CPU RAM, global variables and function jump table
;=======================================================================
;
; This area is defined here, but copied to the top page of RAM at
; initialization!
;
; The top page (256 bytes) of CPU address space is used to maintain
; a jump table of functions available to all diagnostic modules.
; It also contains some global variables at fixed locations for use
; by diagnostic modules.
;
; The console function addresses are set dynamically based on the
; console driver that is installed at boot.
;
_ffimg	.equ	$
;
	.org	$FF00			; Set code org
;
_jptbl:
cinit	jp	0			; Console port initialization
cin	jp	0			; Console read byte
cout	jp	0			; Console write byte
cist	jp	0			; Console input status
cost	jp	0			; Console output status
;
	.fill	$FF80-$
_cpu	.db	0			; CPU type
_mmgr	.db	0			; Memory manager type
;
	.fill	$10000-$
	.end


