#!/usr/local/bin/zasm -o original/test.ram


#target ram

#code CODE, 0x100

; ––––––––––––––––––––––––––
; mult (A DE -- HL)
;
;   mod B
;   ret z

A_times_DE:
		ld hl,0
		or a
		ret z
		ld b,9
3$:     rlca
		dec b
		jr nc,3$
1$:     add hl,de
2$:     dec b
		ret z
		add hl,hl
		rlca
		jp c,1$
		jp 2$

; ––––––––––––––––––––––––––
; div (DEHL C -- DEHL rem A)
;
;   mod B=0
;
; function originally from z80-heaven.wikidot.com
; was broken --> corrected!

DEHL_div_C:
	ld b,32
	xor a
1$: add hl,hl
	rl e
	rl d
	rla
	jr c,3$
	cp c
	jr c,2$
3$: inc l
	sub c
2$: djnz 1$
	ret

; ––––––––––––––––––––––––––
; mult (DEHL A -- ADEHL)
;
; --> z80-heaven.wikidot.com
;     ***broken!***

DEHL_times_A_broken:
	push hl
	or a
	sbc hl,hl
	exx
	pop de
	sbc hl,hl
	ld b,8
mul32loop:
	add hl,hl
	rl e
	rl d
	add a,a
	jr nc,$+8
		add hl,de
		exx
		adc hl,de
		inc a
		exx
	djnz mul32loop
	push hl
	exx
	pop de
	ex hl,de
	ret

; ––––––––––––––––––––––––––
; mult (DE A -- AHL)
;
;   mod BC=0
;   --> wikiti.brandonw.net

DE_times_A:
	ld bc,$0800
	ld h,c
	ld l,c
1$: add hl,hl
	rla         ; Check most-significant bit of accumulator
	jr nc,2$    ; If zero, skip addition
	add hl,de
	adc a,c
2$: djnz 1$
	ret

; mult (DEHL A -- ADEHL)
;
;   mod BC=0 DE' HL'
;   --> kio@little-bat.de

DEHL_times_A:

	push hl
	ld hl,0
	exx
	pop de      ; de = low word, de' = high word

	ld  bc,$0800      ; b=8
	ld  h,c \ ld l,c  ; c=0, hl=0, hl'=0

1$: add hl,hl
	exx
	adc hl,hl
	exx
	rla         ; get most-significant bit of accumulator
	jr nc,2$    ; If zero, skip addition
	add hl,de
	exx
	adc hl,de
	exx
	adc a,c
2$: djnz 1$

	push hl
	exx
	pop de
	ex hl,de
	ret

; ––––––––––––––––––––––––––
; div10 (HL -- HL rem A)
;
; Speed Optimised HL_div_10
;
;   mod BC=10

HL_div_10:
	ld  bc,$0D0A
	xor a
	add hl,hl \ rla
	add hl,hl \ rla
	add hl,hl \ rla
2$: add hl,hl \ rla
	cp  c
	jr  c,1$
	sub c
	inc l
1$: djnz 2$
	ret

; ––––––––––––––––––––––––––
; print_hex (A)
;
;   mod A

print_hex_byte:
	push af
	rrca \ rrca \ rrca \ rrca
	call print_hex_char
	pop  af
	;jp  print_hex_char

; ––––––––––––––––––––––––––
; print_hex_nibble (A)
;
;   mod A

print_hex_char:
	and 0x0f
	cp  10
	jr  c,1$
	add 'A' - ('9'+1)

1$: add '0'
	rst stdout
	ret

; ––––––––––––––––––––––––––
; print_number (HL)
;
;   mod A BC HL

; print HL as a decimal number:
print_hl:
	call HL_div_10      ; -> HL rem A
	push af
	ld  a,h
	or  l
	call nz,print_hl
	pop af

print_decimal_digit:
	add  a,'0'
	rst stdout
	ret


; -------------------------------------------
#code RST, 0
; interrupt handler for testing interrupt handling test

CON_IO  equ 0xfe        ; a port address

reset:  db  0xed,0x66   ; this is an illegal opcode
		di \ halt \ jp reset

		.org 8
stdin:  in a,(CON_IO)
		and a           ; Z if a=0 (no char available); NZ if char returned
		ret

		.org 16
stdout: out (CON_IO),a
		ret

		.org 24
puts:   ld  a,(hl++)
		and a
		ret z
		out (CON_IO),a
		jr  puts

		.org 32
		.org 40

		.org 48
		jp  0000        ; aux. interrupt entry for special test

		.org 56
