** Machine-specific macros for the implementation-dependent
** Z80 instructions.

** These are intended for the Spectrum emulator only.

	XREF	_SOUNDDATA

	INCLUDE "envdata.i"

** ---------------------------------------------------------------------

** I/O ports on the ZX Spectrum
**
**  In general:
**
**    The address bits 0 to 4 are usually set. At most one of them
**    should be reset at any time. A reset bit signals an I/O operation.
**    Bits 5 to 7 are normally set, and are not used by the standard
**    hardware. The address bits 8 to 15 are in some cases (e.g. when
**    reading the keyboard) used to give more information. Usually they
**    are ignored.
**
**  Port xxFE	(bit 0 reset)
**	General I/O port
**	Input:
**		Bit 7		Unknown
**		Bit 6		Value of EAR port
**		Bit 5		Unknown
**		Bits 4 to 0	Keyboard data
**
**	The keyboard rows are ordered from 0 (Caps Shift to V) through
**	3 (1 to 5), 4 (0 to 6) and 7 (Space to B).
**	  The bits 0 to 4 represent keys in a row, where bit 0 is the
**	outermost key. A bit is low if the key is pressed, and high if
**	it is not pressed.
**	  The address bits 8 to 15 correspond to rows 0 to 7 respectively,
**	and specify which rows will be read. A reset bit means the row
**	will be read. The selected rows are bitwise combined. In the
**	result, each bit is reset if one of the bits in the same position
**	was reset, and set if none of them were reset. Taking the bit
**	pattern 11111 and bitwise AND:ing with all selected rows would
**	give the same result.
**	  The value of bit 6 is not affected by the high address bits.
**
**	Output:	
**		Bits 7 to 5	Not used
**		Bit 4		Speaker control
**		Bit 3		MIC port control
**		Bits 2 to 0	Border colour code
**
**	The speaker and MIC ports are active low.
**	  The colour codes are specified as on/off flags for the RGB
**	components, where bits 2 through 0 correspond to Green, Red and
**	Blue respectively, giving totally 8 different colours. There is
**	no Bright mode flag for the border.
**
**  Port xxFD	(bit 1 reset)
**	Used for Microdrive, Networking or RS232C. I have no details.
**	Input:
**	Output:	
**	
**  Port xxFB	(bit 2 reset)
**	ZX Printer control port
**	Input:
**		Bit 7		High when stylus is in write position.
**		Bit 6		Low only if the printer is present.
**		Bits 5 to 0	Unknown
**
**	Output:
**		Bit 7		High "for the actual printing".
**		Bits 6 to 3	Unknown
**		Bit 2		Low starts the motor.
**		Bit 1		High slows the motor.
**		Bit 0		Unknown
**
**  Port xxF7	(bit 3 reset)
**	Used for Microdrive, Networking or RS232C. I have no details.
**	Input:
**	Output:	
**
**  Port xxEF	(bit 4 reset)
**	Used for Microdrive, Networking or RS232C. I have no details.
**	Input:
**	Output:	
**
**  Port xx1F	(bits 0 to 4 set, bits 5 to 7 reset)
**	Kempston interface port (external hardware)
**	Input:
**		Bits 7 to 5	Unknown
**		Bit 4		Fire
**		Bit 3		Up
**		Bit 2		Down
**		Bit 1		Left
**		Bit 0		Right
**
**	Bits 4 to 0 are active high.
**
**	Output:	Unknown
**

** ---------------------------------------------------------------------

** The keyboard data:
**	An array of 8 bytes (0 to 7) corresponding exactly to the
**	keyboard rows 0 to 7. Bits 5 to 7 are zero.

Kbd = Z80_Envdata+Envd_Kbd	;offset from control structure base

** ---------------------------------------------------------------------

	IFD VERBOSE
	LIST
** Compiling the machine.i file designed for the ZX Spectrum emulator.
	NOLIST
	ENDC


** A default read value, used by some lazy routines like Inir:
DEFAULT_IN = $FF


	;Macro for fast keyboard data reading.
	;RowSpec (register) corresponds to high byte of I/O address.
	;Dest (register) must be $FF before macro call.
KBD_READ	MACRO	;RowSpec, Dest
		add.b	\1,\1
		bcs.s	.7set
		and.b	Kbd+7(TableB),\2   ;row 7
.7set		add.b	\1,\1
		bcs.s	.6set
		and.b	Kbd+6(TableB),\2
.6set		add.b	\1,\1
		bcs.s	.5set
		and.b	Kbd+5(TableB),\2
.5set		add.b	\1,\1
		bcs.s	.4set
		and.b	Kbd+4(TableB),\2
.4set		add.b	\1,\1
		bcs.s	.3set
		and.b	Kbd+3(TableB),\2
.3set		add.b	\1,\1
		bcs.s	.2set
		and.b	Kbd+2(TableB),\2
.2set		add.b	\1,\1
		bcs.s	.1set
		and.b	Kbd+1(TableB),\2
