MacPlus_MiSTer/via.v

366 lines
11 KiB
Verilog

/* 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