mirror of
https://github.com/Gehstock/Mist_FPGA.git
synced 2026-01-20 01:34:38 +00:00
484 lines
14 KiB
Verilog
484 lines
14 KiB
Verilog
`timescale 1ns / 1ps
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Engineer: Thomas Skibo
|
|
//
|
|
// Create Date: Sep 24, 2011
|
|
//
|
|
// Module Name: via6522
|
|
//
|
|
// Description:
|
|
//
|
|
// A simple implementation of the 6522 Versatile Interface Adapter (VIA).
|
|
// Tri-state lines aren't used. Instead, All PIA I/O signals have
|
|
// seperate "in" and "out" signals. Wire or ignore appropriately.
|
|
//
|
|
// A seperate "slow clock" (a synchronous pulse) runs the timers.
|
|
// Typically, it's 1Mhz.
|
|
//
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright (C) 2011, Thomas Skibo. All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
// * Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright
|
|
// notice, this list of conditions and the following disclaimer in the
|
|
// documentation and/or other materials provided with the distribution.
|
|
// * The names of contributors may not be used to endorse or promote products
|
|
// derived from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL Thomas Skibo OR CONTRIBUTORS BE LIABLE FOR
|
|
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
// SUCH DAMAGE.
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
module via6522
|
|
(
|
|
output reg [7:0] data_out, // cpu interface
|
|
input [7:0] data_in,
|
|
input [3:0] addr,
|
|
input strobe,
|
|
input we,
|
|
|
|
output reg irq,
|
|
|
|
output reg [7:0] porta_out,
|
|
input [7:0] porta_in,
|
|
output reg [7:0] portb_out,
|
|
input [7:0] portb_in,
|
|
|
|
input ca1_in,
|
|
output reg ca2_out,
|
|
input ca2_in,
|
|
output reg cb1_out,
|
|
input cb1_in,
|
|
output reg cb2_out,
|
|
input cb2_in,
|
|
|
|
input ce,
|
|
input clk,
|
|
input reset
|
|
);
|
|
|
|
// Register address offsets
|
|
parameter [3:0]
|
|
ADDR_PORTB = 4'h0,
|
|
ADDR_PORTA = 4'h1,
|
|
ADDR_DDRB = 4'h2,
|
|
ADDR_DDRA = 4'h3,
|
|
ADDR_TIMER1_LO = 4'h4,
|
|
ADDR_TIMER1_HI = 4'h5,
|
|
ADDR_TIMER1_LATCH_LO = 4'h6,
|
|
ADDR_TIMER1_LATCH_HI = 4'h7,
|
|
ADDR_TIMER2_LO = 4'h8,
|
|
ADDR_TIMER2_HI = 4'h9,
|
|
ADDR_SR = 4'ha,
|
|
ADDR_ACR = 4'hb,
|
|
ADDR_PCR = 4'hc,
|
|
ADDR_IFR = 4'hd,
|
|
ADDR_IER = 4'he,
|
|
ADDR_PORTA_NH = 4'hf;
|
|
|
|
wire wr_strobe = strobe && we;
|
|
wire rd_strobe = strobe && !we;
|
|
|
|
///////////////////////////////////////////////////
|
|
// IER - Interrupt Enable Register
|
|
reg [6:0] ier;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) ier <= 7'd0;
|
|
else if (wr_strobe && addr == ADDR_IER) ier <= data_in[7] ? (ier | data_in[6:0]) : (ier & ~data_in[6:0]);
|
|
end
|
|
|
|
////////////////////////////////////////////////////
|
|
// PCR - Peripheral Control Register
|
|
reg [7:0] pcr;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) pcr <= 8'h00;
|
|
else if (wr_strobe && addr == ADDR_PCR) pcr <= data_in;
|
|
end
|
|
|
|
//////////////////////////////////////////////////////
|
|
// ACR - Auxiliary Control Register
|
|
reg [7:0] acr;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) acr <= 8'h00;
|
|
else if (wr_strobe && addr == ADDR_ACR) acr <= data_in;
|
|
end
|
|
|
|
/////////////////////////////////////////////////////
|
|
// PORTs and DDRs
|
|
reg [7:0] ddra;
|
|
reg [7:0] ddrb;
|
|
reg pb7_nxt; // generated by timer1 logic, used when acr7 is set
|
|
|
|
// Implement PORTA (out)
|
|
always @(posedge clk) begin
|
|
if (reset) porta_out <= 8'h00;
|
|
else if (wr_strobe && (addr == ADDR_PORTA || addr == ADDR_PORTA_NH)) porta_out <= data_in;
|
|
end
|
|
|
|
// Implement DDRA
|
|
always @(posedge clk) begin
|
|
if (reset) ddra <= 8'h00;
|
|
else if (wr_strobe && addr == ADDR_DDRA) ddra <= data_in;
|
|
end
|
|
|
|
// Implement PORTB (out).
|
|
always @(posedge clk) begin
|
|
if (reset) portb_out[6:0] <= 7'h00;
|
|
else if (wr_strobe && addr == ADDR_PORTB) portb_out[6:0] <= data_in[6:0];
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) portb_out[7] <= 1'b0;
|
|
else if (acr[7]) portb_out[7] <= pb7_nxt;
|
|
else if (wr_strobe && addr == ADDR_PORTB) portb_out[7] <= data_in[7];
|
|
end
|
|
|
|
// Implement DDRB
|
|
always @(posedge clk) begin
|
|
if (reset) ddrb <= 8'h00;
|
|
else if (wr_strobe && addr == ADDR_DDRB) ddrb <= data_in;
|
|
end
|
|
|
|
////////////////////////////////////////////////////////
|
|
// CA interrupt logic
|
|
reg irq_ca1;
|
|
reg irq_ca2;
|
|
|
|
// CA1 and CA2 transition logic.
|
|
reg ca1_in_1;
|
|
reg ca2_in_1;
|
|
always @(posedge clk) begin
|
|
ca1_in_1 <= ca1_in;
|
|
ca2_in_1 <= ca2_in;
|
|
end
|
|
|
|
// detect "active" transitions.
|
|
wire ca1_act_trans = ((ca1_in && !ca1_in_1 && pcr[0]) ||
|
|
(!ca1_in && ca1_in_1 && !pcr[0]));
|
|
wire ca2_act_trans = ((ca2_in && !ca2_in_1 && pcr[2]) ||
|
|
(!ca2_in && ca2_in_1 && !pcr[2]));
|
|
|
|
// logic for clearing CA1 and CA2 interrupt bits.
|
|
wire irq_ca1_clr = ((strobe && addr == ADDR_PORTA) ||
|
|
(wr_strobe && addr == ADDR_IFR && data_in[1]));
|
|
wire irq_ca2_clr = ((strobe && addr == ADDR_PORTA) ||
|
|
(wr_strobe && addr == ADDR_IFR && data_in[0]));
|
|
|
|
always @(posedge clk) begin
|
|
if (reset || (irq_ca1_clr && !ca1_act_trans)) irq_ca1 <= 1'b0;
|
|
else if (ca1_act_trans) irq_ca1 <= 1'b1;
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset || (irq_ca2_clr && !ca2_act_trans)) irq_ca2 <= 1'b0;
|
|
else if (ca2_act_trans) irq_ca2 <= 1'b1;
|
|
end
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
// CB logic
|
|
reg irq_cb1;
|
|
reg irq_cb2;
|
|
|
|
// transition logic
|
|
reg cb1_in_1;
|
|
reg cb2_in_1;
|
|
always @(posedge clk) begin
|
|
cb1_in_1 <= cb1_in;
|
|
cb2_in_1 <= cb2_in;
|
|
end
|
|
|
|
// detect "active" transitions.
|
|
wire cb1_act_trans = ((cb1_in && !cb1_in_1 && pcr[4]) ||
|
|
(!cb1_in && cb1_in_1 && !pcr[4]));
|
|
wire cb2_act_trans = ((cb2_in && !cb2_in_1 && pcr[6]) ||
|
|
(!cb2_in && cb2_in_1 && !pcr[6]));
|
|
|
|
// logic for clearing CB1 and CB2 interrupt bits.
|
|
wire irq_cb1_clr = ((strobe && addr == ADDR_PORTB) ||
|
|
(wr_strobe && addr == ADDR_IFR && data_in[4]));
|
|
wire irq_cb2_clr = ((strobe && addr == ADDR_PORTB) ||
|
|
(wr_strobe && addr == ADDR_IFR && data_in[3]));
|
|
|
|
always @(posedge clk) begin
|
|
if (reset || (irq_cb1_clr && !cb1_act_trans)) irq_cb1 <= 1'b0;
|
|
else if (cb1_act_trans) irq_cb1 <= 1'b1;
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset || (irq_cb2_clr && !cb2_act_trans)) irq_cb2 <= 1'b0;
|
|
else if (cb2_act_trans) irq_cb2 <= 1'b1;
|
|
end
|
|
|
|
///////////////////////////////////////////////////
|
|
// CA2/CB2 output modes
|
|
always @(posedge clk) begin
|
|
case (pcr[3:1])
|
|
3'b100: ca2_out <= irq_ca1;
|
|
3'b101: ca2_out <= !ca1_act_trans;
|
|
3'b111: ca2_out <= 1'b1;
|
|
default: ca2_out <= 1'b0;
|
|
endcase
|
|
end
|
|
|
|
reg cb2_out_r;
|
|
wire portb_wr_strobe = wr_strobe && addr == ADDR_PORTB;
|
|
wire cb2_sr_out;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset || (portb_wr_strobe && !cb1_act_trans)) cb2_out_r <= 1'b0;
|
|
else if (cb1_act_trans) cb2_out_r <= 1'b1;
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (acr[4]) cb2_out <= cb2_sr_out;
|
|
else begin
|
|
case (pcr[7:5])
|
|
3'b100: cb2_out <= cb2_out_r;
|
|
3'b101: cb2_out <= !portb_wr_strobe;
|
|
3'b111: cb2_out <= 1'b1;
|
|
default: cb2_out <= 1'b0;
|
|
endcase
|
|
end
|
|
end
|
|
|
|
//////////////////////////////////////////////////////////
|
|
// Implement PORTA (in) latch
|
|
reg [7:0] porta_in_r;
|
|
always @(posedge clk) begin
|
|
if (!acr[0] || !irq_ca1) porta_in_r <= porta_in;
|
|
end
|
|
|
|
// Implement PORTB (in) latch
|
|
reg [7:0] portb_in_r;
|
|
always @(posedge clk) begin
|
|
if (!acr[1] || !irq_cb1) portb_in_r <= portb_in;
|
|
end
|
|
|
|
///////////////////////////////////////////////////
|
|
// Timers
|
|
reg [15:0] timer1;
|
|
reg [7:0] timer1_latch_lo;
|
|
reg [7:0] timer1_latch_hi;
|
|
|
|
reg [15:0] timer2;
|
|
reg [7:0] timer2_latch_lo;
|
|
|
|
reg irq_t1_one_shot;
|
|
reg irq_t1;
|
|
reg irq_t2_one_shot;
|
|
reg irq_t2;
|
|
|
|
// TIMER1
|
|
always @(posedge clk) begin
|
|
if (reset) timer1 <= 16'hffff;
|
|
else if (wr_strobe && addr == ADDR_TIMER1_HI) timer1 <= {data_in, timer1_latch_lo};
|
|
else if (timer1 == 16'h0000 && ce && acr[6]) timer1 <= {timer1_latch_hi, timer1_latch_lo};
|
|
else if (ce) timer1 <= timer1 - 1'b1;
|
|
end
|
|
|
|
// T1 latch lo
|
|
always @(posedge clk) begin
|
|
if (reset) timer1_latch_lo <= 8'hff;
|
|
else if (wr_strobe && (addr == ADDR_TIMER1_LO || addr == ADDR_TIMER1_LATCH_LO)) timer1_latch_lo <= data_in;
|
|
end
|
|
|
|
// T1 latch hi
|
|
always @(posedge clk) begin
|
|
if (reset) timer1_latch_hi <= 8'hff;
|
|
else if (wr_strobe && (addr == ADDR_TIMER1_HI || addr == ADDR_TIMER1_LATCH_HI)) timer1_latch_hi <= data_in;
|
|
end
|
|
|
|
// "one-shot" logic so we only get an interrupt on first counter roll-over
|
|
always @(posedge clk) begin
|
|
if (reset) irq_t1_one_shot <= 1'b0;
|
|
else if (wr_strobe && addr == ADDR_TIMER1_HI) irq_t1_one_shot <= 1'b1;
|
|
else if (timer1 == 16'h0000 && ce) irq_t1_one_shot <= 1'b0;
|
|
end
|
|
|
|
// T1 interrupt set and clear logic
|
|
wire irq_t1_set = (timer1 == 16'h0000 && ce && (irq_t1_one_shot || acr[6]));
|
|
wire irq_t1_clr = ((wr_strobe && addr == ADDR_TIMER1_HI) ||
|
|
(wr_strobe && addr == ADDR_TIMER1_LATCH_HI) ||
|
|
(rd_strobe && addr == ADDR_TIMER1_LO) ||
|
|
(wr_strobe && addr == ADDR_IFR && data_in[6]));
|
|
|
|
// T1 IRQ
|
|
always @(posedge clk) begin
|
|
if (reset || irq_t1_clr) irq_t1 <= 1'b0;
|
|
else if (irq_t1_set) irq_t1 <= 1'b1;
|
|
end
|
|
|
|
// I forget what this is for
|
|
always @(posedge clk) begin
|
|
if (reset) pb7_nxt <= 1'b1;
|
|
else if (wr_strobe && addr == ADDR_TIMER1_HI) pb7_nxt <= 1'b0;
|
|
else if (timer1 == 16'h0001 && ce) pb7_nxt <= !pb7_nxt;
|
|
end
|
|
|
|
// TIMER2
|
|
always @(posedge clk) begin
|
|
if (reset) timer2 <= 16'hffff;
|
|
else if (wr_strobe && addr == ADDR_TIMER2_HI) timer2 <= {data_in, timer2_latch_lo};
|
|
else if ((!acr[5] || !portb_in[6]) && ce) timer2 <= timer2 - 1'b1;
|
|
end
|
|
|
|
// T2 latch lo (i.e. writes to T2L)
|
|
always @(posedge clk) begin
|
|
if (reset) timer2_latch_lo <= 8'hff;
|
|
else if (wr_strobe && addr == ADDR_TIMER2_LO) timer2_latch_lo <= data_in;
|
|
end
|
|
|
|
// T2 IRQ "one-shot" logic
|
|
always @(posedge clk) begin
|
|
if (reset) irq_t2_one_shot <= 1'b0;
|
|
else if (wr_strobe && addr == ADDR_TIMER2_HI) irq_t2_one_shot <= 1'b1;
|
|
else if (timer2 == 16'h0000 && ce) irq_t2_one_shot <= 1'b0;
|
|
end
|
|
|
|
// T2 IRQ set and clear logic
|
|
wire irq_t2_set = (timer2 == 16'h0000 && ce && irq_t2_one_shot);
|
|
wire irq_t2_clr = ((wr_strobe && addr == ADDR_TIMER2_HI) ||
|
|
(rd_strobe && addr == ADDR_TIMER2_LO) ||
|
|
(wr_strobe && addr == ADDR_IFR && data_in[5]));
|
|
|
|
// T2 IRQ
|
|
always @(posedge clk) begin
|
|
if (reset || irq_t2_clr) irq_t2 <= 1'b0;
|
|
else if (irq_t2_set) irq_t2 <= 1'b1;
|
|
end
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
// SR - shift register
|
|
reg [7:0] sr;
|
|
reg [2:0] sr_cntr;
|
|
reg [7:0] sr_clk_div_ctr;
|
|
reg sr_clk_div;
|
|
reg irq_sr;
|
|
reg sr_go;
|
|
reg do_shift;
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) sr <= 8'h00;
|
|
else if (wr_strobe && addr == ADDR_SR) sr <= data_in;
|
|
else if (do_shift) sr <= { sr[6:0], (acr[4] ? sr[7] : cb2_in) };
|
|
end
|
|
|
|
assign cb2_sr_out = sr[7];
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) sr_clk_div_ctr <= 8'd0;
|
|
else if (ce && sr_clk_div_ctr == 8'd0) sr_clk_div_ctr <= timer2_latch_lo;
|
|
else if (ce) sr_clk_div_ctr <= sr_clk_div_ctr - 1'b1;
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) sr_clk_div <= 1'b0;
|
|
else sr_clk_div <= (ce && sr_clk_div_ctr == 8'd0);
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset || (strobe && addr == ADDR_SR)) sr_cntr <= 3'd7;
|
|
else if (do_shift) sr_cntr <= sr_cntr - 1'b1;
|
|
end
|
|
|
|
// SR IRQ set and clr logic
|
|
wire irq_sr_set = do_shift && sr_cntr == 3'b000;
|
|
wire irq_sr_clr = ((strobe && addr == ADDR_SR) || (wr_strobe && addr == ADDR_IFR && data_in[2]));
|
|
|
|
// SR IRQ
|
|
always @(posedge clk) begin
|
|
if (reset || (irq_sr_clr && !irq_sr_set)) irq_sr <= 1'b0;
|
|
else if (irq_sr_set) irq_sr <= 1'b1;
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) sr_go <= 1'b0;
|
|
else if (strobe && addr == ADDR_SR) sr_go <= 1'b1;
|
|
else if (irq_sr_set) sr_go <= 1'b0;
|
|
end
|
|
|
|
// cominatorial logic for do_shift signal.
|
|
always @(sr_clk_div or ce or cb1_act_trans or sr_go or acr) begin
|
|
case (acr[4:2])
|
|
3'b000: do_shift = 1'b0;
|
|
3'b100: do_shift = sr_clk_div;
|
|
3'b001,
|
|
3'b101: do_shift = (sr_go && sr_clk_div);
|
|
3'b010,
|
|
3'b110: do_shift = (sr_go && ce);
|
|
3'b011,
|
|
3'b111: do_shift = cb1_act_trans;
|
|
endcase
|
|
end
|
|
|
|
always @(posedge clk) begin
|
|
if (reset) cb1_out <= 1'b1;
|
|
else if (do_shift) cb1_out <= !cb1_out;
|
|
end
|
|
|
|
////////////////////////////////////////////////////////
|
|
// IRQ and enable logic.
|
|
//
|
|
|
|
// IFR register (not including bit 7)
|
|
wire [6:0] ifr = { irq_t1, irq_t2, irq_cb1, irq_cb2, irq_sr, irq_ca1, irq_ca2 };
|
|
|
|
// IRQ combinatorial logic
|
|
wire irq_p = |{ (ifr & ier) };
|
|
|
|
// IRQ output
|
|
always @(posedge clk) begin
|
|
if (reset) irq <= 1'b0;
|
|
else irq <= irq_p;
|
|
end
|
|
|
|
///////////////////////////////////////////////////
|
|
// Read data mux
|
|
wire [7:0] porta = (porta_out & ddra) | (porta_in_r & ~ddra);
|
|
wire [7:0] portb = (portb_out & ddrb) | (portb_in_r & ~ddrb);
|
|
|
|
always @(*) begin
|
|
case (addr)
|
|
ADDR_PORTB: data_out = portb;
|
|
ADDR_PORTA: data_out = porta;
|
|
ADDR_DDRB: data_out = ddrb;
|
|
ADDR_DDRA: data_out = ddra;
|
|
ADDR_TIMER1_LO: data_out = timer1[7:0];
|
|
ADDR_TIMER1_HI: data_out = timer1[15:8];
|
|
ADDR_TIMER1_LATCH_LO: data_out = timer1_latch_lo;
|
|
ADDR_TIMER1_LATCH_HI: data_out = timer1_latch_hi;
|
|
ADDR_TIMER2_LO: data_out = timer2[7:0];
|
|
ADDR_TIMER2_HI: data_out = timer2[15:8];
|
|
ADDR_IER: data_out = {1'b1, ier};
|
|
ADDR_PCR: data_out = pcr;
|
|
ADDR_ACR: data_out = acr;
|
|
ADDR_IFR: data_out = {irq_p, ifr};
|
|
ADDR_SR: data_out = sr;
|
|
ADDR_PORTA_NH: data_out = porta;
|
|
default: data_out = 8'hXX;
|
|
endcase
|
|
end
|
|
|
|
endmodule // via6522
|