.1set		add.b	\1,\1
		bcs.s	.0set
		and.b	Kbd+0(TableB),\2     ;row 0
.0set
	ENDM


**
** The instruction macros themselves:
**

	;sadly, one can't nest macros.

In_r_1C1_subm	MACRO
		cmp.b	#$fe,C
		st	\1	;flags unaffected
		bne.s	.end
		move.b	B,d7
		KBD_READ d7,\1
.end		tst.b	\1	;V cleared
		putsr	d7
		eor.b	d7,d6
		and.w	#1,d6
		eor.b	d7,d6	;keep old carry
		parity	\1
		skip 1
		next
		ENDM

In_A_1C1_mac	MACRO
		In_r_1C1_subm	A
		ENDM
In_B_1C1_mac	MACRO
		In_r_1C1_subm	B
		ENDM
In_C_1C1_mac	MACRO
		In_r_1C1_subm	C
		ENDM
In_D_1C1_mac	MACRO
		In_r_1C1_subm	D
		ENDM
In_E_1C1_mac	MACRO
		In_r_1C1_subm	E
		ENDM
In_L_1C1_mac	MACRO
		In_r_1C1_subm	L
		ENDM

In_H_1C1_mac	MACRO
		swap	d6	;save flags
		move.w	#$00ff,d6	;for parity indexing
		cmp.b	#$fe,C
		bne.s	.end
		move.b	B,d7
		KBD_READ d7,d6
.end		move.w	HL,(Work)
		move.b	d6,(Work)       ;V cleared,N & Z tested
		putsr	d7
		or.b	Z80_Parity(TableB,d6.w),d7 ;set parity in d7
		move.w	(Work),HL
		swap	d6	;get old flags
		eor.b	d7,d6
		and.w	#1,d6
		eor.b	d7,d6	;keep old carry
		skip 1
		next
		ENDM

	;"Undocumented" instruction. Reads from the port but does not
	;store the value. Flags are affected by the read value as usual,
	;and this instruction is sometimes named "In F,(C)". The name
	;"In (HL),(C)" would be symmetric, but is not correct.
In_xx_1C1_mac	MACRO
		swap	d6	;save flags
		move.w	#$00ff,d6	;for parity indexing
		cmp.b	#$fe,C
		bne.s	.end
		move.b	B,d7
		KBD_READ d7,d6
.end		tst.b	d6	;V cleared,N & Z tested
		putsr	d7
		or.b	Z80_Parity(TableB,d6.w),d7 ;set parity in d7
		swap	d6	;forget the value, get old flags
		eor.b	d7,d6
		and.w	#1,d6
		eor.b	d7,d6	;keep old carry
		skip 1
		next
		ENDM
** ----
In_A_1n1_mac	MACRO
		getRPC
		getz	d7,d7
		cmp.b	#$fe,d7
		bne.s	.notFE
		move.b	A,d7
		st	A
		KBD_READ d7,A
.end		skip 1
		next

.notFE		st	A
		bra.s	.end
		ENDM
** ----

** The block In instructions do not bother to 'read ports'.
** They simply "read" the default in value.

Ind_mac 	MACRO
		move.b	#DEFAULT_IN,d7
		putz	d7,HL
		decw	HL
		and.w	#%1011,d6
		decb	B
		bne.s	.nz
		or.w	#%0100,d6
.nz		skip 1
		next
		ENDM
** ----
Indr_mac	MACRO
		move.b	#DEFAULT_IN,d7
.loop		putz	d7,HL
		decw	HL
		decb	B
		bne.s	.loop
		or.w	#%0100,d6
		skip 1
		next
		ENDM
** ----
Ini_mac 	MACRO
		move.b	#DEFAULT_IN,d7
		putz	d7,HL
		incw	HL
		and.w	#%1011,d6
		decb	B
		bne.s	.nz
		or.w	#%0100,d6
.nz		skip 1
		next
		ENDM
** ----
Inir_mac	MACRO
		move.b	#DEFAULT_IN,d7
.loop		putz	d7,HL
		incw	HL
		decb	B
		bne.s	.loop
		or.w	#%0100,d6
		skip 1
		next
		ENDM
** ----
Ld_A_R_mac	MACRO
		getRPC	;take bits from RPC (since PPC is always even)
		move.b	Z80_R(TableB),A
		eor.w	d7,d6
		and.w	#80,d6	;keep bit 7 of stored R
		eor.w	d7,d6	;V is cleared, Z and N tested.
		putsr	d7
		eor.w	d7,d6
		and.w	#1,d6	;keep old carry
		eor.w	d7,d6
		tst.b	Z80_IFF(TableB)    ;test IFF2
		beq.s	.clear
		or.w	#%0010,d6	;set V if IFF2 set
.clear		skip 1
		next
		ENDM
** ----
Ld_R_A_mac	MACRO
		move.b	A,Z80_R(TableB)	;(the bits 6-0 are never reused)
		skip 1
		next
		ENDM
