mirror of
https://github.com/MiSTer-devel/MacPlus_MiSTer.git
synced 2025-02-17 01:31:02 +00:00
366 lines
11 KiB
Verilog
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
|