// part of NeoGS project (c) 2007-2008 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
 
//
 
//
 
// TODO: add DMA functionality
 
 
 
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
 
 
 
        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)
 
 
 
        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;
 
 
 
        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
 
 
 
        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 switcher
 
 
 
        assign dbin[7:0] = zxid;
 
 
 
        always @*
 
        begin
 
                if( zxbusin == 1'b1 )
 
                begin
 
                        zxid <= 8'bZZZZZZZZ;
 
                end
 
                else // zxbusin==0
 
                begin
 
                        zxid <= dbout[7:0];
 
                end
 
        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
 
// +
 
 
 
 
 
// stubs for possible dma mode
 
 
 
        always @*
 
        begin
 
                zxblkrom_n <= 1'b1;
 
                zxgenwait_n <= 1'b1;
 
        end
 
 
 
 
 
// 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
 
        // +
 
 
 
 
 
// read to zxbus some registers
 
 
 
        always @*
 
        begin
 
                if( (!zxiord_n) && ( zxdataport || zxcommport ) )
 
                        zxbusin <= 1'b0;
 
                else
 
                        zxbusin <= 1'b1;
 
 
 
                if( ( (!zxiowr_n) || (!zxiord_n) ) && ( zxdataport || zxcommport || zxrstport ) )
 
                        zxbusena_n <= 1'b0;
 
                else
 
                        zxbusena_n <= 1'b1;
 
        end
 
 
 
        always @*
 
        begin
 
                case( {zxdataport,zxcommport} )
 
                        2'b10:   dbout <= data_reg_in;
 
                        2'b01:   dbout <= { data_bit, 6'bXXXXXX, command_bit };
 
                        default: dbout <= 8'bXXXXXXXX;
 
                endcase
 
        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