; Z80TYPE.ASM - DETERMINE AND PRINT Z80 CPU TYPE
; WRITTEN BY SERGEY KISELEV <SKISELEV@GMAIL.COM>
; Adapted by Jan Bobrowski, 2024-10-04

; Use
; mktap -b -p ';;;' z80type 2 <z80type.asm >z80type.tap
; mktap z80type 24576 <z80type.bin >>z80type.tap
; [torinak.com/~jb/zx/#mktap]

;;; 1 GOTO 5
;;; 2 CLEAR 24575
;;; 3 LOAD ""CODE
;;; 4 CLS
;;; 5 RANDOMIZE USR 24576

CHAN_OPEN equ 0x1601
BORDCR equ 0x5C48

	org 0x6000

	ld a, 2
	call CHAN_OPEN

	LD	DE,MSGSIGNIN
	CALL	PRINTSTR

	CALL	TESTCMOS
	LD	HL,ISCMOS
	LD	(HL),A		; store result to ISCMOS

	CALL	TESTU880
	LD	HL,ISU880
	LD	(HL),A		; store result to ISU880

	CALL	TESTXY
	LD	HL,XYRESULT
	LD	(HL),A

;-------------------------------------------------------------------------
; Debug
	LD	HL,DEBUG
	LD	A,(HL)
	CP	0
	JP	Z,DETECTCPU

	LD	DE,MSGRAWCMOS	; display CMOS test result
	CALL	PRINTSTR
	LD	HL,ISCMOS
	LD	A,(HL)
	cp 055h
	jr z, .unkn
	CALL	PRINTHEX
	jr .next
.unkn:	ld de,msgunkn
	call PRINTSTR
.next:

	LD	HL,ISU880
	LD	A,(HL)		; store result to ISU880
	LD	DE,MSGRAWU880	; display U880 test result
	CALL	PRINTSTR
	CALL	PRINTHEX

	LD	HL,XYRESULT
	LD	A,(HL)
	LD	DE,MSGRAWXY	; display XF/YF flags test result
	CALL	PRINTSTR
	CALL	PRINTHEX

	LD	DE,MSGCRLF
	CALL	PRINTSTR

	CALL	TESTFLAGS	; TEST HOW FLAGS SCF AFFECTS FLAGS

;-------------------------------------------------------------------------
; CPU detection logic
DETECTCPU:

	LD	DE,MSGCPUTYPE
	CALL	PRINTSTR

; check for U880 CPU

	LD	HL,ISU880
	LD	A,(HL)
	CP	0		; Is it a U880?
	JP	Z,CHECKZ80	; check Z80 flavor

	LD	HL,XYRESULT
	LD	A,(HL)
	CP	0FFH		; does it always set XF/YF?
	LD	DE,MSGU880NEW
	JP	Z,DONE		; jump if a new U880/Thesys Z80
	LD	DE,MSGU880OLD
	JP	DONE

; check for Z80 type
CHECKZ80:

	LD	HL,ISCMOS
	LD	A,(HL)
	; A value of 55 indicate no test performend; assuming NMOS
	CP	0ffh		; Is it a NMOS CPU?
	JP	Z,CHECKCMOS	; check CMOS Z80 flavor

; check for Sharp LH5080A
	LD	HL,XYRESULT
	LD	A,(HL)
	CP	30H
	JP	Z,SHARPLH5080A
	CP	0FFH		; does it always set XF/YF?
	JP	Z,NMOSZ80
	CP	0FDH		; does it sometimes not set XF when FLAGS.3=1?
	JP	Z,NECU780C
	CP	0F4H
	JP	Z,KR1858VM1
	LD	DE,MSGNMOSUNKNOWN
	JP	DONE

SHARPLH5080A:
	LD	DE,MSGSHARPLH5080A
	JP	DONE

NMOSZ80:
	LD	DE,MSGNMOSZ80
	JP	DONE

NECU780C:
	LD	DE,MSGNECD780C
	JP	DONE

KR1858VM1:
	LD	DE,MSGKR1858VM1
	JP	DONE

CHECKCMOS:
	LD	HL,XYRESULT
	LD	A,(HL)
	CP	0FFH		; does it always set XF/YF?
	JP	Z,CMOSZ80
	CP	3FH		; does it never set YF when A.5=1?
	JP	Z,TOSHIBA

; test for NEC D70008AC. These CPUs seem to behave as following:
; A.5=1 & F.5=0 => YF=1
; A.3=1 & F.3=0 => XF is not set at all, or only sometimes is set
; A.5=0 & F.5=1 => YF is sometimes set
; A.3=0 & F.3=1 => XF is sometimes set
; Note: All of 3 D70008AC that I have behave a bit differently here
;       this might need to be updated when more tests are done
	CP	20H		; YF is often set when A.5=1?
	JP	NC,CMOSUNKNOWN	; XYRESULT > 1Fh, not a NEC...
	AND	0FH		; F.5=1 & A.5=0 and F.3=1 & A.3=0 results
	CP	03H		; F.5=1 & A.5=0 never result in YF set?
	JP	C,CMOSUNKNOWN
	AND	03H		; F.3=1 & A.3=0 results
	JP	NZ,NEC

CMOSUNKNOWN:
	LD	DE,MSGCMOSUNKNOWN
	JP	DONE

CMOSZ80:
	LD	DE,MSGCMOSZ80
	JP	DONE

TOSHIBA:
	LD	DE,MSGTOSHIBA
	JP	DONE

NEC:
	LD	DE,MSGNECD70008AC
	JP	DONE

DONE:
	CALL	PRINTSTR
	LD	DE,MSGCRLF
	CALL	PRINTSTR
	RET

;-------------------------------------------------------------------------
; TESTCMOS - Test if the CPU is a CMOS variety according to OUT (C),0 test
; Note: CMOS Sharp LH5080A is reported as NMOS
; Input:
;	None
; Output:
;	A = 00 - NMOS
;	A = FF - CMOS
;------------------------------------------------------------------------- 
TESTCMOS:
	ld bc, 0FFFDh
	xor a
	out (c), a
	ld b, 0BFh
	ld a,55h
	out (c), a
	ld b, 0FFh
	in d, (c)
	cp d
	ret nz ; No such port
	ld b, 0BFh
	DB	0EDH, 071H	; UNDOCUMENTED OUT (C),<0|0FFH> INSTRUCTION
				; WRITE 0 OR FF TO THE SIO INTERRUPT VECTOR
	ld b, 0FFh
	in a, (c)
	ret

;-------------------------------------------------------------------------
; TESTU880 - Check if the CPU is MME U880 or Thesys Z80
; Input:
;	None
; Output:
;	A = 0 - Non-U880
;	A = 1 - U880
;-------------------------------------------------------------------------
TESTU880:
	LD	HL,0FFFFH
	ld a, (BORDCR)
	rra
	rra
	rra
	ld (hl), a
	xor a
	ld bc, 0x01fe
	SCF
	outi
	adc a, 0
	ret

;-------------------------------------------------------------------------
; TESTXY - Tests how SCF (STC) instruction affects FLAGS.5 (YF) and FLAGS.3 (XF)
; Input:
;	None
; Output:
;	A[7:6] - YF result of F = 0, A = C | 0x20 & 0xF7
;	A[5:4] - XF result of F = 0, A = C | 0x08 & 0xDF
;	A[3:2] - YF result of F = C | 0x20 & 0xF7, A = 0
;	A[1:0] - XF result of F = C | 0x08 & 0xDF, A = 0
;	Where the result bits set as follows:
;	00 - YF/XF always set as 0
;	11 - YF/XF always set as 1
;	01 - YF/XF most of the time set as 0
;	10 - YF/XF most of the time set as 1
;-------------------------------------------------------------------------
TESTXY:
	LD	C,0FFH		; loop counter
	di

TESTXY1:
	LD	HL,XFYFCOUNT	; results stored here

; check F = 0, A = C | 0x20 & 0xF7
	LD	E,00H		; FLAGS = 0
	LD	A,C
	OR	020H		; A.5 = 1
	AND	0F7H		; A.3 = 0
	LD	D,A		; A = C | 0x20 & 0xF7
	PUSH	DE		; PUSH DE TO THE STACK
	POP	AF		; POP A AND FLAGS FROM THE STACK (DE)
	SCF			; SET CF FLAG, DEPENDING ON THE CPU TYPE THIS
				; ALSO MIGHT CHANGE YF AND XF FLAGS
	CALL	STOREYCOUNT

; check F = 0, A = C | 0x08 & 0xDF
	LD	E,00H		; FLAGS = 0
	LD	A,C
	OR	08H		; A.3 = 1
	AND	0DFH		; A.5 = 0
	LD	D,A		; A = C | 0x08 & 0xDF
	PUSH	DE		; PUSH DE TO THE STACK
	POP	AF		; POP A AND FLAGS FROM THE STACK (DE)
	SCF			; SET CF FLAG, DEPENDING ON THE CPU TYPE THIS
				; ALSO MIGHT CHANGE YF AND XF FLAGS
	CALL	STOREXCOUNT

; check F = C | 0x20 & 0xF7, A = 0
	LD	A,C
	OR	020H		; FLAGS.5 = 1
	AND	0F7H		; FLAGS.3 = 0
	LD	E,A		; FLAGS = C | 0x20 & 0xF7
	LD	D,00H		; A = 0
	PUSH	DE		; PUSH DE TO THE STACK
	POP	AF		; POP A AND FLAGS FROM THE STACK (DE)
	SCF			; SET CF FLAG, DEPENDING ON THE CPU TYPE THIS
				; ALSO MIGHT CHANGE YF AND XF FLAGS
	CALL	STOREYCOUNT

; check F = C | 0x08 & 0xDF, A = 0
	LD	A,C
	OR	08H		; FLAGS.3 = 1
	AND	0DFH		; FLAGS.5 = 0
	LD	E,A		; FLAGS = C | 0x08 & 0xDF
	LD	D,00H		; A = 0
	PUSH	DE		; PUSH DE TO THE STACK
	POP	AF		; POP A AND FLAGS FROM THE STACK (DE)
	SCF			; SET CF FLAG, DEPENDING ON THE CPU TYPE THIS
				; ALSO MIGHT CHANGE YF AND XF FLAGS
	CALL	STOREXCOUNT

	DEC	C
	JP	NZ,TESTXY1
	ei

	LD	C,4		; iteration count - number of bytes
	LD	HL,XFYFCOUNT	; counters

TESTXY2:
	RLA
	RLA
	AND	0FCH		; zero two least significant bits
	LD	B,A		; store A to B
	LD	A,(HL)
	CP	7FH
	JP	NC,TESTXY3		; jump if the count is 0x80 or more
	CP	0
	JP	Z,TESTXY5		; the count is 0 leave bits at 0
	LD	A,1		; the count is between 1 and 0x7F, set result bits to 01
	JP	TESTXY5
TESTXY3:
	CP	0FFH
	LD	A,2		; the count is between 0x80 and 0xFE, set result bits to 10
	JP	NZ,TESTXY4
	LD	A,3		; the count is 0xFF, set result bits to 11
	JP	TESTXY5
TESTXY4:
; Bug!
;	LD	A,1		; the count is 0x7F or less, set result bits to 01
TESTXY5:
	OR	B
	INC	HL
	DEC	C
	JP	NZ,TESTXY2
	RET

;-------------------------------------------------------------------------
; STOREXCOUNT - Isolates and stores XF to the byte counter at (HL)
; Input:
;	FLAGS	- flags
;	HL	- pointer to the counters
; Output:
;	HL	- incremented by 1 (points to the next counter)
; Trashes A and DE
;-------------------------------------------------------------------------
STOREXCOUNT:
	PUSH	AF		; transfer flags
	POP	DE		; to E register
	LD	A,E
	AND	08H		; isolate XF
	JP	Z,STOREXDONE
	INC	(HL)		; increment the XF counter (HL)
STOREXDONE:
	INC	HL		; point to the next entry
	RET

;-------------------------------------------------------------------------
; STOREYCOUNT - Isolates and stores YF to the byte counter at (HL)
; Input:
;	FLAGS	- flags
;	HL	- pointer to the counters
; Output:
;	HL	- incremented by 1 (points to the next counter)
; Trashes A and DE
;-------------------------------------------------------------------------
STOREYCOUNT:
	PUSH	AF		; transfer flags
	POP	DE		; to E register
	LD	A,E
	AND	20H		; isolate YF
	JP	Z,STOREYDONE
	INC	(HL)		; increment the YF counter (HL)
STOREYDONE:
	INC	HL		; point to the next entry
	RET

;-------------------------------------------------------------------------
; TESTFLAGS - TEST HOW SCF INSTRUCTION AFFECTS YF AND XF FLAGS
; NOTE: YF IS FLAGS.5 AND XF IS FLAGS.3
; INPUT:
;	NONE
; OUTPUT:
;	PRINTED ON CONSOLE
;-------------------------------------------------------------------------
TESTFLAGS:
	LD	DE,MSGFLAGS
	call PRINTSTR
	LD	D,00H
TFLOOP1:
	LD	E,00H
TFLOOP2:
	PUSH	DE
	DI
	PUSH	DE		; PUSH DE TO THE STACK
	POP	AF		; POP A AND FLAGS FROM THE STACK (DE)
	CCF			; SET CF FLAG, DEPENDING ON THE CPU TYPE THIS
				; ALSO MIGHT CHANGE YF AND XF FLAGS
	PUSH	AF		; STORE A AND F
	POP	DE		; NEW FLAGS IN E
	EI
	LD	A,E		; FLAGS TO ACCUMULATOR
	POP	DE
	JP	CONT

PRINTFLAGS:
	CALL	PRINTHEX	; PRINT ACCUMULATOR
	LD	A,E		; FLAGS TO ACCUMULATOR
	POP	DE
	PUSH	AF
	LD	A,D		; PRINT ORIGINAL ACCUMULATOR(FLAGS)
	CALL	PRINTHEX
	POP	AF
	CALL	PRINTHEX	; PRINT NEW FLAGS
	PUSH	DE
	LD	DE,MSGCRLF
	CALL	PRINTSTR
	POP	DE
CONT:

	LD	HL,XFCOUNT	; POINT TO XF COUNTER
	RRCA			; BIT 3 TO CF
	RRCA
	RRCA
	RRCA
	JP	NC,TFLOOP4
	INC	(HL)		; INCREMENT COUNTER IF FLAG IS SET
	JP	NZ,TFLOOP4		; NO OVERFLOW
	INC	HL		; MOVE TO THE HIGH BIT
	INC	(HL)		; INCREMENT HIGHER BIT

TFLOOP4:
	LD	HL,YFCOUNT	; POINT TO YF COUNTER
	RRCA			; BIT 5 TO CF
	RRCA
	JP	NC,TFLOOP5
	INC	(HL)		; INCREMENT COUNTER IF FLAG IS SET
	JP	NZ,TFLOOP5		; NO OVERFLOW
	INC	HL		; MOVE TO THE HIGH BIT
	INC	(HL)		; INCREMENT HIGHER BIT
TFLOOP5:
	INC	E
	JP	NZ,TFLOOP2
	INC	D		; INCREMENT D
	JP	NZ,TFLOOP1

; PRINT VALUES
	LD	C,4		; 4 BYTES
	LD	HL,YFCOUNT+1	; POINT AT THE MSB
TFLOOP6:
	LD	A,(HL)
	CALL	PRINTHEX
	DEC	HL
	DEC	C
	JP	NZ,TFLOOP6		; PRINT NEXT DIGIT

	LD	DE,MSGCRLF
	jp PRINTSTR

; PRINT VALUES
	LD	HL,YFCOUNT+1	; MSB OF YF COUNT
	LD	A,(HL)
	CALL	PRINTHEX
	DEC	HL		; LSB OF YF COUNT
	LD	A,(HL)
	CALL	PRINTHEX
	LD	HL,XFCOUNT+1	; MSB OF XF COUNT
	LD	A,(HL)
	CALL	PRINTHEX
	DEC	HL		; LSB OF XF COUNT
	LD	A,(HL)
	CALL	PRINTHEX

	LD	DE,MSGCRLF
	jp PRINTSTR

;-------------------------------------------------------------------------
; PRINTHEX - PRINT BYTE IN HEXADECIMAL FORMAT
; INPUT:
;	A - BYTE TO PRINT
; OUTPUT:
;	NONE
;-------------------------------------------------------------------------
PRINTHEX:
	PUSH	BC
	PUSH	DE
	PUSH	HL
	PUSH	AF		; SAVE PRINTED VALUE ON THE STACK
	RRCA			; ROTATE HIGHER 4 BITS TO LOWER 4 BITS
	RRCA
	RRCA
	RRCA
	CALL	PRINTDIGIT	; PRINT HIGHER 4 BITS
	POP	AF		; RESTORE PRINTED VALUE
	PUSH	AF		; PUSH IT TO THE STACK AGAIN
	CALL	PRINTDIGIT	; PRINT LOWER 4 BITS
	POP	AF
	POP	HL
	POP	DE
	POP	BC
	RET

;-------------------------------------------------------------------------
; PRINTDIGIT - PRINT DIGIT IN HEXADECIMAL FORMAT
; INPUT:
;	A - DIGIT TO PRINT, LOWER 4 BITS 
; OUTPUT:
;	NONE
; TRASHES REGISTERS A, FLAGS, BC, DE, HL
;-------------------------------------------------------------------------
PRINTDIGIT:
	AND	0FH		; ISOLATE LOWER 4 BITS
	ADD	A,'0'		; CONVERT TO ASCII
	CP	'9'+1		; GREATER THAN '9'?
	JP	C,PRINTIT
	ADD	A,'A'-'9'-1	; CONVERT A-F TO ASCII

PRINTIT:
	rst 10h
	RET

;-------------------------------------------------------------------------
; PRINTSTR - Print string
; INPUT:
;	D - address of the string to print
; OUTPUT:
;	None
; Note: String must be terminated with a dollar sign
;-------------------------------------------------------------------------
PRINTSTR:
	PUSH	AF
	PUSH	BC
	PUSH	DE
	PUSH	HL
.next:
	ld a, (de)
	inc de
	cp '$'
	jr nz, .print
	POP	HL
	POP	DE
	POP	BC
	POP	AF
	RET

.print:
	rst 10h
	jr .next

DEBUG		DB	1
ISCMOS		DB	0
ISU880		DB	0
XYRESULT	DB	0
XFYFCOUNT	DB	0,0,0,0
XFCOUNT		DW	0
YFCOUNT		DW	0
MSGSIGNIN	DB	'Z80 Processor Type Detection',0dh,7fh,' 2024 Sergey Kiselev',0dh
MSGCRLF		DB	0DH,'$'
MSGRAWCMOS	DB	'Raw results:',0dh,' CMOS: $'
MSGRAWU880	DB	0dh,' U880: $'
MSGRAWXY	DB	0dh,' XF/YF: $'
msgunkn:	db	'not tested, assuming NMOS$'
MSGFLAGS	DB	0dh,'XF/YF flags test:',0dh,' $'
MSGCPUTYPE	DB	0dh,'Detected CPU type:',0dh,' $'
MSGU880NEW	DB	'Newer MME U880, Thesys Z80, Microelectronica MMN 80CPU$'
MSGU880OLD	DB	'Older MME U880$'
MSGSHARPLH5080A	DB	'Sharp LH5080A$'
MSGNMOSZ80	DB	'Zilog Z80, Zilog Z08400',0dh,' or similar NMOS CPU',0DH
		DB	' Mostek MK3880N, SGS/ST Z8400,',0dh,' Sharp LH0080A, KR1858VM1$'
MSGNECD780C	DB	'NEC D780C, GoldStar Z8400,',0dh,' possibly KR1858VM1$'
MSGKR1858VM1	DB	'Overclocked KR1858VM1$'
MSGNMOSUNKNOWN	DB	'Unknown NMOS Z80 clone$'
MSGCMOSZ80	DB	'Zilog Z84C00$'
MSGTOSHIBA	DB	'Toshiba TMPZ84C00AP, ST Z84C00AB$'
MSGNECD70008AC	DB	'NEC D70008AC$'
MSGCMOSUNKNOWN	DB	'Unknown CMOS Z80 clone$'

	END
