// part of NeoGS project (c) 2007-2009 NedoPC
//
// ZXBUS interface module
// Features:
// I. provides IORQGE generation via zxblkiorq_n output on addresses $xxB3, $xxBB and $xx33
// II. Asynchronously outs data_reg_in or data_bit and command bit to zxbus
// III. Asynchronouly latches in data_reg_out and command_reg_out from zxbus
// IV. Synchronously updates data_bit and command_bit according to zxbus activity,
// or sets it to *_bit_in value when *_bit_wr is 1
// V. Generates nmi_n negative 8-clock pulse when $010xxxxx is written to $xx33 port.
// Initiates internal reset when $100xxxxx is written to $xx33 port
// Toggles LED when $001xxxxx is written
// VI. Manages zx-dma
//
module zxbus(
zxid, // zxbus DATA BUS in/out [7:0]
zxa, // zxbus ADDRESS 7-0 in [7:0]
zxa14,zxa15, // zxbus ADDRESS 14,15
zxiorq_n,zxmreq_n, // zxbus /IORQ,/MREQ
zxrd_n,zxwr_n, // zxbus /RD, /WR
zxcsrom_n, // zxbus /CSROM
zxblkiorq_n, // active low - signals to generate IORQGE
zxblkrom_n, // active low - signals to block onboard zx ROM
zxgenwait_n, // active low - signals to generate /WAIT to ZXBUS
zxbusin, // controls 74hct245 buffer direction (1 - input from bus, 0 - output to zx)
zxbusena_n, // 74hct245 buffer enable signal
// ports related
command_reg_out, // output data of command register (asynchronous)
data_reg_out, // output data of data register (asynchronous)
data_reg_in, // input data of data register (asynchronous)
data_bit, // data bit out (synchronous to cpu_clock)
command_bit, // command bit out (synchronous to cpu_clock)
data_bit_in, // input data to be written to data bit
command_bit_in, // input data to be written to command bit
data_bit_wr, // write strobe (positive), which writes data from data_bit_in to data_bit (sync to cpu_clock)
command_bit_wr, // write strobe (positive), which writes data from command_bit_in to command_bit (sync to cpu_clock)
//
// DMA related
dma_on, // input, must be 1 for DMA to operate
wait_ena, // input, asynchronously resets wait trigger (signal synchronous to cpu_clock of NGS)
dmaread, // for use in dma_zx module
dmawrite, //
dma_data_written, // output, stores data last written by ZX
dma_data_toberead, // input, shows data for ZX to read
rst_from_zx_n, // reset out to NGS z80 indicating board must be reset
nmi_n, // nmi out to NGS z80 (2^NMI_CNT_SIZE cpu_clock periods low pulse) - synchronous!
led_toggle,
rst_n, // chip-wide reset input (some critical init)
cpu_clock // NGS Z80 CPU clock in (clk_fpga on schematics)
);
parameter NMI_CNT_SIZE = 2;
localparam GSCOM = 8'hBB;
localparam GSSTAT = GSCOM;
localparam GSDAT = 8'hB3;
localparam GSCTR = 8'h33;
// INPUTS/OUTPUTS of module
inout reg [7:0] zxid;
input [7:0] zxa;
input zxa14,zxa15;
input zxiorq_n,zxmreq_n;
input zxrd_n,zxwr_n;
input zxcsrom_n;
output reg zxblkiorq_n,zxblkrom_n,zxbusin,zxbusena_n,zxgenwait_n;
output reg [7:0] command_reg_out;
output reg [7:0] data_reg_out;
input [7:0] data_reg_in;
output reg data_bit;
output reg command_bit;
input data_bit_in;
input command_bit_in;
input data_bit_wr;
input command_bit_wr;
input dma_on;
output dmaread; // =1 when dma enabled and ZX reads from 0000-3fff and ROM is ON (csrom_n active)
output dmawrite; // =1 when dma enabled and ZX writes to 0000-3fff (no ROM dependent)
input wait_ena;
output reg [7:0] dma_data_written;
input [7:0] dma_data_toberead;
output reg rst_from_zx_n;
output reg nmi_n;
output reg led_toggle;
input cpu_clock;
input rst_n;
// internal regs and wires
wire [7:0] dbin; // input data from zx data bus
reg [7:0] dbout; // output to the zx data bus
wire [7:0] zxlowaddr; // low address on ZX address bus
wire zxdataport; // =1 when data port address selected ($B3)
wire zxcommport; // =1 when command port address selected ($BB)
wire zxrstport; // =1 when reset/nmi port address selected ($33)
wire zxiord_n; // = iorq_n | rd_n
wire zxiowr_n; // = iorq_n | wr_n
wire dmaread; // =1 when dma enabled and ZX reads from 0000-3fff and ROM is ON (csrom_n active)
wire dmawrite; // =1 when dma enabled and ZX writes to 0000-3fff (no ROM dependent)
wire romaddr; // =1 when rom address is on zxbus
reg [2:0] rddataport; // for data_bit
reg [2:0] wrdataport; // synchronous of and
reg [2:0] wrcommport; // control command_bit
reg async_rst_toggle; // asynchronous toggles on writes to port $33
reg async_nmi_toggle;
reg async_led_toggle;
reg [2:0] sync_rst_toggle; // syncing toggles in and detect edges
reg [2:0] sync_nmi_toggle; // generate z80res or nmi on every edge
reg [2:0] sync_led_toggle; // LED toggle on every edge
reg prezxrst1,prezxrst2; // reset out (rst_from_zx_n) must have some negation delay when rst_n asserts
reg [NMI_CNT_SIZE:0] nmi_counter; // counter to make 2^NMI_CNT_SIZE cpu_clock's pulses, plus stopbit
// actual code
//---------------------------------------------------------------------------
// zx data bus control
assign dbin[7:0] = zxid;
// control tri-state in-outs of internal zx data bus (zxid)
always @*
begin
if( (!zxbusin) && (!zxbusena_n) )
zxid <= dbout[7:0];
else
zxid <= 8'bZZZZZZZZ;
end
// control both state of zxid and state of external '245 buffer
always @*
begin
if( ((!zxiord_n) && ( zxdataport || zxcommport )) || dmaread )
zxbusin = 1'b0;
else
zxbusin = 1'b1;
if( ( ((!zxiowr_n) || (!zxiord_n) ) && ( zxdataport || zxcommport || zxrstport )) || dmaread || dmawrite )
zxbusena_n <= 1'b0;
else
zxbusena_n <= 1'b1;
end
// control data to be out on the bus
always @*
begin
if( dmaread )
dbout = dma_data_toberead;
else
case( {zxdataport,zxcommport} )
3'b10: dbout <= data_reg_in;
3'b01: dbout <= { data_bit, 6'bXXXXXX, command_bit };
default: dbout <= 8'hXX;
endcase
end
// +
// zx address decoder, IORQGE generator
assign zxdataport = (zxa == GSDAT); // =1 when $B3
assign zxcommport = (zxa == GSCOM); // =1 when $BB
assign zxrstport = (zxa == GSCTR); // =1 when $33
always @*
begin
if( zxdataport || zxcommport || zxrstport ) // address if any of ports is on bus
zxblkiorq_n <= 1'b0; // generate IORQGE!
else
zxblkiorq_n <= 1'b1;
end
// +
// zx DMA related things
// zx ROM address decoder
assign romaddr = ( {zxa15,zxa14}==2'b00 );
// block zx rom access if dma is on
always @*
begin
if( romaddr && dma_on )
zxblkrom_n = 1'b0;
else
zxblkrom_n = 1'b1;
end
// control /WAIT pin of ZXBUS
always @*
begin
if( dma_on && romaddr && (!zxmreq_n) ) // on write cycles, /WAIT is accepted at the same clock edge
zxgenwait_n = ~wait_ena; // the Z80 begins /WR, so we can't use /WR (and then /RD)
else // for /WAIT strobing
zxgenwait_n = 1'b1;
end
// zx to dma write and read pulses
assign dmaread = dma_on & romaddr & (~zxmreq_n) & (~zxrd_n) & (~zxcsrom_n);
assign dmawrite = dma_on & romaddr & (~zxmreq_n) & (~zxwr_n);
// store data
always @(negedge dmawrite) dma_data_written <= dbin;
// +
// I/O RD and WR strobes
assign zxiord_n = zxiorq_n | zxrd_n;
assign zxiowr_n = zxiorq_n | zxwr_n;
// +
// write from zxbus to the data register
always @(posedge zxiowr_n)
begin
if( zxdataport )
begin
data_reg_out <= dbin;
end
end
// +
// write from zxbus to the command register
always @(posedge zxiowr_n)
begin
if( zxcommport )
begin
command_reg_out <= dbin;
end
end
// +
// SYNCHRONOUS PART
// ---------------------------------------------------
// synchronous control of port writes and reads
always @(posedge cpu_clock) // sync in read and write states
begin
rddataport[2:0] <= { rddataport[1:0], zxdataport&(~zxiord_n) };
wrdataport[2:0] <= { wrdataport[1:0], zxdataport&(~zxiowr_n) };
wrcommport[2:0] <= { wrcommport[1:0], zxcommport&(~zxiowr_n) };
end
// data_bit
always @(posedge cpu_clock)
begin
if( rddataport[2:1]==2'b10 )
begin
data_bit <= 1'b0; // clear on data port reading by ZX (after end of cycle)
end
else if( wrdataport[2:1]==2'b10 )
begin
data_bit <= 1'b1; // set on data port writing by ZX
end
else if( data_bit_wr==1'b1 )
begin
data_bit <= data_bit_in; // or load from internal NGS operation
end
end
// +
// command bit
always @(posedge cpu_clock)
begin
if( wrcommport[2:1]==2'b10 )
begin
command_bit <= 1'b1; // set on command port writing by ZX
end
else if( command_bit_wr==1'b1 )
begin
command_bit <= command_bit_in; // or load from internal NGS operation
end
end
// +
///////////////////////////////
// handle reset/nmi port $33 //
///////////////////////////////
always @(negedge rst_n,posedge zxiowr_n)
begin
if( !rst_n )
begin
async_rst_toggle <= 1'b0;
async_nmi_toggle <= 1'b0;
async_led_toggle <= 1'b0;
end
else if( zxrstport )
begin
if( dbin[7:5]==3'b100 )
async_rst_toggle <= ~async_rst_toggle;
if( dbin[7:5]==3'b010 )
async_nmi_toggle <= ~async_nmi_toggle;
if( dbin[7:5]==3'b001 )
async_led_toggle <= ~async_led_toggle;
end
end
// +
always @(negedge rst_n, posedge cpu_clock)
begin
if( !rst_n )
begin
sync_rst_toggle[2:0] <= 3'd0;
sync_nmi_toggle[2:0] <= 3'd0;
sync_led_toggle[2:0] <= 3'd0;
nmi_counter[NMI_CNT_SIZE:0] <= 32'hFFFFFFFF;
prezxrst1 <= 1'b1;
nmi_n <= 1'b1;
led_toggle <= 1'b0;
end
else // rst_n=1
begin
sync_rst_toggle[2:0] <= { sync_rst_toggle[1:0], async_rst_toggle };
sync_nmi_toggle[2:0] <= { sync_nmi_toggle[1:0], async_nmi_toggle };
sync_led_toggle[2:0] <= { sync_led_toggle[1:0], async_led_toggle };
if( sync_rst_toggle[2] != sync_rst_toggle[1] )
begin
prezxrst1 <= 1'b0;
end
if( sync_nmi_toggle[2] != sync_nmi_toggle[1] )
begin
nmi_counter[NMI_CNT_SIZE:0] <= 0;
end
else
begin
if( !nmi_counter[NMI_CNT_SIZE] )
nmi_counter <= nmi_counter + 1;
end
nmi_n <= nmi_counter[NMI_CNT_SIZE];
if( sync_led_toggle[2] != sync_led_toggle[1] )
led_toggle <= 1'b1;
else
led_toggle <= 1'b0;
end
end
// +
always @(posedge cpu_clock)
begin
prezxrst2 <= prezxrst1;
rst_from_zx_n <= prezxrst2;
end
// +
endmodule