/* VIA
	
	This implementation assumes the I/O data directions and PCR edge triggers used in the Macintosh,
	and ignores most writes to the VIA data direction registers and the PCR.
	
	The 16 VIA registers are mapped to addresses {8'hEF, 8'b111xxxx1, 8'hFE}:
			0	$0		vBufB		register B
			1	$200	?????		register A (controls handshake)
			2	$400	vDirB		register B direction register
			3	$600	vDirA		register A direction register
			4	$800	vT1C		timer 1 counter (low-order byte) - for sound?
			5	$A00	vT1CH		timer 1 counter (high-order byte)
			6	$C00	vT1L		timer 1 latch (low-order byte)
			7	$E00	vT1LH		timer 1 latch (high-order byte)
			8	$1000	vT2C		timer 2 counter (low-order byte) - W: writes T2L-L R: read T2C-L and clear interrupt flag
			9	$1200	vT2CH		timer 2 counter (high-order byte) - W: write T2C-H, transfer T2L-L to T2C-L, clear interrupt flag R: read T2C-H
			10	$1400	vSR		shift register (keyboard)
			11	$1600	vACR		auxiliary control register
			12	$1800	vPCR		peripheral control register
			13	$1A00	vIFR		interrupt flag register
			14	$1C00	vIER		interrupt enable register
			15	$1E00	vBufA		register A (no handshake)
			
	Register A:
		Bit(s)  Name             Dir	Description

		7       vSCCWReq         in	SCC wait/request
		6       vPage2           out	Alternate screen buffer (1 = main buffer)
		5       vHeadSel         out	Disk SEL line
		4       vOverlay         out	ROM low-memory overlay (1 = overlay on)
		3       vSndPg2          out	Alternate sound buffer (1 = main buffer)
		0-2     vSound (mask)    out	Sound volume

	Register B:
		Bit   Name       Dir		Description

		7     vSndEnb    out		Sound enable/disable
		6     vH4        in		Horizontal blanking
		5     vY2        in		Mouse Y2
		4     vX2        in		Mouse X2
		3     vSW        in		Mouse switch
		2     rTCEnb     out		Real-time clock serial enable (active low I think)
		1     rTCClk     out		Real-time clock data-clock line
		0     rTCData    in/out	Real-time clock serial data

	Interrupt flag and enable registers:
		IFR bit 7: remains set (and the IRQ line to the processor is held low) as long as any enabled VIA
					  interrupt is occurring. 
		IER bit 7: "enable/disable":  If bit 7 is a 1, each 1 in bits 0-6 enables the corresponding interrupt; 
					  if bit 7 is a 0, each 1 in bits 0-6 disables that interrupt. In either case, 0's in bits 0-6 do not
					  change the status of those interrupts. Bit 7 is always read as a 1. 
					  
		Bit   Interrupting device

		7     IRQ (IFR) or enable (IER)
		6     Timer 1 timeout
		5     Timer 2 timeout
		4     Keyboard clock (CB1)
		3     Keyboard data bit (CB2)
		2     Keyboard data ready (completion of 8 shifts) (SR)
		1     Vertical blanking interrupt (CA1)
		0     One-second interrupt (CA2)

	Peripheral control register:
		Bit	Description
		
		5-7	CB2 control (keyboard data bit)
		4		CB1 control (keyboard clock)
		1-3	CA2 control (one-second interrupt)
		0		CA1 control (vertical blanking interrupt)
		
		1-bit controls: 0 = negative edge trigger (normal Macintosh mode), 1 = positive edge trigger
		3-bit controls: 
			000	set IFR on negative edge, clear IFR on read/write from register A/B. Normal Macintosh mode.
			001	set IFR on negative edge
			010	set IFR on positive edge, clear IFR on read/write from register A/B
			011	set IFR on positive edge
			100-111 not used in Macintosh (output mode)	
		
	Auxiliary control register:
		Bit	Description
		6-7	T1 control, 00 = one-shot mode, output to PB7 disabled, 11 = free running mode, output to PB7 enabled
		5		T2 control, 0 = interval timer in one-shot mode (Mac mode), 1 = counts a predetermined number of pulses on pin PB6 (not used)
		2-4	shift register control
		1		PB latch enable
		0		PA latch enable
		
	Timer 2:
		For Macintosh, always operates as a one-shot inerval timer.
		8	$1000	vT2C		W: write T2L-L R: read T2C-L and clear interrupt flag
		9	$1200	vT2CH		W: write T2C-H, transfer T2L-L to T2C-L, clear interrupt, arms timer flag R: read T2C-H		
*/