int38h: push af         ; --- default interrupt handler $FF = RST 38h ---
		push hl

		ld  hl,(systime)    ; increment a system time
		inc hl
		ld  (systime),hl

		rst stdin
		jr z,1$         ; $00 => no char available

		cp  13
		jr  nz,2$
		ld  a,10        ; replace cr with nl

2$:     rst stdout      ; echo

1$:     pop af
		pop hl
		ei
		ret

systime dw  0



; -------------------------------------------
; test A_times_DE:
; -------------------------------------------

#test TEST1, 0x1000
#local
	.test-timeout 100 ms
min = 207
max = 382

	ld  sp,0
	ld  a,3
	ld  de,7
	call A_times_DE

	.expect a' = 0
	.expect a = 3
	.expect de = 7
	.expect hl = 3*7
	.expect cc > 44+min
	.expect cc < 44+max

1$: xor a
	ld  de,0x0101
	call A_times_DE

	.expect a = 0
	.expect de = 0x0101
	.expect hl = 0
	.expect cc < 31+min

2$: ld  a,1
	call A_times_DE

	.expect a = 1
	.expect de = 0x0101
	.expect hl = 0x0101
	.expect cc = 24+min

3$: ld  a,0xff
	call A_times_DE

	.expect a = 0xff
	.expect de = 0x0101
	.expect hl = 0xFFFF
	.expect cc <= 24+max



Lxx = 4

.macro test_AxDE &A,&DE

Lxx = Lxx+1
{Lxx}$:
	ld    a,&A
	ld    de,&DE
	call  A_times_DE

	.expect a = &A
	.expect de = &DE
	.expect hl = &A * &DE
	.expect cc >= 34+207
	.expect cc <= 34+382

.endm

	test_AxDE 4,5
	test_AxDE 44,55
	test_AxDE 114,115



;   .expect f=0     ; will fail

#endlocal


; -------------------------------------------
; test DEHL_div_C:
; -------------------------------------------

#test TEST1B, 0x1000
#local
	.test-timeout 100 ms

.macro DEHL_div_C &N, &D
	ld  dehl,&N
	ld  c,&D
	call DEHL_div_C
	.expect de = (&N/&D) >> 16
	.expect hl = (&N/&D) & 0xffff
	.expect dehl = &N/&D
	.expect a  = (&N%&D)
	.expect b  = 0
	.expect c  = &D
.endm

	DEHL_div_C 12345,17
	DEHL_div_C 12345678,17
	DEHL_div_C 6474678,123
	DEHL_div_C 37901,149
	DEHL_div_C 65535,127
	DEHL_div_C 65536,127
	DEHL_div_C 65536,128
	DEHL_div_C 65536,255
	DEHL_div_C 65535,255
	DEHL_div_C 0,55
	DEHL_div_C 1,155
	DEHL_div_C 2048876234,1

; test division by zero:
	ld  dehl,123
	ld  c,0
	call DEHL_div_C
	.expect de = 0xffff
	.expect hl = 0xffff
	.expect dehl = 0xffffffff
	.expect a  = 123
	.expect b  = 0
	.expect c  = 0

#endlocal


; -------------------------------------------
; test DEHL_times_A
; -------------------------------------------

#test TEST1C, 0x1000
#local
	.test-timeout 100 ms

	ld  bc,47111
	ld  dehl,45678
	ld  a,123
	call DEHL_times_A
	.expect de = (45678*123) >> 16
	.expect hl = (45678*123) & 0xffff
	.expect dehl = 45678*123
	.expect a  = 0
	.expect bc = 47111


.macro DEHL_times_A &N, &D
	ld  bc,4711
	ld  dehl,&N
	ld  a,&D
	call DEHL_times_A
	.expect de = (&N*&D) >> 16
	.expect hl = (&N*&D) & 0xffff
	.expect dehl = &N * &D
	.expect a  = 0          ; zasm can't calculate 40 bit ints => take care for max result!
	.expect bc = 4711
.endm

	DEHL_times_A 12345,17
	DEHL_times_A 12345678,17
	DEHL_times_A 6474678,123

#endlocal


