// part of NeoGS project
//
// (c) NedoPC 2007-2009
//
// SD-card dma controller
//
// includes dma address regs, dma control reg
/*
Read from sd-card modes:
1. Full burst: first all 512 bytes are read into 512b buffer, then dma-bursted into main memory.
Not as fast in latency, but steals minimum of CPU cycles, though stops cpu for 1024+ clocks (if no other DMAs are active)
2. As soon as possible: initiates DMA as soon as new byte is arrived into 512b buffer.
Makes bursts of 2-3 bytes, when applicable (more bytes arriven since beginning of dma_req up to acknowledge in dma_ack)
Write to sd-card modes:
1. Full burst: all 512 bytes are read in one burst, transmission starts as soon as first byte arrives to the buffer.
Uses minimum of CPU cycles (1024+) but in one chunk.
2. DMA initiated as soon as spi is again ready to initiate new transfer (kind a throttling). Probably DMA pulls 2 or 4 bytes at once.
Structure:
- FIFO based on mem512b and two pointers
- SD controller - FSM which either reads or writes SD-card SPI iface
- DMA controller - FSM which either reads or writes DMA iface
- overall control - Controls operation of everything above, controls muxing of data to the
SD write port and FIFO write port, tracks operation end, maintains DMA_ADDRESS registers.
*/
module dma_sd(
input wire clk,
input wire rst_n,
// control to spi module of SD-card
//
output reg sd_start,
input wire sd_rdy,
input wire [7:0] sd_receiveddata,
output wire [7:0] sd_datatosend,
//
output reg sd_override, // when 1, override sd_start and sd_datatosend to the sd spi module
// signals for ports.v
//
input wire [7:0] din, // input and output from ports.v
output reg [7:0] dout,
//
input wire module_select, // =1 - module selected for read-write operations from ports.v
input wire write_strobe, // one-cycle positive write strobe - writes to the selected registers from din
//
input wire [1:0] regsel, // 2'b00 - high address, 2'b01 - middle address, 2'b10 - low address, 2'b11 - control register
// signals for DMA controller/DMA sequencer
//
output reg [20:0] dma_addr,
output wire [7:0] dma_wd, // data written to DMA
input wire [7:0] dma_rd, // data read from DMA
output reg dma_rnw,
//
output wire dma_req,
input wire dma_ack,
input wire dma_end
);
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
reg dma_snr; // Send-NotReceive. Send to SDcard (==1) or Receive (==0) from it.
reg dma_burst; // whether burst transfers are on
// control dout bus
always @*
case( regsel[1:0] )
_HAD: dout = { 3'b000, dma_addr[20:16] };
_MAD: dout = dma_addr[15:8];
_LAD: dout = dma_addr[7:0];
_CST: dout = { dma_on, 5'bXXXXX, dma_burst, dma_snr};
endcase
// ports.v write access & dma_addr control
always @(posedge clk, negedge rst_n)
if( !rst_n ) // async reset
begin
dma_on <= 1'b0;
dma_snr <= 1'b0; // receive is less dangerous since it won't destroy SDcard info
dma_burst <= 1'b0;
end
else // posedge clk
begin
// dma_on control
if( module_select && write_strobe && (regsel==_CST) )
begin
dma_on <= din[7];
dma_burst <= din[1];
dma_snr <= din[0];
end
else if( dma_finish )
begin
dma_on <= 1'b0;
end
// dma_addr control
if( dma_ack && dma_on )
dma_addr <= dma_addr + 21'd1; // increment on beginning of DMA transfer
else if( module_select && write_strobe )
begin
if( regsel==_HAD )
dma_addr[20:16] <= din[4: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
// fifo,dma,sd control FSMs/etc.
reg init;
wire wr_stb,rd_stb;
wire wdone,rdone,empty;
wire [7:0] fifo_wd; // data for FIFO to be written
wire [7:0] fido_rd; // data read from FIFO
// MUX data to FIFO
assign fifo_wd = dma_snr ? dma_rd : sd_receiveddata;
// MUX data to SDcard
assign sd_datatosend = dma_snr ? fifo_rd : 8'hFF;
// connect dma in to fifo out without muxing
assign dma_wd = fifo_rd;
fifo512_oneshot fifo512_oneshot ( .clk(clk),
.rst_n(rst_n),
.init(init),
.wr_stb(wr_stb),
.rd_stb(rd_stb),
.wdone(wdone),
.rdone(rdone),
.empty(empty),
.wd(fifo_wd),
.rd(fifo_rd)
);
// fifo control
reg sd_wr_stb,sd_rd_stb; // set in SD FSM
reg dma_wr_stb,dma_rd_stb; // set in DMA FSM
//
assign wr_stb = sd_wr_stb | dma_wr_stb;
assign rd_stb = sd_rd_stb | dma_rd_stb;
// dma control
wire dma_put_req;
wire dma_get_req;
assign dma_req = dma_put_req | dma_get_req;
reg sd_go; // start strobe for SD FSM
reg sd_idle; // whether SD FSM is idle
reg dma_go;
reg dma_idle;
// SD-card controlling FSM
reg [2:0] sd_state, sd_next_state;
localparam SD_IDLE = 3'b000;
localparam SD_READ1 = 3'b100;
localparam SD_READ2 = 3'b101;
localparam SD_WRITE1 = 3'b110;
localparam SD_WRITE2 = 3'b111;
always @(posedge clk, negedge rst_n)
begin
if( !rst_n )
begin
sd_state = SD_IDLE;
end
else // posedge clk
begin
sd_state <= sd_next_state;
end
end
always @*
begin
case( sd_state )
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_IDLE:begin
if( sd_go )
begin
if( sd_snr ) // send to SD
begin
sd_next_state = SD_WRITE1;
end
else // !sd_snr: read from SD
begin
sd_next_state = SD_READ1;
end
end
else
begin
sd_next_state = SD_IDLE;
end
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_READ1:begin
if( wdone )
begin
sd_next_state = SD_IDLE;
end
else // !wdone - can still send bytes to the fifo
begin
if( !sd_rdy ) // not ready with previous byte - wait here
begin
sd_next_state = SD_READ1;
end
else // sd_rdy - can proceed further
begin
sd_next_state = SD_READ2;
end
end
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_READ2:begin
sd_next_state = SD_READ1;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_WRITE1:begin
if( rdone )
begin
sd_next_state = SD_IDLE;
end
else
begin
if( sd_rdy && !empty ) // whether sd ready and we can take next byte from fifo
begin
sd_next_state = SD_WRITE2;
end
else // can't start next byte: wait
begin
sd_next_state = SD_WRITE1;
end
end
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_WRITE2:begin
sd_next_state = SD_WRITE1;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
default:begin
sd_next_state = SD_IDLE;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endcase
end
always @(posedge clk, negedge rst_n)
begin
if( !rst_n )
begin
sd_wr_stb = 1'b0;
sd_rd_stb = 1'b0;
sd_start = 1'b0;
sd_override = 1'b0;
sd_idle = 1'b0;
end
else // posedge clk
begin
case( sd_next_state )
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_IDLE:begin
sd_wr_stb <= 1'b0;
sd_rd_stb <= 1'b0;
sd_start <= 1'b0;
sd_override <= 1'b0;
sd_idle <= 1'b1;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_READ1:begin
sd_override <= 1'b1; // takeover SD card SPI iface
sd_start <= 1'b0;
sd_wr_stb <= 1'b0;
sd_idle <= 1'b0;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_READ2:begin
sd_start <= 1'b1; // trigger new SPI exchange
sd_wr_stb <= 1'b1; // trigger FIFO write
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_WRITE1:begin
sd_override <= 1'b1;
sd_start <= 1'b0;
sd_rd_str <= 1'b0;
sd_idle <= 1'b0;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
SD_WRITE2:begin
sd_start <= 1'b1;
sd_rd_stb <= 1'b1;
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endcase
end
end
// DMA-controlling FSM
reg [3:0] dma_state, dma_next_state
localparam DMA_IDLE
localparam DMA_PUT_WAIT
localparam DMA_PUT_RUN
always @(posedge clk, negedge rst_n)
begin
if( !rst_n )
begin
dma_state = DMA_IDLE;
end
else // posedge clk
begin
dma_state <= dma_next_state;
end
end
always @*
begin
case( dma_state )
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DMA_IDLE:begin
if( dma_go )
begin
if( dma_snr )
begin
........dma_state = DMA_GET_WAIT;
end
else // !dma_snr
begin
dma_next_state = DMA_PUT_WAIT;
end
end
else
begin
dma_next_state = DMA_IDLE;
end
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DMA_PUT_WAIT:begin
if( rdone )
begin
dma_next_state = DMA_IDLE;
end
else // !rdone
begin
if( !empty ) // fifo is not empty
begin
dma_next_state = DMA_PUT_RUN;
end
else // fifo empty
begin
dma_next_state = DMA_PUT_WAIT;
end
end
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
DMA_PUT_RUN:begin
if( rdone )
begin
dma_next_state = DMA_IDLE;
end
else
begin
if( empty )
begin
dma_next_state = DMA_PUT_WAIT;
end
else // !empty
begin
dma_next_state = DMA_PUT_RUN;
end
end
end
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endcase
end
always @(posedge clk, negedge rst_n)
begin
if( !rst_n )
begin
end
else // posedge clk
begin
case( dma_next_state )
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
endcase
end
end
endmodule
// this is "one-shot" fifo: after each 512 bytes both written and read back, it must be initialized by means of 'init'
//
module fifo512_oneshot(
input wire clk,
input wire rst_n,
input wire init, // initializes fifo: wptr=rptr=0
input wire wr_stb, // write strobe: writes data from wd to the current wptr, increments wptr
input wire rd_stb, // read strobe: increments rptr
output wire wdone, // write done - all 512 bytes are written (end of write operation)
output wire rdone, // read done - all 512 bytes are read (end of read operation)
output wire empty, // fifo empty: when wptr==rptr (rd_stb must not be issued when empty is active, otherwise everytrhing desyncs)
input wire [7:0] wd, // data to be written
output wire [7:0] rd // data just read from rptr address
);
reg [9:0] wptr;
reg [9:0] rptr;
always @(posedge clk, negedge rst_n)
begin
if( !rst_n )
begin
wptr = 10'd0;
rptr = 10'd0;
end
else
begin // posedge clk
if( init )
begin
wptr <= 10'd0;
end
else if( wr_stb )
begin
wptr <= wptr + 10'd1;
end
if( init )
begin
rptr <= 10'd0;
end
else if( rd_stb )
begin
rptr <= rptr + 10'd1;
end
end
end
assign wdone = wptr[9];
assign rdone = rptr[9];
assign empty = ( wptr==rptr );
mem512b fifo512_oneshot_mem512b( .clk(clk),
.rdaddr(rptr[8:0]),
.dataout(rd),
.wraddr(wptr[8:0]),
.datain(wd),
.we(wr_stb)
);
endmodule