; Z80 XCF Flavor v1.6
; Copyright (C) 2022-2024 Manuel Sainz de Baranda y Goñi.
;
; This program is free software: you can redistribute it and/or modify it under
; the terms of the GNU General Public License as published by the Free Software
; Foundation, either version 3 of the License, or (at your option) any later
; version.
;
; This program is distributed in the hope that it will be useful, but WITHOUT
; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License along with
; this program. If not, see <http://www.gnu.org/licenses/>.

.area _CODE

TXT_OUTPUT = 0xBB5A

.module Z80_XCF_Flavor
.z80

CURSOR_LEFT = 8
PAPER = 14
PEN = 15
TRANSPARENCY_FLAG  = 22
INVERSE = 24
CHAR_COPYRIGHT = 0xA4
.macro Q0_F0_A0
xor a      ; A = 0; YF, XF, YQ, XQ = 0
.endm

.macro Q0_F1_A0
xor a      ;
dec a      ; YF, XF = 1
ld  a, #0     ; A = 0; Q = 0
.endm

.macro Q1_F1_A0
xor a      ; A = 0
ld  e, a     ;
dec e      ; YF, XF, YQ, XQ = 1
.endm

.macro Q0_F0_A1
xor a      ; YF, XF = 0
ld  a, #0xFF   ; A = FFh; Q = 0
.endm

.macro Q0_F1_A1
xor a      ;
dec a      ; A = FFh; YF, XF = 1
nop      ; Q = 0
.endm

.macro Q1_F1_A1
xor a      ;
dec a      ; A = FFh; YF, XF, YQ, XQ = 1
.endm

cpc_run_address::


start:
ld   hl, #header_text ; Print the header.
call print      ;
ld   bc, #results     ; Set BC to the address of the results array.

di      ; Disable interrupts.

; Test all factor combinations with `ccf` and
; keep the resulting values of YF and XF.
Q0_F0_A0
ccf
call keep_yxf
Q0_F1_A0
ccf
call keep_yxf
Q1_F1_A0
ccf
call keep_yxf
Q0_F0_A1
ccf
call keep_yxf
Q0_F1_A1
ccf
call keep_yxf
Q1_F1_A1
ccf
call keep_yxf

; Test all factor combinations with `scf` and
; keep the resulting values of YF and XF.
Q0_F0_A0
scf
call keep_yxf
Q0_F1_A0
scf
call keep_yxf
Q1_F1_A0
scf
call keep_yxf
Q0_F0_A1
scf
call keep_yxf
Q0_F1_A1
scf
call keep_yxf
Q1_F1_A1
scf
call keep_yxf

ei ; The interrupt-sensitive part is done, can re-enable interrupts now.

ld   c, #6      ; C = number of rows to print.
ld   hl, #rows_text      ; (HL) = Static text of the row.
ld   de, #results      ; (DE) = `ccf` results.
ld   ix, #results + 6      ; (IX) = `scf` results.
.print_table_row:
call print      ; Print the static text of the row.
ld   a, (de)      ; Print the results for `ccf` and point DE
call print_yxf      ;   to the next element in the results
inc  de      ;   array.
dec  hl      ; Point HL to the last two spaces in the
dec  hl      ;   static text of the row, and print those
call print      ;   spaces (column gap). Next, point HL to
inc  hl      ;   the static text of the next row.
ld   a, (ix)      ; Print the results for `scf` and point IX
call print_yxf      ;   to the next element in the results
inc  ix      ;   array.
dec  c      ;
jr   nz, .print_table_row    ; Repeat until all rows have been printed.

call print      ; Now HL points to the footer; print it.

ld   de, #results      ; Compare the values obtained with `ccf`,
ld   hl, #results + 6      ;   against those obtained with `scf`.
call compare_results      ;   They should be the same; otherwise,
cp   #0      ;   the behavior is unknown (or unstable)
jr   nz, .unknown_flavor     ;   and we report it.