; -------------------------------------------
; test .expect itself
; -------------------------------------------
#test TEST2, 0x1000
#local
	di
	im 2
	ld  a,3
	ld  i,a
	ld  a,0x80
	ld  r,a

	ld  sp,$1011
	ld  hl,$1213 \ push hl \ pop af
	ld  bc,$1415
	ld  de,$1617
	ld  hl,$1819
	ld  ix,$2021
	ex  af,af'
	exx
	ld  hl,$2223 \ push hl \ pop af
	ld  bc,$2425
	ld  de,$2627
	ld  hl,$2829
	ld  iy,$3031

	.expect pc = $
	.expect im = 2
	.expect i = 3
	.expect r = 0x80 + 19
	.expect iff1 = 0        ; di
	.expect iff2 = 0        ; di

	.expect sp = $1011
	.expect af' = $1213
	.expect bc' = $1415
	.expect de' = $1617
	.expect HL' = 1819h
	.expect af2 = $1213
	.expect BC2 = $1415
	.expect de2 = $1617
	.expect hl2 = 1819h
	.expect IX  = $2021
	.expect af = $2223
	.expect bc = $2425
	.expect de = $2627
	.expect HL = 2829h
	.expect iy = $3031
	.expect sph = $10
	.expect spl = $11
	.expect a' = $12
	.expect f' = $13
	.expect b' = $14
	.expect C' = $15
	.expect D' = $16
	.expect e' = $17
	.expect h' = $18
	.expect l' = $19
	.expect a2 = $12
	.expect f2 = $13
	.expect b2 = $14
	.expect C2 = $15
	.expect D2 = $16
	.expect e2 = $17
	.expect h2 = $18
	.expect l2 = $19
	.expect ixh = $20
	.expect ixl = $21
	.expect xh = $20
	.expect xl = $21
	.expect a = $22
	.expect f = $23
	.expect b = $24
	.expect C = $25
	.expect D = $26
	.expect e = $27
	.expect h = $28
	.expect l = $29
	.expect iyh = $30
	.expect iyl = $31
	.expect yh = $30
	.expect yl = $31

	ld  a,10
	ld  r,a
	ei

	.expect iff1 = 1        ; ei
	.expect iff2 = 1        ; ei
	.expect r = 10 + 1      ; ei

	ld  a,0
	ld  iy,0
	ld  bcde, 0xe0b0c0df
	ld  ixhl, 0x23456789

	.expect bc = 0xe0b0
	.expect ix = 0x2345
	.expect de = 0xc0df
	.expect hl = 0x6789
	.expect bcde = 0xe0b0c0df
	.expect ixhl = 0x23456789
	.expect a=0
	.expect iy=0

	ld  a,0
	ld  hl,0
	ld  ixbc, 0xcdef2345
	ld  iyde, 0x98765432

	.expect ix = 0xcdef
	.expect iy = 0x9876
	.expect bc = 0x2345
	.expect de = 0x5432
	.expect ixbc = 0xcdef2345
	.expect iyde = 0x98765432
	.expect a=0
	.expect hl=0

#endlocal


; -------------------------------------------
; test running with cc limiter and no interrupts
; -------------------------------------------

#test TEST3, 0x1000
#local
	.test-clock 4 MHz           ; set speed
	.test-timeout 1 s

	ld  bc,0
1$: djnz 1$
	dec c
	jr  nz,1$

duration = 10 + 13*$ff00 + 24*$100 - 5  ; 854789

	.expect cc = duration
	.expect cc < 4000000
	.expect cc > $10000 * 13    ; ~ 850000
	.expect bc = 0
#endlocal


; -------------------------------------------
; test running with cc limiter and interrupts
; test output to console
; -------------------------------------------

#test TEST4, 0x1000
#local
	.test-clock   4 MHz         ; set speed
	.test-int     100 Hz        ; set interrupt frequency
	.test-console CON_IO        ; read from / dump to console
	.test-timeout 1 s

	im  1       ; rst $38
	ei

	ld  bc,0
1$: djnz 1$
	dec c
	jr  nz,1$

duration = 10 + 13*$ff00 + 24*$100 - 5  ; 854789
dur_int  = 13 + 83 + 34 + 25            ; cc per interrupt

	.expect cc = 12 + duration + 21 * dur_int   ; 858056
	.expect bc = 0

	ld  hl,(systime)
	.expect hl = 21     ; irpt 100 Hz => cc/int = 40000 => 858056/40000 = 21

	ld  hl,msg
2$: ld  a,(hl++)
	and a
	jr  z,msg_end
	rst stdout
	jr  2$

msg: dm "--> Hello World, this is Test #4 speaking!",10,0
msg_end:

#endlocal


; -------------------------------------------
; test running with cc limiter and interrupts
; test input from list
; test compare output with data from list
; -------------------------------------------

#test TEST5, 0x1000
#local
in_addr  equ 1
out_addr equ 2

	.test-clock   4 MHz             ; set speed
	.test-int     10000 cc          ; set interrupt using cpu cycles (=> 400 Hz)
	.test-intack  opcode(RST 48)    ; set opcode read from bus during int ack cycle
	.test-timeout 100 ms

	; set input data:
	.test-in    in_addr,  {'y'}*10, "abcde", 0, "DEFGH", {0}*10       ; 31 bytes

	; set output compare data:
	.test-out   out_addr, "yyyyyyyyyy", "abcde", "DEFGH"


	; make rst 48 point to my handler
	ld  hl,int_handler
	ld  (48+1),hl

	im  0               ; -> read instruction from bus
	ld  b,31            ; 31 bytes to read
