`timescale 1ns / 100ps /* * VIA6522 module for minimigmac. * * The processor interface part of the VIA modified to use my * home made bus interface. The rest is hugely simplified to * only operate in the modes used by a Macintosh. The shift * register is not implemented as such, instead, we have a * direct 8 bit bus to the keyboard interface. * * My understanding of the original Mac design is that the VIA * two phase clock is fed from the original m68k's E line, thus * ticks at 1/10 of the 7.82 Mhz CPU frequency. * * In this variant, the register interface is clocked at the full * internal system clock, and we generate artificially a 782 kHz * enable signal from it to feed the timers. We don't aim for * great precision here, so for now we simply divide 16.666 Mhz * by 21 which gives us an approximate 793kHz. * * In addition, to simplify the logic, the direction of the * port A and B bits is hard wired and the reset values adapted * to enable the ROM overlay at startup. * */ /* Register numbers */ `define VIA_REG_ORB 4'h0 `define VIA_REG_ORA 4'h1 `define VIA_REG_DDRB 4'h2 `define VIA_REG_DDRA 4'h3 `define VIA_REG_T1CL 4'h4 `define VIA_REG_T1CH 4'h5 `define VIA_REG_T1LL 4'h6 `define VIA_REG_T1LH 4'h7 `define VIA_REG_T2CL 4'h8 `define VIA_REG_T2CH 4'h9 `define VIA_REG_SR 4'ha `define VIA_REG_ACR 4'hb `define VIA_REG_PCR 4'hc `define VIA_REG_IFR 4'hd `define VIA_REG_IER 4'he `define VIA_REG_ORA2 4'hf /* Interrupt bits */ `define CA2_IRQ 0 `define CA1_IRQ 1 `define SR_IRQ 2 `define CB2_IRQ 3 `define CB1_IRQ 4 `define T2_IRQ 5 `define T1_IRQ 6 module via6522(input sysclk, input reset, /* Bus interface. 4-bit address, to be wired * appropriately upstream (to A9..A12) */ input cs, input we, input [3:0] rs, input [7:0] wdata, output [7:0] rdata, output _irq, /* Port A and B signals. Fixed direction. Port B bit * 0 (RTC data) is duplicated for in/out. */ input [7:7] pa_in7, output [6:0] pa_out, output [2:0] pb_out2_0, output pb_out7, input [6:3] pb_in6_3, input pb_in0, /* CA1 and CA2 are inputs always */ input ca1, input ca2, /* Pseudo shift-register keyboard interface */ input [7:0] sr_in, input sr_in_strobe, output [7:0] sr_out, output reg sr_out_strobe); /* Registers */ reg [7:0] pa; reg [7:0] pb; reg [7:0] sr; reg [7:0] acr; reg [6:0] ier; reg [6:0] ifr; reg [7:0] pcr; reg [15:0] t1; reg [15:0] t1l; reg [15:0] t2; reg [7:0] t2l; reg ddr_b0; wire pb0_v; wire ifr7; reg ca1_old; reg ca2_old; reg pb6_old; reg t1_armed; reg t2_armed; reg t1_fired; reg t2_fired; reg [4:0] ckdiv; wire ckhalf; reg t1pb7; wire tstrobe; wire t2strobe; wire t1irq; wire t2irq; /* Register selects */ wire rs_orb; wire rs_ora; wire rs_ddrb; wire rs_ddra; wire rs_t1cl; wire rs_t1ch; wire rs_t1ll; wire rs_t1lh; wire rs_t2cl; wire rs_t2ch; wire rs_sr; wire rs_acr; wire rs_pcr; wire rs_ifr; wire rs_ier; wire rs_ora2; assign rs_orb = cs && rs == `VIA_REG_ORB; assign rs_ora = cs && rs == `VIA_REG_ORA; assign rs_ddrb = cs && rs == `VIA_REG_DDRB; assign rs_ddra = cs && rs == `VIA_REG_DDRA; assign rs_t1cl = cs && rs == `VIA_REG_T1CL; assign rs_t1ch = cs && rs == `VIA_REG_T1CH; assign rs_t1ll = cs && rs == `VIA_REG_T1LL; assign rs_t1lh = cs && rs == `VIA_REG_T1LH; assign rs_t2cl = cs && rs == `VIA_REG_T2CL; assign rs_t2ch = cs && rs == `VIA_REG_T2CH; assign rs_sr = cs && rs == `VIA_REG_SR; assign rs_acr = cs && rs == `VIA_REG_ACR; assign rs_pcr = cs && rs == `VIA_REG_PCR; assign rs_ifr = cs && rs == `VIA_REG_IFR; assign rs_ier = cs && rs == `VIA_REG_IER; assign rs_ora2 = cs && rs == `VIA_REG_ORA2; /* Outputs & interrupt generation */ assign pa_out = pa[6:0] ; assign pb_out2_0 = pb[2:0]; assign pb_out7 = acr[7] ? t1pb7 : pb[7]; assign sr_out = sr; assign ifr7 = (ifr & ier) != 7'b0000000; assign _irq = ~ifr7; /* PB[0] value */ assign pb0_v = ddr_b0 ? pb[0] : pb_in0; /* Register reads */ assign rdata = rs_ora ? { pa_in7, pa[6:0] } : rs_ora2 ? { pa_in7, pa[6:0] } : rs_orb ? { pb[7], pb_in6_3, pb[2:1], pb0_v } : rs_ddrb ? { 1'b1, 4'b0000, 2'b11, ddr_b0 } : rs_ddra ? { 1'b0, 7'b1111111 } : rs_t1cl ? t1[7:0] : rs_t1ch ? t1[15:8] : rs_t1ll ? t1l[7:0] : rs_t1lh ? t1l[15:8] : rs_t2cl ? t2[7:0] : rs_t2ch ? t2[15:8] : rs_sr ? sr : rs_acr ? acr : rs_pcr ? pcr : rs_ifr ? { ifr7, ifr } : rs_ier ? { 1'b1, ier } : 8'b11111111; /* Write to ORA */ always@(posedge sysclk or posedge reset) begin if (reset) pa <= 8'b11111111; else if (we && (rs_ora || rs_ora2)) pa <= wdata; end /* Write to ORB */ always@(posedge sysclk or posedge reset) begin if (reset) pb <= 8'b11111111; else if (we && rs_orb) pb <= wdata; end /* Write to DDRA ignored */ /* Write to DDRB */ always@(posedge sysclk or posedge reset) begin if (reset) ddr_b0 <= 1'b0; else if (we && rs_orb) ddr_b0 <= wdata[0]; end /* Write to ACR */ always@(posedge sysclk or posedge reset) begin if (reset) acr <= 8'b0; else if (we && rs_acr) acr <= wdata; end /* Write to PCR */ always@(posedge sysclk or posedge reset) begin if (reset) pcr <= 8'b0; else if (we && rs_pcr) pcr <= wdata; end /* Timers * * Note: When writing to TnCH, we load the timer immediately * and start counting on the next tstrobe (ie. P2 clock), which * means that depending on when the CPU hits, the first P2 period * might be reduced to one sysclk ... this could be "fixed" by causing * all VIA accesses to wait for a P2 clock to complete but I'm happy * to ignore the "problem" for now. * * Also, to keep things simple, PB7 goes up right after writing to * T1CH, tho then follows proper periods (hopefully) */ /* Clock division. We divide by 21 to get approx 793kHz */ always@(posedge sysclk or posedge reset) begin if (reset) ckdiv <= 20; else begin if (ckdiv == 0) ckdiv <= 20; else ckdiv <= ckdiv - 1; end end assign tstrobe = (ckdiv == 0); assign ckhalf = (ckdiv == 10); /* Timer 1 latches */ always@(posedge sysclk or posedge reset) begin if (reset) t1l <= 0; else begin if (we && (rs_t1cl || rs_t1ll)) t1l[7:0] <= wdata; if (we && (rs_t1ch || rs_t1lh)) t1l[15:8] <= wdata; end end /* Timer 1 counter */ always@(posedge sysclk or posedge reset) begin if (reset) t1 <= 0; else begin /* Handle transfer from latch on write */ if (we && rs_t1ch) begin t1[7:0] <= t1l[7:0]; t1[15:8] <= wdata; end else if (tstrobe) begin /* In "free run", reload from latch * * XXX There's an inconsistency in the doco * as to whether we shall just roll over and * count in one-shot mode or whether we shall * -also- reload from the latch on T1 (it's * clear for T2). For now I just let it roll * over, we'll see if any SW gets upset */ if (acr[6] && t1_fired) t1 <= t1l; else t1 <= t1 - 1; end end end /* "armed" latch. This is set on a write and cleared when * running out in "one shot" mode */ always@(posedge sysclk or posedge reset) begin if (reset) t1_armed <= 0; else begin /* Always armed when in continuous mode, else * arm when writing to T1CH */ if ((we && rs_t1ch) || acr[6]) t1_armed <= 1; else if (t1_fired && !acr[6]) t1_armed <= 0; end end /* "fired" latch. This is set on tstrobe with counter == 0 * and is used to do whatever has to be done (irq, pb7, ...) * on the next half phase 2 clock. It's set for the duration * of the next P2 period. */ always@(posedge sysclk or posedge reset) begin if (reset) t1_fired <= 0; else begin if (tstrobe) t1_fired <= t1_armed && t1 == 0; end end /* Generate PB7 output */ always@(posedge sysclk or posedge reset) begin if (reset) t1pb7 <= 1; else begin /* We are 0 when timer is armed and not fired yet */ if (we && rs_t1ch) t1pb7 <= 0; /* We invert when fired */ if (ckhalf && t1_fired) t1pb7 <= ~t1pb7; end end /* T1 interrupt */ assign t1irq = ckhalf & t1_fired; /* Timer 2 * * Either one shot or count PB6 pulses (ie. hblank) */ always@(posedge sysclk or posedge reset) begin if (reset) pb6_old <= 1'b0; else pb6_old <= pb_in6_3[6]; end assign t2strobe = acr[5] ? (pb6_old & ~pb_in6_3[6]) : tstrobe; /* Timer 2 latche (low only) */ always@(posedge sysclk or posedge reset) begin if (reset) t2l <= 0; else begin if (we && rs_t2cl) t2l <= wdata; end end /* Timer 2 counter */ always@(posedge sysclk or posedge reset) begin if (reset) t2 <= 0; else begin /* Handle transfer from latch on write */ if (we && rs_t2ch) begin t2[7:0] <= t2l; t2[15:8] <= wdata; end else if (t2strobe) t2 <= t2 - 1; end end /* T2 "armed" latch (see T1) */ always@(posedge sysclk or posedge reset) begin if (reset) t2_armed <= 0; else begin if (we && rs_t2ch) t2_armed <= 1; else if (t2_fired) t2_armed <= 0; end end /* "fired" latch. This is set on tstrobe with counter == 0 * or in pulse counting right when hitting the 0 -> -1 * transitionand. It's set for the duration of the next P2 period * in the former case, and for one sysclk in the later case. */ always@(posedge sysclk or posedge reset) begin if (reset) t2_fired <= 0; else begin if (t2strobe) t2_fired <= t2_armed && t2 == 0; if (acr[5] && t2_fired) t2_fired <= 0; end end /* T2 Interrupt */ assign t2irq = acr[5] ? t2_fired : (t2_fired & ckhalf); /* Shift register * * CB1 is the kbd clock, CB2 the shift register, * we don't implement them that way, instead we * have a direct 8-bit bus to the PS2 conversion * module. The Mac only uses these ACR modes: * 111 used when sending to the keyboard * 110 used to assert the data line to 0 to start comm * 011 used when receiving from the keyboard * 000 used when transitioning * * We ignore the 110 mode as we don't need the * keyboard to clock us. Thus we only care about * mode 111 and 011. In the former, any write to * sr results in an sr_out_strobe pulse to signal * the keyboard. In mode 011, a pulse on sr_in_strobe * results in updating the SR and eventually signaling * an interrupt */ /* Write to SR (including external input) */ always@(posedge sysclk or posedge reset) begin if (reset) sr <= 8'b0; else begin if (we && rs_sr) sr <= wdata; if (acr[4:2] == 3'b011 && sr_in_strobe) sr <= sr_in; end end /* Generate sr_out_strobe */ always@(posedge sysclk or posedge reset) begin if (reset) sr_out_strobe <= 1'b0; else begin if (we && rs_sr && acr[4:2] == 3'b111) sr_out_strobe <= 1; else sr_out_strobe <= 0; end end /* CA1 / CA2 edge detection */ always@(posedge sysclk or posedge reset) begin if (reset) begin ca1_old <= 0; ca2_old <= 0; end else begin ca1_old <= ca1; ca2_old <= ca2; end end /* Interrupts */ /* Write to IER */ always@(posedge sysclk or posedge reset) begin if (reset) ier <= 1'b0; else if (we && rs_ier) begin if (wdata[7]) ier <= ier | wdata[6:0]; else ier <= ier & ~wdata[6:0]; end end /* Handle IFR */ always@(posedge sysclk or posedge reset) begin if (reset) ifr <= 8'b0; else begin /* First, write-1-to-clear */ if (we && rs_ifr) ifr <= ifr & ~wdata[6:0]; /* Now clear individual sources based on * register accesses */ if (rs_ora && (pcr[3:1] == 3'b000 || pcr[3:1] == 3'b010)) ifr[`CA2_IRQ] <= 0; if (rs_ora) ifr[`CA1_IRQ] <= 0; if (rs_sr) ifr[`SR_IRQ] <= 0; if (rs_orb && (pcr[7:5] == 3'b000 || pcr[7:5] == 3'b010)) ifr[`CB2_IRQ] <= 0; if (rs_orb) ifr[`CB1_IRQ] <= 0; if (we && rs_t2ch) ifr[`T2_IRQ] <= 0; if ((!we) && rs_t2cl) ifr[`T2_IRQ] <= 0; if (we && (rs_t1ch || rs_t1lh)) ifr[`T1_IRQ] <= 0; if ((!we) && rs_t1cl) ifr[`T1_IRQ] <= 0; /* Finally set sources based on relevant * events */ /* -- SR interrupt -- * /* Note that when shifting out, we strobe and * thus interrupt immediately when ACR is set * to 111 (shift under external clock). That is * sending commands to the keyboard. * * When ACR is set to 110 (sending a 0 pulse to * the keyboard to wake it up), we just swallow * everything here and don't generate an interrupt, * the Mac ROM seems to be happy enough that way. */ /* Shift out under control of external clock */ if (we && rs_sr && acr[4:2] == 3'b111) ifr[`SR_IRQ] <= 1; /* Shift in under control of external clock */ if (acr[4:2] == 3'b011 && sr_in_strobe) ifr[`SR_IRQ] <= 1; /* CA2 */ if (pcr[3] == 0 && ((!pcr[2] && ca2_old && !ca2) || ( pcr[2] && !ca2_old && ca2))) ifr[`CA2_IRQ] <= 1; /* CA1 */ if ((!pcr[0] && ca1_old && !ca1) || ( pcr[0] && !ca1_old && ca1)) ifr[`CA1_IRQ] <= 1; /* No CB1/CB2 on the mac ? Well they are kbd data * and clock, I don't think they are ever used, but * we may want to check... */ /* Timer IRQs */ if (t1irq) ifr[`T1_IRQ] <= 1; if (t2irq) ifr[`T2_IRQ] <= 1; end end endmodule