ld   de, #results      ; Compare the values obtained with `ccf`
ld   hl, #results_on_zilog    ;   against the reference values for Zilog
call compare_results      ;   CPU models.
ld   hl, #zilog_text      ;
cp   #0      ; If the values match, report "Zilog
jr   z, .print_result      ;   flavor" and exit.

ld   de, #results      ; Compare the values obtained with `ccf`
ld   hl, #results_on_nec_nmos ;   against the reference values for NEC
call compare_results      ;   NMOS CPU models.
ld   hl, #nec_nmos_text      ;
cp   #0      ; If the values match, report "NEC NMOS
jr   z, .print_result      ;   flavor" and exit.

ld   de, #results      ; Compare the values obtained with `ccf`
ld   hl, #results_on_st_cmos  ;   against the reference values for ST
call compare_results      ;   CMOS CPU models.
ld   hl, #st_cmos_text      ;
cp   #0      ; If the values match, report "ST CMOS
jr   z, .print_result      ;   flavor" and exit.

.unknown_flavor:
ld   hl, #unknown_text      ; Report "Unknown flavor".
.print_result:
call print
ld   hl, #flavor_text
call print

loop: jp loop
; ret      ; Exit to BASIC.


; Keeps YF and XF into the results array.
;
; On entry:
;   BC - Address of the element in the results array.
; On exit:
;   BC - Address of the next element in the results array.
; Destroys:
;   A and DE.

keep_yxf:
push af        ; Transfer F to A.
pop  de        ;
ld   a, e      ;
and  #0b00101000 ; Clear all flags except YF and XF.
ld   (bc), a   ; Keep YF and XF into the results array.
inc  bc        ; Point BC to the next element of the array.
ret


; Prints YF and XF.
;
; On entry:
;   A - Flags.
; Destroys:
;   A and B.

print_yxf:
ld   b, a   ; Copy the flags to B.
; ld   a, INK ; Set blue ink.
; rst  $10    ;
; ld   a, 1   ;
; rst  $10    ;
srl  b     ; Shift the flags to the right until XF is at bit 0.
srl  b     ;
srl  b     ;
ld   a, b   ; Copy the shifted flags to A, and shift this register to
srl  a     ;   the right until YF is at bit 0.
srl  a     ;
and  #1     ; Clear all bits except bit 0.
add  #0x30    ; Translate the value of YF to ASCII.
call TXT_OUTPUT    ; Print the value of YF.
ld   a, b   ; Copy B to A. Now bit 0 of A contains XF.
and  #1     ; Clear all bits except bit 0.
add  #0x30    ; Translate the value of XF to ASCII.
call TXT_OUTPUT    ; Print the value of XF.
; ld   a, INK ; Restore the default ink.
; rst  $10    ;
; ld   a, 8   ;
; rst  $10    ;
ret


; Prints a 1Fh-terminated string.
;
; On entry:
;   HL - String address.
; On exit:
;   HL - Address of the termination byte.
; Destroys:
;   A.

print: ld   a, (hl)
cp   #0x1F
ret  z
call TXT_OUTPUT
inc  hl
jr   print


; Compares 2 arrays of results.
;
; On entry:
;   HL - Array 1 address.
;   DE - Array 2 address.
; On exit:
;   A - 0 if the arrays are equal; otherwise, a non-zero value.
; Destroys:
;   C, DE and HL.

compare_results:
ld   c, #6
.compare:
ld   a, (de)
sub  (hl)
ret  nz
inc  de
inc  hl
dec  c
jr   nz, .compare
ret