** ----

** It is OK for Otir and Otdr to be a bit inefficient. The real Z80
** instruction does a complete re-execution each time it loops.
** If that is done here as well, the block output timing will be nicer.

Otdr_mac	MACRO
.loop		getz	HL,d7
		cmp.b	#$fe,C
		bne.s	.end
		and.b	#$10,d7 	;speaker(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		getz	HL,d7
		and.b	#8,d7		;mic(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		decw	HL
		decb	B
		bne.s	.loop
		or.w	#%0100,d6	;Set Z
		skip 1
		next
		ENDM
** ---
Otir_mac	MACRO
.loop		getz	HL,d7
		cmp.b	#$fe,C
		bne.s	.end
		and.b	#$10,d7 	;speaker(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		getz	HL,d7
		and.b	#8,d7		;mic(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		incw	HL
		decb	B
		bne.s	.loop
		or.w	#%0100,d6	;Set Z
		skip 1
		next
		ENDM
** ----

	;no macro nesting, sadly
Out_1C1_r_subm	MACRO
		cmp.b	#$fe,C
		bne.s	.end
		move.b	\1,d7		;speaker
		and.b	#$10,d7 	;(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		move.b	\1,d7		;mic
		and.b	#8,d7		;(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		skip 1
		next
		ENDM

Out_1C1_A_mac	MACRO
		Out_1C1_r_subm	A
		ENDM
Out_1C1_B_mac	MACRO
		Out_1C1_r_subm	B
		ENDM
Out_1C1_C_mac	MACRO
		Out_1C1_r_subm	C
		ENDM
Out_1C1_D_mac	MACRO
		Out_1C1_r_subm	D
		ENDM
Out_1C1_E_mac	MACRO
		Out_1C1_r_subm	E
		ENDM
Out_1C1_L_mac	MACRO
		Out_1C1_r_subm	L
		ENDM

Out_1C1_H_mac	MACRO
		cmp.b	#$fe,C
		bne.s	.end
		move.w	HL,(Work)
		move.b	(Work),d7       ;speaker
		and.b	#$10,d7 	;(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		move.b	(Work),d7       ;mic
		and.b	#8,d7		;(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		skip 1
		next
		ENDM
** ----
	;"Undocumented" instruction. Seems to output a zero value
	;to the port. The name "Out (C),(HL)" would be symmetric,
	;but is not correct.
Out_1C1_xx_mac	MACRO
		cmp.b	#$fe,C
		bne.s	.end
		moveq	#0,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		skip 1
		next
		ENDM
** ----
Out_1n1_A_mac	MACRO
		getRPC
		getz	d7,d7	;immediate data
		cmp.b	#$fe,d7
		bne	.end
		move.b	A,d7		;speaker
		and.b	#$10,d7 	;(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		move.b	A,d7		;mic
		and.b	#8,d7		;(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		skip 1
		next
		ENDM
** ----
Outd_mac	MACRO
		getz	HL,d7
		cmp.b	#$fe,C
		bne.s	.end
		and.b	#$10,d7 	;speaker(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		getz	HL,d7
		and.b	#8,d7		;mic(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		decw	HL
		and.w	#%1011,d6
		decb	B
		bne.s	.nz
		or.w	#%0100,d6
.nz		skip 1
		next
		ENDM
** ----
Outi_mac	MACRO
		getz	HL,d7
		cmp.b	#$fe,C
		bne.s	.end
		and.b	#$10,d7 	;speaker(0 or 16)
		add.b	d7,d7
		move.b	d7,_SOUNDDATA
		move.b	d7,_SOUNDDATA+1
		getz	HL,d7
		and.b	#$08,d7 	;mic(0 or 8)
		add.b	d7,d7
		add.b	d7,d7
		move.b	d7,_SOUNDDATA+2
		move.b	d7,_SOUNDDATA+3
.end		incw	HL
		and.w	#%1011,d6
		decb	B
		bne.s	.nz
		or.w	#%0100,d6
.nz		skip 1
		next
		ENDM
** ----

** The Reti and Retn instructions do not themselves cause any signalling,
** but interrupt-controlling hardware could be watching the bus to see
** when an interrupt finishes.

Reti_mac	MACRO
		;Do any "hardware" emulation first.
		getz	ZSP,1(Work)
		incw	ZSP
		getz	ZSP,(Work)
		incw	ZSP
		move.w	(Work),d7
		makePPC
		testreq
		ENDM
** ----
Retn_mac	MACRO
		;Do any "hardware" emulation first.
		getz	ZSP,1(Work)
		incw	ZSP
		getz	ZSP,(Work)
		incw	ZSP
		move.w	(Work),d7
		makePPC
		move.b	Z80_IFF(TableB),d7
		asr.b	#1,d7		;IFF2 -> IFF1 -> bit 5
		and.b	#$C0,d7 	;reset bits 5-0
		move.b	d7,Z80_IFF(TableB)
		testreq
		ENDM

** =====================================================================
