aboutsummaryrefslogtreecommitdiffstats
path: root/t80pio
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2003-09-12 04:42:55 +0000
committerH. Peter Anvin <hpa@zytor.com>2003-09-12 04:42:55 +0000
commit7a4493a8ebd6633f0f892cdb4a035c7d348f8392 (patch)
treefdebdfd540c5e470821aa712aa9e90d107097b21 /t80pio
parent04ed1bd635b5566f115cde9ab44204337149f406 (diff)
downloadabc80-7a4493a8ebd6633f0f892cdb4a035c7d348f8392.tar.gz
abc80-7a4493a8ebd6633f0f892cdb4a035c7d348f8392.tar.xz
abc80-7a4493a8ebd6633f0f892cdb4a035c7d348f8392.zip
*** empty log message ***
Diffstat (limited to 't80pio')
-rw-r--r--t80pio/README32
-rw-r--r--t80pio/t80pio.v228
2 files changed, 260 insertions, 0 deletions
diff --git a/t80pio/README b/t80pio/README
new file mode 100644
index 0000000..f5f012e
--- /dev/null
+++ b/t80pio/README
@@ -0,0 +1,32 @@
+This is a peripheral device modelled after Zilog's Z80PIO. It
+diverges from the Z80PIO in the following ways, in order to make it
+better suited for embedded implementation without the constraints of
+physical pin count:
+
+- The Z80PIO is a dual-port device, which in some conditions "borrow"
+ pins (BSTB# and BRDY are sometimes used by port A), but otherwise
+ the two ports are completely independent. This implementations
+ contains only one port; however, instantiating two to create a
+ two-port module is trivial; including the pin-sharing behaviour of a
+ Z80PIO (connect BSTB# to both A.BSTB_n and B.ASTB_n;
+ assign BRDY = A.BRDY | B.ARDY).
+
+- The pin names are consistent with the "A" port of the Z80PIO. For
+ the bidirectional busses D7..D0 and A7..A0 I have separated out the
+ input and output sides as DI/DO and AI/AO since embedded
+ applications usually do not use multidriver tristate busses. The
+ suffix _n is used for inverted signals (since neither Verilog nor
+ VHDL allows # in symbol names.)
+
+- The Z80PIO snoops the CPU data bus to recognize the RETI
+ instruction. Since this implementation doesn't use true shared
+ busses, an additional input, RETI#, has been added; this is expected
+ to be driven low by the CPU (or by additional logic plugged into the
+ CPU input data bus) when executing the RETI instruction.
+
+- The reset is not multiplexed onto the M1# pin like it is on the
+ Z80PIO. Instead, a RESET# input is provided.
+
+- The output data bus (Do) is set to all ones at any time it wouldn't
+ be driven by Z80PIO. The intent is that all the data outputs can be
+ AND'd together before sending to the CPU.
diff --git a/t80pio/t80pio.v b/t80pio/t80pio.v
new file mode 100644
index 0000000..f2f6e7c
--- /dev/null
+++ b/t80pio/t80pio.v
@@ -0,0 +1,228 @@
+module T80PIO (
+ CLK_n,
+ RST_n,
+
+ Di,
+ Do,
+ CDsel, /* C/D# sel */
+ CE_n,
+ M1_n,
+ IORQ_n,
+ RD_n,
+ IEI,
+ IEO,
+ INT_n,
+
+ Ai,
+ Ao,
+ ASTB_n,
+ ARDY,
+ BSTB_n,
+ BRDY
+ );
+ input CLK_n;
+ input RST_n;
+
+ input [7:0] Di;
+ output [7:0] Do;
+ input CDsel;
+ input CE_n;
+ input M1_n;
+ input IORQ_n;
+ input RD_n;
+ input IEI;
+ input IEO;
+ input INT_n;
+ input [7:0] Ai;
+ input [7:0] Ao;
+ input ASTB_n;
+ output ARDY;
+ input BSTB_n;
+ output BRDY;
+
+ reg [7:1] V; // Interrupt vector
+ reg [1:0] M; // Operating mode
+ reg [7:0] IO; // Mode 3 direction mask
+ reg [3:0] ICW; // Interrupt control word
+ reg [7:0] MB; // Interrupt mask
+ reg [7:0] D; // Output direction data register
+ reg [7:0] Do_q; // Data out to CPU
+
+ reg io_next; // Next control word is I/O mask
+ reg mb_next; // Next control word is interrupt mask
+ reg i_data_flag; // Have input data
+ reg o_data_flag; // Have input data
+ reg ASTB_n_old; // Previous ASTB_n
+
+ wire [7:0] data; // "Current data"
+ reg [7:0] data_in; // Latched input data (Di)
+
+ // Data as it would be read by the CPU
+ assign read_data =
+ (M == 2'b00) ? D :
+ (M == 2'b01) ? data_in :
+ (M == 2'b10) ? data_in :
+ (M == 2'b11) ? (D & ~IO) | (data_in & IO);
+
+ assign Do = Do_q;
+
+ // Output data word (note: pins which would be tristated on Z80PIO
+ // we feed back the input.)
+ assign Ao =
+ (M == 2'b00) ? D :
+ (M == 2'b01) ? data_in :
+ (M == 2'b10) ? D :
+ (M == 2'b11) ? (D & ~IO) | (data_in & IO);
+
+ // Output RDY signals
+ assign ARDY =
+ (M[0] == 1'b0) ? o_data_flag :
+ (M == 2'b01) ? ~i_data_flag :
+ 0;
+ assign BRDY = (M == 2'b10) && ~i_data_flag;
+
+ // This signal is high if we should latch the input data
+ assign latch_in =
+ ((M == 2'b01 && ~ASTB_n) |
+ (M == 2'b10 && ~BSTB_n) |
+ (M == 2'b11)) &
+ ~( ~cpu_iorq_n & ~cpu_ce_n );
+
+ // Output INT#
+ reg need_irq; // Need to issue interrupt
+ reg intak; // INTAK in process
+ reg servicing_irq; // Waiting for RETI
+
+ assign INT_n = ~( need_irq & IEI );
+ assign IEO = IEI & ~need_irq & ~intak & ~servicing_irq;
+
+ always @(posedge CLK_n or negedge RST_n)
+ begin
+ if ( !RST_n )
+ begin
+ M <= 2'b01;
+ IO <= 0;
+ ICW <= 0;
+ MB <= 0;
+ D <= 0;
+ io_next <= 0;
+ mb_next <= 0;
+ need_irq <= 0;
+ intak <= 0;
+ servicing_irq <= 0;
+ ASTB_n_old <= ASTB_n;
+ Do_q <= ~0; // Output all ones when "inactive"
+ end
+ else
+ begin
+ Do_q <= ~0;
+
+ if ( M1_n & ~IORQ_n & ~CE_n )
+ begin
+ case ( { RD_n, CDsel } )
+ 2'b00: // Data Read
+ begin
+ i_data_flag <= 0;
+ Do_q <= read_data;
+ end
+
+ 2'b01: // Control read
+ begin
+ // No readable control registers
+ end
+
+ 2'b10: // Data Write
+ begin
+ D <= Di;
+ o_data_flag <= 1;
+ end
+
+ 2'b11: // Control Write
+ begin
+ io_next <= 0;
+ mb_next <= 0;
+
+ if ( io_next )
+ IO <= Di;
+ else if ( mb_next )
+ MB <= Di
+ else
+ begin
+ casex ( Di[3:0] )
+ 4'bxxx0:
+ V[7:1] <= Di[7:1];
+ 4'b1111:
+ begin
+ M[1:0] <= Di[7:6];
+ io_next <= (Di[7:6] == 2'b11);
+ end
+ 4'b0111:
+ begin
+ ICW[3:0] <= Di[7:4];
+ mb_next <= Di[4];
+ end
+ 4'b0011:
+ begin
+ ICW[3] <= Di[7];
+ end
+ endcase // casex( Di[3:0] )
+ end // else: !if( mb_next )
+ end // case: 2'b11
+ endcase // case( { RD_n, CDsel } )
+ end // if ( ~IORQ_n & ~CE_n )
+ else
+ // Latch input data every cycle unless we are
+ // currently being addressed by the CPU
+ if ( latch_in )
+ data_in <= Di;
+
+ // Input strobe detect
+ if ( ASTB_n & ~ASTB_n_old ) // ASTB_n rising edge
+ begin
+ if ( M[0] == 1'b0 ) // Output or Bidir modes
+ o_data_flag <= 0; // Data sent, now empty
+ else if ( M == 2'b01 ) // Input mode
+ i_data_flag <= 1; // Data received, now full
+
+ if ( M[0] != 2'b11 && ICW[3] )
+ need_irq <= 1;
+ end
+
+ // Delayed ASTB# for edge detection
+ ASTB_n_old <= ASTB_n;
+
+ // Mode 3 interrupt control
+ if ( M == 2'b11 && ICW[3] )
+ begin
+ wire [7:0] xor_mask = ICW[1] ? 8'hFF : 8'h00;
+
+ wire irq_cond = ICW[2] ?
+ // "AND" mode
+ !((read_data ^ xor_mask) & MB) :
+ // "OR" mode
+ ((read_data ^ ~xor_mask) & MB);
+
+ need_irq <= need_irq | irq_cond;
+ end // if ( M == 2'b11 && ICW[3] )
+
+ // Actual interrupt-generating logic. We have two
+ // possible states to be in; both can be active:
+ // need_irq (INT# -> INTAK) and servicing_irq (INTAK -> RETI).
+ if ( ~M1_n & ~IORQ_n & ((need_irq & IEI) | intak) )
+ begin
+ Do_q <= { V, 1'b0 };
+ servicing_irq <= 1;
+ intak <= 1;
+ need_irq <= 0;
+ end
+ else
+ intak <= 0;
+
+ if ( ~RETI_n )
+ servicing_irq <= 0;
+
+ end // else: !if( !RST_n )
+ end // always @ (posedge CLK_n or negedge RST_n)
+endmodule // T80PIO
+
+ \ No newline at end of file