results:
.ds 12
results_on_zilog:
.db 0b00000000, 0b00101000, 0b00000000, 0b00101000, 0b00101000, 0b00101000
results_on_nec_nmos:
.db 0b00000000, 0b00000000, 0b00000000, 0b00101000, 0b00101000, 0b00101000
results_on_st_cmos:
.db 0b00000000, 0b00100000, 0b00000000, 0b00001000, 0b00101000, 0b00101000
header_text:
.ascii "Z80 XCF "
.db PAPER, 3
.ascii "FL"
.db PAPER, 0
.ascii "A"
.db PAPER, 2, PEN, 0
.ascii "VO"
.db PAPER, 1, PEN, 0
.ascii "R"
.db PAPER, 0, PEN, 1
.ascii " v1.6\r\n"
.db CHAR_COPYRIGHT
.ascii " Manuel Sainz de Baranda y Gon"
.db TRANSPARENCY_FLAG, 1, CURSOR_LEFT
.ascii "~"
.db TRANSPARENCY_FLAG, 0
.ascii "i\r\n"
.ascii "https://zxe.io \r\n"
.ascii "Ported to the Amstrad CPC by cpcitor\r\n"
.ascii "https://github.com/cpcitor\r\n"
.ascii "\r\n"
.ascii "This program checks the behavior\r\n"
.ascii "of the undocumented flags during\r\n"
.ascii "the CCF and SCF instructions and\r\n"
.ascii "detects the Z80 CPU type of your\r\n"
.ascii "Amstrad CPC.\r\n"
.ascii "\r\n"
.db INVERSE
.ascii "  Case    Any  NEC   ST    HOST \r\n"
.ascii " Tested  Zilog NMOS CMOS   CPU  \r\n"
.ascii "(Q<>F)|A   YX   YX   YX   YX  YX"
.db INVERSE
.db 0x1F
rows_text:
.ascii "\r\n(0<>0)|0   00   00   00   "
.db 0x1F
.ascii "\r\n(0<>1)|0   11   00   10   "
.db 0x1F
.ascii "\r\n(1<>1)|0   00   00   00   "
.db 0x1F
.ascii "\r\n(0<>0)|1   11   11   01   "
.db 0x1F
.ascii "\r\n(0<>1)|1   11   11   11   "
.db 0x1F
.ascii "\r\n(1<>1)|1   11   11   11   "
.db 0x1F
footer_text:
.db INVERSE
.ascii "\r\n                         ccf scf\r\n"
.db INVERSE
.ascii "\nResult: "
.db 0x1F
zilog_text:
.db PAPER, 2
.ascii "Zilog"
.db 0x1F
nec_nmos_text:
.db PAPER, 2, PEN, 0
.ascii "NEC NMOS"
.db 0x1F
st_cmos_text:
.db PAPER, 2, PEN, 0
.ascii "ST CMOS"
.db 0x1F
unknown_text:
.db PAPER, 3, PEN, 0
.ascii "Unknown"
.db 0x1F
flavor_text:
.db PAPER, 0, PEN, 1
.ascii " flavor\r\n"
.db 0x1F
; nn:
;  .db 00000000b ; ñ
;  .db 00111000b
;  .db 00000000b
;  .db 01011000b
;  .db 01100100b
;  .db 01000100b
;  .db 01000100b
;  .db 00000000b

;PROGRAM_SIZE = $ - start


; savesna 'Z80 XCF Flavor.sna', start


; CLEAR   = $FD
; CODE   = $AF
; LOAD   = $EF
; RANDOMIZE = $F9
; USR   = $C0

;  org $5C00
; basic: .db  0, 1
;  dw  LINE_1_SIZE
; line_1: .db  CLEAR, '8', $0E, 0, 0
;  dw  start - 1
;  .db  0, ':'
;  .db  LOAD, '"'
; name: ds  10, 32
;  org name
;  .db  'XCF Flavor'
;  org name + 10
;  .db  '"', CODE, $0D
; LINE_1_SIZE = $ - line_1
;  .db  0, 2
;  dw  LINE_2_SIZE
; line_2: .db  RANDOMIZE, USR, '8', $0E, 0, 0
;  dw  start
;  .db  0, $0D
; LINE_2_SIZE = $ - line_2
; BASIC_SIZE  = $ - basic

.area _DATA