`define INT_ONESEC		0  
`define INT_VBLANK		1  
`define INT_KEYREADY		2
`define INT_KEYBIT		3
`define INT_KEYCLK		4
`define INT_T2				5
`define INT_T1				6

module via
(
	input clk,
	input cep,
	input cen,

	input _reset,
	input selectVIA,
	input _cpuRW,
	input _cpuUDS,	
	input [15:0] dataIn,
	input [3:0] cpuAddrRegHi,
	input _hblank,
	input _vblank,
	input mouseY2,
	input mouseX2,
	input mouseButton,
	input rtcData,
	input sccWReq,
	output _irq,
	output [15:0] dataOut,
	output memoryOverlayOn,
	output SEL, // to IWM

	output snd_ena,
	output snd_alt,
	output [2:0] snd_vol,
	
	input [7:0] kbd_in_data,
	input kbd_in_strobe,
	output [7:0] kbd_out_data,
	output reg kbd_out_strobe
);

	wire [7:0] dataInHi = dataIn[15:8];
	reg [7:0] dataOutHi;
	assign dataOut = { dataOutHi, 8'hEF };
	
	reg [7:0] viaADataOut;
	reg [7:0] viaBDataOut;
	reg viaB0DDR;
	reg [6:0] viaIFR;
	reg [6:0] viaIER;
	reg [7:0] viaACR;
	reg [7:0] viaSR;
	reg [15:0] viaTimer1Count;
	reg [15:0] viaTimer1Latch;
	reg [15:0] viaTimer2Count;
	reg [7:0] viaTimer2LatchLow;
	reg viaTimer2Armed;

	
	// shift register can be written by CPU and by external source
	/* Write to SR (including external input) */
	assign kbd_out_data = viaSR;
   always @(posedge clk or negedge _reset) begin
		if (_reset == 1'b0)
			viaSR <= 8'b0;           
		else if(cen) begin
			if((selectVIA == 1'b1) && (_cpuUDS == 1'b0) && 
				(_cpuRW == 1'b0) && (cpuAddrRegHi == 4'hA))
						viaSR <= dataInHi;
						
         if (viaACR[4:2] == 3'b011 && kbd_in_strobe)
                  viaSR <= kbd_in_data;
      end
	end

	/* Generate sr_out_strobe */
   always @(posedge clk  or negedge _reset) begin
		if (_reset == 1'b0)
			kbd_out_strobe <= 1'b0;                
		else if(cen) begin
			if((selectVIA == 1'b1) && (_cpuUDS == 1'b0) && 
				(_cpuRW == 1'b0) && (cpuAddrRegHi == 4'hA) && 
				(viaACR[4:2] == 3'b111))
					kbd_out_strobe <= 1;
				else
					kbd_out_strobe <= 0;   
		end
	end
 	
	// divide by 10 clock divider for the VIA timers: 0.78336 MHz
	reg [3:0] clkDiv;
	always @(posedge clk) begin
		if(cep) begin
			if (clkDiv == 4'h9)
				clkDiv <= 0;
			else
				clkDiv <= clkDiv + 1'b1;
		end
	end
	wire timerStrobe = (clkDiv == 0);
	
	// store previous vblank value, for edge detection
	reg _lastVblank;
	always @(posedge clk) if(cen) _lastVblank <= _vblank;

	// count vblanks, and set 1 second interrupt after 60 vblanks
	reg [5:0] vblankCount;
	always @(posedge clk) begin
		if(cen) begin
			if (_vblank == 1'b0 && _lastVblank == 1'b1) begin
				if (vblankCount != 59) begin
					vblankCount <= vblankCount + 1'b1;
				end
				else begin
					vblankCount <= 6'h0;
				end
			end
		end
	end
	assign _irq = (viaIFR & viaIER) == 0 ? 1'b1 : 1'b0;
	
	// register write
	wire loadT2 = selectVIA == 1'b1 && _cpuUDS == 1'b0 && _cpuRW == 1'b0 && cpuAddrRegHi == 4'h9;
	always @(posedge clk or negedge _reset) begin
		if (_reset == 1'b0) begin
			viaB0DDR <= 1'b1;
			viaADataOut <= 8'b01111111;
			viaBDataOut <= 8'b11111111;
			viaIFR <= 7'b0000000;
			viaIER <= 7'b0000000;
			viaACR <= 8'b00000000;
			viaTimer1Count <= 16'h0000;
			viaTimer1Latch <= 16'h0000;
			viaTimer2Count <= 16'h0000;
			viaTimer2LatchLow <= 8'h00;
			viaTimer2Armed <= 0;
		end
		else if(cen) begin
			if (selectVIA == 1'b1 && _cpuUDS == 1'b0) begin
				if (_cpuRW == 1'b0) begin
					// normal register writes
					case (cpuAddrRegHi)
						4'h0: // B
							viaBDataOut <= dataInHi;
						4'h2: // B DDR
							viaB0DDR <= dataInHi[0];
						// 4'h3: ignore A DDR
						4'h4: // timer 1 count low
							viaTimer1Count[7:0] <= dataInHi;
						4'h5: // timer 1 count high
							viaTimer1Count[15:8] <= dataInHi;
						4'h6: // timer 1 latch low
							viaTimer1Latch[7:0] <= dataInHi;
						4'h7: // timer 1 latch high
							viaTimer1Latch[15:8] <= dataInHi;
						4'h8: // timer 2 latch low
							viaTimer2LatchLow <= dataInHi;
						4'h9: begin // timer 2 count high
							viaTimer2Count[15:8] <= dataInHi;
							viaTimer2Count[7:0] <= viaTimer2LatchLow;
							viaTimer2Armed = 1'b1;
							viaIFR[`INT_T2] <= 1'b0;
						end
						4'hA: begin // shift register
							if( viaACR[4:2] == 3'b111 )
								viaIFR[`INT_KEYREADY] <= 1'b1;
						end
						4'hB: // Aux control register
							viaACR <= dataInHi;		
						// 4'hC: ignore PCR
						4'hD: // IFR
							viaIFR <= viaIFR & ~dataInHi[6:0];
						4'hE: // IER
							if (dataInHi[7])
								viaIER <= viaIER | dataInHi[6:0];
							else
								viaIER <= viaIER & ~dataInHi[6:0];
						4'hF: // A		
							viaADataOut <= dataInHi;
					endcase
				end
				else begin
					// interrupt flag modifications due to register reads
					case (cpuAddrRegHi)
						4'h0: begin // reading (and writing?) register B clears KEYCLK and KEYBIT interrupt flags
							viaIFR[`INT_KEYCLK] <= 1'b0;
							viaIFR[`INT_KEYBIT] <= 1'b0;
						end
						4'h8: // reading T2C-L clears the T2 interrupt flag
							viaIFR[`INT_T2] <= 1'b0; 
						4'hA: // reading SR clears the SR interrupt flag
							viaIFR[`INT_KEYREADY] <= 1'b0;
						4'hF: begin // reading (and writing?) register A clears VBLANK and ONESEC interrupt flags
							viaIFR[`INT_ONESEC] <= 1'b0;
							viaIFR[`INT_VBLANK] <= 1'b0;
						end
					endcase
				end
			end
			// external interrupts
			if (_vblank == 1'b0 && _lastVblank == 1'b1) begin
				viaIFR[`INT_VBLANK] <= 1'b1; // set vblank interrupt
				if (vblankCount == 59) 	
					viaIFR[`INT_ONESEC] <= 1'b1; // set one second interrupt after 60 vblanks
			end				
			// timer 2
			if (timerStrobe && !loadT2) begin
				if (viaTimer2Armed && viaTimer2Count == 0) begin
					viaIFR[`INT_T2] <= 1'b1;
					viaTimer2Armed <= 0;
				end
				viaTimer2Count <= viaTimer2Count - 1'b1;
			end
			
			// Shift in under control of external clock
         if (viaACR[4:2] == 3'b011 && kbd_in_strobe)
				viaIFR[`INT_KEYREADY] <= 1;
			
		end
	end
	
	// register read
	always @(*) begin
		dataOutHi = 8'hBE;
	
		case (cpuAddrRegHi)
			4'h0: // B
				// TODO: clear CB1 and CB2 interrupts
				dataOutHi = { viaBDataOut[7], ~_hblank, mouseY2, mouseX2, mouseButton, viaBDataOut[2:1], viaB0DDR == 1'b1 ? viaBDataOut[0] : rtcData };
			4'h2: // B DDR
				dataOutHi = { 7'b1000011, viaB0DDR };
			4'h3: // A DDR
				dataOutHi = 8'b01111111;
			4'h4: // timer 1 count low
				dataOutHi = viaTimer1Count[7:0];
			4'h5: // timer 1 count high
				dataOutHi = viaTimer1Count[15:8];
			4'h6: // timer 1 latch low
				dataOutHi = viaTimer1Latch[7:0];
			4'h7: // timer 1 latch high
				dataOutHi = viaTimer1Latch[15:8];
			4'h8: // timer 2 count low
				dataOutHi = viaTimer2Count[7:0]; 
			4'h9: // timer 2 count high
				dataOutHi = viaTimer2Count[15:8];	
			4'hA: // shift register
				dataOutHi = viaSR;	
			4'hB: // Aux control register
				dataOutHi = viaACR;
			4'hC: // PCR
				dataOutHi = 0; 					
			4'hD: // IFR
				dataOutHi = { viaIFR & viaIER == 0 ? 1'b0 : 1'b1, viaIFR };
			4'hE: // IER
				dataOutHi = { 1'b1, viaIER };			
			4'hF: // A
				// TODO: clear CA1 and CA2 interrupts
				dataOutHi = { sccWReq, viaADataOut[6:0] };
			default:
				dataOutHi = 8'hBE;
		endcase
	end
	
	assign snd_vol = viaADataOut[2:0];
	assign snd_alt = !viaADataOut[3];
	assign snd_ena = viaBDataOut[7];
	
	assign memoryOverlayOn = viaADataOut[4];
	assign SEL = viaADataOut[5];
	
endmodule
