// 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