3$: ei
	halt
	djnz 3$
	jr  resume

int_handler:
	in  a,(in_addr)
	and a
	ret z               ; $00 => no char available
	out (out_addr),a    ; echo
	ret

resume:
	nop

	.expect cc > 31 * 10000
	.expect cc < 31 * 10000 + 200
	.expect cc = 310074             ; lt. zasm

#endlocal


; -------------------------------------------
; test running without cc limiter and with interrupts
; read input from list
; output to console
; -------------------------------------------

#test TEST6, 0x1000
#local
in_addr  equ 1
out_addr equ 2

	.test-int     10000 cc      ; set interrupt using cpu cycles
	.test-timeout 100 ms

	.test-in  in_addr,  "--> ", {"Hello, "}*2, "this is Test #6 queeking", 10, {0}*
	.test-console out_addr


	; setup interrupt table:
	im  2                           ; -> jump via table
	ld  a,hi(int_table) \ ld i,a    ; hi byte in i
	.test-intack  lo(int_table)     ; lo byte from bus in int ack cycle


	ld  b,48            ; 48 bytes to read
3$: ei
	halt
	djnz 3$
	jr  resume


int_table:
	dw  int_handler

int_handler:
	in  a,(in_addr)
	and a
	ret z               ; $00 => no char available
	out (out_addr),a    ; echo
	ret

resume:
	nop

	.expect cc > 48 * 10000
	.expect cc < 48 * 10000 + 200
	.expect cc = 480071             ; lt. zasm

#endlocal


; -------------------------------------------
; run without cc limiter and with interrupts
; calculate emulation speed
; -------------------------------------------

#test TEST7, 0x1000
#local

	.test-int     1000 Hz       ; fastest allowed for tests
	;.test-clock  100 MHz       ; used for reference measurement
	.test-console CON_IO        ; output to console
	.test-timeout 105 ms


	; setup interrupt table:
	im  2                           ; -> jump via table
	ld  a,hi(int_table) \ ld i,a    ; hi byte in i
	.test-intack  lo(int_table)     ; lo byte from bus in int ack cycle

	ld  sp,0
	ei

	xor a
	ld  de,0
	jr  2$

1$: pop de
	pop af
	inc a
	dec de \ dec de \ dec de
2$: push af
	push de

	call A_times_DE

	ld  hl,counter
3$: inc (hl++)
	jr  z,3$

	ld  a,(systime)
	cp  a,100
	jr  nz,1$

	jr  resume

counter:
	dw  0,0

systime:
	db  0

int_table:
	dw  int_handler

int_handler:
	ex  af,af'
	ld  a,(systime)
	inc a
	ld  (systime),a
	ex  af,af'
	ei
	ret


; divide DEHL by 199.8
div1998:
	ld  a,5
	call DEHL_times_A
	ld  c,9
	call DEHL_div_C
	ld  c,111
	jp  DEHL_div_C

msg_loops_done: dm "--> loops done in 100 ms: 0x",0
msg_mhz1:       dm "--> Z80 running at ",0
msg_mhz2:       dm " MHz",10,0

resume:
	ld  hl,msg_loops_done
	rst puts
	ld  hl,counter+4
	ld  a,(--hl)
	call print_hex_byte
	ld  a,(--hl)
	call print_hex_byte
	ld  a,(--hl)
	call print_hex_byte
	ld  a,(--hl)
	call print_hex_byte
	ld  a,10
	rst stdout

	ld  hl,msg_mhz1
	rst puts
	ld  dehl,(counter)
	call div1998            ; divide dehl by 199.8 --> hl = MHz
	call print_hl
	ld  hl,msg_mhz2
	rst puts

	; used during reference measurement:
	;.expect cc > 100000 * 100
	;.expect cc < 100000 * 100 + 10000
	;.expect hl=0   ; --> print
	;.expect de=0   ; --> print

; 20 MHz:    $0F93  = 3987   loops  -> 199.35  loops/MHz
; 40 MHz:    $1F2F  = 7983   loops  -> 199.575 loops/MHz
; 100 MHz:   $4DFB  = 19963  loops  -> 199.63  loops/MHz

; AMD Ryzen 5 2400G @ 3.2GHz:
;  clang: max. 1818 MHz
;  gcc:   max. 2648 MHz --> almost 50% faster!

#endlocal

#end















