// part of NeoGS project
 
//
 
// (c) NedoPC 2007-2009
 
//
 
// ZX dma controller
 
//
 
// includes dma address regs, dma control reg
 
 
 
module dma_zx(
 
 
 
        input clk,
 
        input rst_n,
 
 
 
 
 
        // ZXBUS-related signals
 
 
 
        input zxdmaread,  // async strobes made directly from zxbus signals
 
        input zxdmawrite, //
 
 
 
        input      [7:0] dma_wr_data, // data written by ZXBUS here
 
        output reg [7:0] dma_rd_data, // to be output to the ZXBUS from here
 
 
 
        output reg wait_ena, // for zxbus module, to stop temporarily ZX Z80
 
 
 
 
 
 
 
 
 
        // different global & control signals
 
 
 
        output reg dma_on,
 
 
 
 
 
        // signals from ports.v
 
 
 
        input      [7:0] din,  // input and output from ports.v
 
        output reg [7:0] dout,
 
 
 
        input module_select, // =1 - module selected for read-write operations from ports.v
 
        input write_strobe, // one-cycle positive write strobe - writes to the selected registers from din
 
 
 
        input [1:0] regsel, // 2'b00 - high address, 2'b01 - middle address, 2'b10 - low address, 2'b11 - control register
 
 
 
 
 
        // signals for DMA controller
 
 
 
      output reg [21:0] dma_addr,
 
      output reg  [7:0] dma_wd,
 
      input       [7:0] dma_rd,
 
      output reg        dma_rnw,
 
 
 
      output reg dma_req,
 
      input      dma_ack,
 
      input      dma_end
 
 
 
);
 
 
 
        reg [7:0] dma_rd_temp; // temporarily buffered read data from DMA module
 
 
 
        reg zxdmaread_sync;  // syncing appropriate zxbus signals - stage 1
 
        reg zxdmawrite_sync; //
 
 
 
        reg [1:0] zxdmaread_strobe;  // syncing zxbus signals: stage 2 and change detection
 
        reg [1:0] zxdmawrite_strobe; //
 
 
 
 
 
        reg zxread_beg, zxwrite_beg; // 1-cycle positive pulses based on synced in zxdmaread and zxdmawrite
 
        reg zxread_end, zxwrite_end; //
 
 
 
 
 
        reg dma_prireq; // primary dma request
 
        reg dma_prirnw; // primary rnw for dma request
 
 
 
        reg waitena_reg; // registered wait_ena
 
        reg waitena_fwd; // forwarded early wait_ena: output wait_ena made from both this signals
 
 
 
 
 
        reg [3:0] zdma_state, zdma_next; // main FSM for zx-dma
 
 
 
        reg [1:0] dmarq_state,dmarq_next; // DMA req gen
 
 
 
 
 
 
 
        localparam _HAD = 2'b00; // high address
 
        localparam _MAD = 2'b01; // mid address
 
        localparam _LAD = 2'b10; // low address
 
        localparam _CST = 2'b11; // control and status
 
 
 
        // control dout bus
 
        always @*
 
        case( regsel[1:0] )
 
                _HAD: dout = { 2'b00, dma_addr[21:16] };
 
                _MAD: dout = dma_addr[15:8];
 
                _LAD: dout = dma_addr[7:0];
 
                _CST: dout = { dma_on, 7'bXXXXXXX };
 
        endcase
 
 
 
        // ports.v write access & dma_addr control
 
        always @(posedge clk, negedge rst_n)
 
        if( !rst_n ) // async reset
 
        begin
 
                dma_on <= 1'b0;
 
        end
 
        else // posedge clk
 
        begin
 
                // dma_on control
 
                if( module_select && write_strobe && (regsel==_CST) )
 
                        dma_on <= din[7];
 
 
 
                // dma_addr control
 
                if( dma_ack && dma_on )
 
                        dma_addr <= dma_addr + 22'd1; // increment on beginning of DMA transfer
 
                else if( module_select && write_strobe )
 
                begin
 
                        if( regsel==_HAD )
 
                                dma_addr[21:16] <= din[5:0];
 
                        else if( regsel==_MAD )
 
                                dma_addr[15:8]  <= din[7:0];
 
                        else if( regsel==_LAD )
 
                                dma_addr[7:0]   <= din[7:0];
 
                end
 
        end
 
 
 
 
 
 
 
        // syncing in zxdmaread and zxdmawrite, making _begin and _end pulses
 
        always @(negedge clk) // forcing signals syncing in!
 
        begin
 
                zxdmaread_sync  <= zxdmaread;
 
                zxdmawrite_sync <= zxdmawrite;
 
        end
 
 
 
        always @(posedge clk)
 
        begin
 
                zxdmaread_strobe[1:0]  <= { zxdmaread_strobe[0],  zxdmaread_sync  };
 
                zxdmawrite_strobe[1:0] <= { zxdmawrite_strobe[0], zxdmawrite_sync };
 
        end
 
 
 
        always @*
 
        begin
 
                zxread_beg  <= zxdmaread_strobe[0]  && (!zxdmaread_strobe[1]);
 
                zxwrite_beg <= zxdmawrite_strobe[0] && (!zxdmawrite_strobe[1]);
 
 
 
                zxread_end  <= (!zxdmaread_strobe[0])  && zxdmaread_strobe[1];
 
                zxwrite_end <= (!zxdmawrite_strobe[0]) && zxdmawrite_strobe[1];
 
        end
 
 
 
 
 
 
 
 
 
 
 
        // main FSM for zx-dma control
 
 
 
        localparam zdmaIDLE       = 0;
 
        localparam zdmaREAD       = 1; // read cycle has begun
 
        localparam zdmaENDREAD1   = 2; // end read cycle: wait for zxread_end
 
        localparam zdmaENDREAD2   = 3; // end read cycle: data from dma_rd_temp to dma_rd_data
 
        localparam zdmaSTARTWAIT  = 4; // assert wait_ena
 
        localparam zdmaFWDNOWAIT1 = 5; // forward data from dma_rd to dma_rd_data, negate wait_ena, if any, go to zdmaREAD if zxread_beg
 
        localparam zdmaFWDNOWAIT2 = 6; // forward data from dma_rd to dma_rd_data, negate wait_ena, if any, go to zdmaREAD always
 
        localparam zdmaWAITED     = 7; // waited until dma_end
 
        localparam zdmaWRITEWAIT  = 8; // write wait
 
 
 
        always @(posedge clk, negedge rst_n)
 
        if( !rst_n )
 
                zdma_state <= zdmaIDLE;
 
        else if( !dma_on )
 
                zdma_state <= zdmaIDLE;
 
        else
 
                zdma_state <= zdma_next;
 
 
 
 
 
        always @*
 
        begin
 
 
 
                case( zdma_state )
 
 
 
                zdmaIDLE:
 
                        if( zxread_beg )
 
                                zdma_next = zdmaREAD;
 
                        else if( zxwrite_end )
 
                                zdma_next = zdmaWRITEWAIT;
 
                        else
 
                                zdma_next = zdmaIDLE;
 
 
 
                zdmaREAD:
 
                        if( dma_end && zxread_end ) // both signal simultaneously
 
                                zdma_next = zdmaFWDNOWAIT1;
 
                        else if( zxread_end )
 
                                zdma_next = zdmaSTARTWAIT;
 
                        else if( dma_end )
 
                                zdma_next = zdmaENDREAD1;
 
                        else
 
                                zdma_next = zdmaREAD;
 
 
 
                zdmaENDREAD1:
 
                        if( zxread_end )
 
                                zdma_next = zdmaENDREAD2;
 
                        else
 
                                zdma_next = zdmaENDREAD1;
 
 
 
                zdmaENDREAD2:
 
                        if( zxread_beg )
 
                                zdma_next = zdmaREAD;
 
                        else
 
                                zdma_next = zdmaIDLE;
 
 
 
                zdmaSTARTWAIT:
 
                        if( dma_end && zxread_beg )
 
                                zdma_next = zdmaFWDNOWAIT2;
 
                        else if( dma_end )
 
                                zdma_next = zdmaFWDNOWAIT1;
 
                        else if( zxread_beg )
 
                                zdma_next = zdmaWAITED;
 
                        else if( zxwrite_beg ) // to prevent dead locks!
 
                                zdma_next = zdmaIDLE;
 
                        else
 
                                zdma_next = zdmaSTARTWAIT;
 
 
 
                zdmaFWDNOWAIT1:
 
                        if( zxread_beg )
 
                                zdma_next = zdmaREAD;
 
                        else
 
                                zdma_next = zdmaIDLE;
 
 
 
                zdmaFWDNOWAIT2:
 
                        zdma_next = zdmaREAD;
 
 
 
                zdmaWAITED:
 
                        if( dma_end )
 
                                zdma_next = zdmaFWDNOWAIT2;
 
                        else
 
                                zdma_next = zdmaWAITED;
 
 
 
 
 
                zdmaWRITEWAIT:
 
                        if( dma_ack )
 
                                zdma_next = zdmaIDLE;
 
                        else if( zxread_beg )
 
                                zdma_next = zdmaIDLE;
 
                        else
 
                                zdma_next = zdmaWRITEWAIT;
 
 
 
 
 
                endcase
 
        end
 
 
 
        //control read data forwarding
 
        always @(posedge clk)
 
                if( dma_end ) dma_rd_temp <= dma_rd;
 
 
 
        always @(posedge clk)
 
                case( zdma_next )
 
                        zdmaENDREAD2:
 
                                dma_rd_data <= dma_rd_temp;
 
                        zdmaFWDNOWAIT1:
 
                                dma_rd_data <= dma_rd;
 
                        zdmaFWDNOWAIT2:
 
                                dma_rd_data <= dma_rd;
 
                endcase
 
 
 
 
 
        // control wait_ena
 
        always @(posedge clk, negedge rst_n)
 
                if( !rst_n )
 
                        waitena_reg <= 1'b0;
 
                else if( !dma_on )
 
                        waitena_reg <= 1'b0;
 
                else if( (zdma_next == zdmaSTARTWAIT) || (zdma_next == zdmaWRITEWAIT) )
 
                        waitena_reg <= 1'b1;
 
                else if( (zdma_state == zdmaFWDNOWAIT1) || (zdma_state == zdmaFWDNOWAIT2) || (zdma_state == zdmaIDLE) )
 
                        waitena_reg <= 1'b0;
 
 
 
        always @*
 
                waitena_fwd = ( (zdma_state==zdmaREAD) && zxread_end && (!dma_end) ) || ( (zdma_state==zdmaIDLE) && zxwrite_end );
 
 
 
        always @*
 
                wait_ena = waitena_reg | waitena_fwd;
 
 
 
 
 
 
 
 
 
 
 
        // FSM for dma requests
 
 
 
        localparam dmarqIDLE   = 0;
 
        localparam dmarqRDREQ1 = 1;
 
        localparam dmarqRDREQ2 = 2;
 
        localparam dmarqWRREQ  = 3;
 
 
 
        always @(posedge clk, negedge rst_n)
 
        if( !rst_n )
 
                dmarq_state <= dmarqIDLE;
 
        else if( !dma_on )
 
                dmarq_state <= dmarqIDLE;
 
        else
 
                dmarq_state <= dmarq_next;
 
 
 
        always @*
 
        case( dmarq_state )
 
 
 
                dmarqIDLE:
 
                        if( zxread_beg )
 
                                dmarq_next <= dmarqRDREQ1;
 
                        else if( zxwrite_end )
 
                                dmarq_next <= dmarqWRREQ;
 
                        else
 
                                dmarq_next <= dmarqIDLE;
 
 
 
                dmarqRDREQ1:
 
                        if( zxwrite_beg )
 
                                dmarq_next <= dmarqIDLE; // to prevent dead ends!
 
                        else if( dma_ack && (!zxread_beg) )
 
                                dmarq_next <= dmarqIDLE;
 
                        else if( (!dma_ack) && zxread_beg )
 
                                dmarq_next <= dmarqRDREQ2;
 
                        else // nothing or both zxread_beg and dma_ack
 
                                dmarq_next <= dmarqRDREQ1;
 
 
 
                dmarqRDREQ2:
 
                        if( dma_ack )
 
                                dmarq_next <= dmarqRDREQ1;
 
                        else
 
                                dmarq_next <= dmarqRDREQ2;
 
 
 
                dmarqWRREQ:
 
                        if( dma_ack || zxread_beg ) //zxread_beg - to prevent dead end!
 
                                dmarq_next <= dmarqIDLE;
 
                        else
 
                                dmarq_next <= dmarqWRREQ;
 
        endcase
 
 
 
        always @(posedge clk, negedge rst_n)
 
        if( !rst_n )
 
                dma_prireq <= 1'b0;
 
        else
 
        case( dmarq_next )
 
 
 
                dmarqIDLE:
 
                begin
 
                        dma_prireq <= 1'b0;
 
                end
 
 
 
                dmarqRDREQ1:
 
                begin
 
                        dma_prireq <= 1'b1;
 
                        dma_prirnw <= 1'b1;
 
                end
 
 
 
                dmarqRDREQ2:
 
                begin
 
                        // nothing
 
                end
 
 
 
                dmarqWRREQ:
 
                begin
 
                        dma_prireq <= 1'b1;
 
                        dma_prirnw <= 1'b0;
 
                end
 
 
 
        endcase
 
 
 
        always @* dma_req <= (dma_prireq | zxread_beg | zxwrite_end ) & dma_on;
 
 
 
        always @*
 
                if( zxread_beg )
 
                        dma_rnw <= 1'b1;
 
                else if( zxwrite_end )
 
                        dma_rnw <= 1'b0;
 
                else
 
                        dma_rnw <= dma_prirnw;
 
 
 
        always @* dma_wd <= dma_wr_data;
 
 
 
 
 
endmodule