/* Copyright 2007-2008, Unpublished Work of Technologic Systems
 * All Rights Reserved.
 *
 * THIS WORK IS AN UNPUBLISHED WORK AND CONTAINS CONFIDENTIAL,
 * PROPRIETARY AND TRADE SECRET INFORMATION OF TECHNOLOGIC SYSTEMS.
 * ACCESS TO THIS WORK IS RESTRICTED TO (I) TECHNOLOGIC SYSTEMS 
 * EMPLOYEES WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE
 * OF THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN TECHNOLOGIC
 * SYSTEMS WHO HAVE ENTERED INTO APPROPRIATE LICENSE AGREEMENTS.  NO
 * PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED, COPIED, 
 * DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED, CONDENSED, 
 * EXPANDED, COLLECTED, COMPILED, LINKED, RECAST, TRANSFORMED, ADAPTED
 * IN ANY FORM OR BY ANY MEANS, MANUAL, MECHANICAL, CHEMICAL, 
 * ELECTRICAL, ELECTRONIC, OPTICAL, BIOLOGICAL, OR OTHERWISE WITHOUT
 * THE PRIOR WRITTEN PERMISSION AND CONSENT OF TECHNOLOGIC SYSTEMS.
 * ANY USE OR EXPLOITATION OF THIS WORK WITHOUT THE PRIOR WRITTEN
 * CONSENT OF TECHNOLOGIC SYSTEMS COULD SUBJECT THE PERPETRATOR TO
 * CRIMINAL AND CIVIL LIABILITY.
 */

/* Core to automatically transfer data between 2 masters.  To use,
 * first setup control register.  DMA operation will proceed on master
 * interfaces with asserted drq_i signal only when the DMA length field
 * in the control register is non-zero.  When DMA operation is done,
 * the dack_o will be asserted on the completed master interface(s).
 *
 * Register map:
 * base + 0x0: DMA control register (R/W)
 *   bits 11:0 - DMA length (number of transfers, not necessarily bytes)
 *   bit 12 - DMA direction (0 - wbm1 reads, 1 - wbm2 reads)
 *   bits 15:13 - wbm2 address increment per transfer
 *   bits 18:16 - wbm1 address increment per transfer
 *   bits 20:19 - DMA transfer size 
 *     0 - 1 byte
 *     1 - 2 bytes (halfwords)
 *     2 - 4 bytes (words)
 *     3 - reserved
 *   bits 29:21 - reserved
 *   bit 30 - DRQ1 input (wbm1 is ready for DMA)
 *   bit 31 - DRQ2 input (wbm2 is ready for DMA)
 * base + 0x4: wbm1 address (R/W) - must be transfer size aligned
 * base + 0x8: wbm2 address (R/W) - must be transfer size aligned
 *
 * When DMA operation starts:
 *   - dack_o signal negedge
 *
 * When DMA operation is in progress:
 *   - dack_o signals will be low
 *   - DMA length (bits 11:0 in DMA control reg) will be decrementing 
 *     to zero.
 *   - wbm1 address and wbm2 address regs will be incrementing as 
 *     configured in bits 18:13 of the DMA control reg
 * 
 * When DMA operation completes:
 *   - dack_o signal posedge
 *   - DMA length (bits 11:0 in DMA control reg) will be zero.
 *   - wbm1 address and wbm2 address regs will be pointing at the
 *     ending address plus 1 address increment (bits 18:13)
 *
 * When DMA is idle:
 *   - dack_o signal is high
 */


module wb_dma(
  wb_clk_i,
  wb_rst_i,

  wbs_cyc_i,
  wbs_stb_i,
  wbs_we_i,
  wbs_sel_i,
  wbs_dat_i,
  wbs_dat_o,
  wbs_ack_o,
  wbs_adr_i,

  wbm1_cyc_o,
  wbm1_stb_o,
  wbm1_adr_o,
  wbm1_dat_o,
  wbm1_dat_i,
  wbm1_sel_o,
  wbm1_ack_i,
  wbm1_we_o,
  wbm1_drq_i,
  wbm1_cti_o,
  wbm1_bte_o,
  wbm1_dack_o,

  wbm2_cyc_o,
  wbm2_stb_o,
  wbm2_adr_o,
  wbm2_dat_o,
  wbm2_dat_i,
  wbm2_sel_o,
  wbm2_ack_i,
  wbm2_we_o,
  wbm2_cti_o,
  wbm2_bte_o,
  wbm2_drq_i,
  wbm2_dack_o
);

input wb_clk_i;
input wb_rst_i;

input wbs_cyc_i, wbs_stb_i, wbs_we_i;
input [3:0] wbs_sel_i;
input [31:0] wbs_adr_i, wbs_dat_i;
output wbs_ack_o;
output [31:0] wbs_dat_o;

output wbm1_cyc_o, wbm1_stb_o, wbm1_we_o;
output [3:0] wbm1_sel_o;
output [31:0] wbm1_adr_o, wbm1_dat_o;
input [31:0] wbm1_dat_i;
input wbm1_ack_i;
output [2:0] wbm1_cti_o;
output [1:0] wbm1_bte_o;

output wbm2_cyc_o, wbm2_stb_o, wbm2_we_o;
output [3:0] wbm2_sel_o;
output [31:0] wbm2_adr_o, wbm2_dat_o;
input [31:0] wbm2_dat_i;
input wbm2_ack_i;
output [2:0] wbm2_cti_o;
output [1:0] wbm2_bte_o;

input wbm1_drq_i, wbm2_drq_i;
output wbm1_dack_o, wbm2_dack_o;

parameter fifo_size = 8'd4;
parameter fifo_blockram = 1'b0;
parameter wbm1_noaddress = 1'b1;
parameter wbm1_noincrement = 1'b1;
parameter wbm2_noaddress = 1'b0;
parameter wbm2_noincrement = 1'b0;
parameter default_dma_ctrl_reg = 32'h00081000;
parameter hardcoded_dma_ctrl_reg_mask = 32'hff000000;

reg [31:0] fifomem [0:255];
reg [7:0] fifo_head, fifo_tail;
reg [8:0] fifo_cnt;
reg [31:0] fifo_dout, fifo_dout_q, fifo_din;
reg [15:0] fifo_din16;
reg [7:0] fifo_din8;
reg fifo_rd, fifo_wr, fifo_wait;
reg fifo_full /* synthesis syn_maxfan=1 */;
reg fifo_empty /* synthesis syn_maxfan=1 */;
//wire fifo_almostempty = (((fifo_head + 1'b1) % fifo_size) == fifo_tail);
//wire fifo_almostfull = (((fifo_tail + 1'b1) % fifo_size) == fifo_head);
reg fifo_almostempty, fifo_almostfull;
wire fifo_almostalmostempty = (((fifo_head + 2'd2) % fifo_size) == fifo_tail);
wire fifo_almostalmostfull = (((fifo_tail + 2'd2) % fifo_size) == fifo_head);
always @(posedge wb_clk_i or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    fifo_head <= 8'd0;
    fifo_tail <= 8'd0;
    fifo_dout_q <= 32'd0;
    fifo_wait <= 1'b0;
    fifo_empty <= 1'b1;
    fifo_full <= 1'b0;
    fifo_almostempty <= 1'b0;
    fifo_almostfull <= 1'b0;
  end else begin
    if (fifo_rd && !fifo_wr) begin
      if (fifo_almostalmostempty) fifo_almostempty <= 1'b1;
      if (fifo_almostempty) begin
        fifo_empty <= 1'b1;
        fifo_almostempty <= 1'b0;
      end
      if (fifo_full) fifo_almostfull <= 1'b1;
      if (fifo_almostfull) fifo_almostfull <= 1'b0;
      fifo_full <= 1'b0;
    end else if (fifo_wr && !fifo_rd) begin
      if (fifo_almostfull) begin
        fifo_full <= 1'b1;
        fifo_almostfull <= 1'b0;
      end
      if (fifo_almostalmostfull) fifo_almostfull <= 1'b1;
      if (fifo_almostempty) fifo_almostempty <= 1'b0;
      if (fifo_empty) fifo_almostempty <= 1'b1;
      fifo_empty <= 1'b0;
    end

    fifo_wait <= 1'b0;
    if (fifo_rd) begin
      fifo_head <= (fifo_head + 1'b1) % fifo_size;
      if (fifo_blockram) fifo_wait <= 1'b1;
    end
    if (fifo_wr) begin
      fifo_tail <= (fifo_tail + 1'b1) % fifo_size;
    end

    fifo_dout_q <= fifomem[fifo_head];
  end
end

always @(posedge wb_clk_i) begin
  if (fifo_wr) fifomem[fifo_tail] <= fifo_din;
end

always @(fifo_dout_q or fifo_head or fifo_blockram) begin
  if (fifo_blockram) fifo_dout = fifo_dout_q;
  else fifo_dout = fifomem[fifo_head];
end

reg [31:0] wbm1_adr, wbm2_adr, dma_ctrl, wbs_dat;
reg [31:0] wbm1_adr_next, wbm2_adr_next;
reg wbm1_cyc, wbm1_stb, wbm2_cyc, wbm2_stb;
reg [3:0] wbm1_sel, wbm2_sel;
reg wbm1_dack, wbm2_dack;
reg wbs_ack;
reg [2:0] wbm1_cti, wbm2_cti;
assign wbm1_dack_o = wbm1_dack;
assign wbm2_dack_o = wbm2_dack;
assign wbm1_we_o = dma_ctrl[12];
assign wbm2_we_o = !dma_ctrl[12];
assign wbm1_dat_o = fifo_dout[31:0];
assign wbm2_dat_o = fifo_dout[31:0];
assign wbm1_cyc_o = wbm1_cyc;
assign wbm1_stb_o = wbm1_stb;
assign wbm2_cyc_o = wbm2_cyc;
assign wbm2_stb_o = wbm2_stb;
assign wbm1_sel_o = wbm1_sel;
assign wbm2_sel_o = wbm2_sel;
assign wbs_dat_o = wbs_dat;
assign wbs_ack_o = wbs_ack;
assign wbm1_adr_o = wbm1_noaddress ? 32'hxxxxxxxx : {wbm1_adr[31:2], 2'b00};
assign wbm2_adr_o = wbm2_noaddress ? 32'hxxxxxxxx : {wbm2_adr[31:2], 2'b00};
assign wbm1_bte_o = 2'b00;
assign wbm2_bte_o = 2'b00;
assign wbm1_cti_o = wbm1_cti;
assign wbm2_cti_o = wbm2_cti;
reg dma_done /* synthesis syn_maxfan=1 */;
reg dma_last;
reg [2:0] cti1, cti2;
wire [31:0] wbs_selmsk = { {8{wbs_sel_i[3]}}, {8{wbs_sel_i[2]}}, 
  {8{wbs_sel_i[1]}}, {8{wbs_sel_i[0]}}};
always @(posedge wb_clk_i or posedge wb_rst_i) begin
  if (wb_rst_i) begin
    wbm1_adr <= 32'h0;
    wbm2_adr <= 32'h0;
    dma_ctrl <= default_dma_ctrl_reg;
    wbs_ack <= 1'b0;
    wbs_dat <= 32'h0;
    dma_done <= 1'b1;
    dma_last <= 1'b0;
    cti1 <= 3'b111;
    cti2 <= 3'b111;
    wbm1_cti <= 3'b111;
    wbm2_cti <= 3'b111;
  end else begin
    wbs_ack <= wbs_cyc_i && wbs_stb_i && !wbs_ack;

    case (wbs_adr_i[3:2]) 
    2'd0, 2'd3: wbs_dat <= {wbm2_drq_i, wbm1_drq_i, dma_ctrl[29:0]};
    2'd1: wbs_dat <= (wbm1_noaddress ? 32'hxxxxxxxx : wbm1_adr);
    2'd2: wbs_dat <= (wbm2_noaddress ? 32'hxxxxxxxx : wbm2_adr);
    endcase

    if (wbs_cyc_i && wbs_stb_i && wbs_we_i && !wbs_ack) begin
      case (wbs_adr_i[3:2])
      2'd0, 2'd3: dma_ctrl <= 
        (dma_ctrl & (~wbs_selmsk | hardcoded_dma_ctrl_reg_mask)) | 
        (wbs_dat_i[31:0] & (wbs_selmsk & ~hardcoded_dma_ctrl_reg_mask));
      2'd1: wbm1_adr <= (wbm1_adr & ~wbs_selmsk) | 
        (wbs_dat_i[31:0] & wbs_selmsk);
      2'd2: wbm2_adr <= (wbm2_adr & ~wbs_selmsk) |
        (wbs_dat_i[31:0] & wbs_selmsk);
      endcase
    end

    if (dma_ctrl[11:0] > 12'd0) dma_done <= 1'b0;
    if (dma_ctrl[11:0] > 12'd1) dma_last <= 1'b0;

    if (dma_ctrl[15:13] == 3'd4 && dma_ctrl[20:19] == 2'd2) cti2 <= 3'b010;
    else if (dma_ctrl[15:13] == 3'd0) cti2 <= 3'b001;
    else cti2 <= 3'b111;

    if (dma_ctrl[18:16] == 3'd4 && dma_ctrl[20:19] == 2'd2) cti1 <= 3'b010;
    else if (dma_ctrl[18:16] == 3'd0) cti1 <= 3'b001;
    else cti1 <= 3'b111;

    if (dma_ctrl[12]) begin
      if (!dma_done && fifo_wr) begin
        wbm2_adr <= wbm2_adr_next;
        dma_ctrl[11:0] <= dma_ctrl[11:0] - 1'b1;
        dma_last <= (dma_ctrl[11:0] <= 12'd2);
        dma_done <= dma_last;
      end 
      if (fifo_rd) begin
        wbm1_adr <= wbm1_adr_next;
      end
      if (fifo_rd && !fifo_wr) begin
        if (fifo_almostalmostempty) wbm1_cti <= 3'b111;
        if (fifo_almostfull && !dma_last) wbm2_cti <= cti2;
      end
      if (fifo_wr && !fifo_rd) begin
        if (fifo_almostempty) wbm1_cti <= cti1;
        if (fifo_almostalmostfull) wbm2_cti <= 3'b111;
        if (dma_ctrl[11:0] <= 12'd2) wbm2_cti <= 3'b111;
      end
    end else begin
      if (!dma_done && fifo_wr) begin
        wbm1_adr <= wbm1_adr_next;
        dma_ctrl[11:0] <= dma_ctrl[11:0] - 1'b1;
        dma_last <= (dma_ctrl[11:0] <= 12'd2);
        dma_done <= dma_last;
      end 
      if (fifo_rd) begin
        wbm2_adr <= wbm2_adr_next;
      end
      if (fifo_rd && !fifo_wr) begin
        if (fifo_almostalmostempty) wbm2_cti <= 3'b111;
        if (fifo_almostfull && !dma_last) wbm1_cti <= cti1;
      end
      if (fifo_wr && !fifo_rd) begin
        if (fifo_almostempty) wbm2_cti <= cti2;
        if (fifo_almostalmostfull) wbm1_cti <= 3'b111;
        if (dma_ctrl[11:0] <= 12'd2) wbm1_cti <= 3'b111;
      end
    end
  if (wbm1_noincrement) dma_ctrl[18:16] <= 3'd0;
  if (wbm2_noincrement) dma_ctrl[15:13] <= 3'd0;
  end
end


always @(dma_ctrl or fifo_empty or fifo_full or wbm1_ack_i or
  wbm1_dat_i or wbm2_ack_i or wbm2_dat_i or dma_done or
  wbm1_adr or wbm2_adr or fifo_wait or wbs_adr_i or
  wbm1_drq_i or wbm2_drq_i or fifo_almostempty or
  fifo_almostfull or dma_last /* or cti1 or cti2 */) begin
  fifo_rd = 1'b0;
  fifo_wr = 1'b0;
  wbm1_cyc = 1'b0;
  wbm1_stb = 1'b0;
  wbm2_cyc = 1'b0;
  wbm2_stb = 1'b0;
//  wbm1_cti = 3'b111;
//  wbm2_cti = 3'b111;

  if (dma_ctrl[12]) begin
    if (!fifo_empty && wbm1_drq_i) begin
      wbm1_cyc = 1'b1;
      wbm1_stb = 1'b1;
      fifo_rd = wbm1_ack_i;
//      if (fifo_almostempty) wbm1_cti = 3'b111;
//      else wbm1_cti = cti1; 
    end
    fifo_din = wbm2_dat_i;
    if (wbm2_adr[1]) fifo_din16 = wbm2_dat_i[31:16];
    else fifo_din16 = wbm2_dat_i[15:0];
    if (dma_ctrl[20:19] == 2'b01) fifo_din = {2{fifo_din16}};
    if (wbm2_adr[1:0] == 2'b00) fifo_din8 = wbm2_dat_i[7:0];
    else if (wbm2_adr[1:0] == 2'b01) fifo_din8 = wbm2_dat_i[15:8];
    else if (wbm2_adr[1:0] == 2'b10) fifo_din8 = wbm2_dat_i[23:16];
    else if (wbm2_adr[1:0] == 2'b11) fifo_din8 = wbm2_dat_i[31:24];
    if (dma_ctrl[20:19] == 2'b00) fifo_din = {4{fifo_din8}};

    if (!fifo_full && !dma_done && wbm2_drq_i) begin
      wbm2_cyc = 1'b1;
      wbm2_stb = !fifo_wait;
      fifo_wr = wbm2_ack_i && wbm2_stb;
//      if (fifo_almostfull || dma_last) wbm2_cti = 3'b111;
//      else wbm2_cti = cti2;
    end
    wbm2_dack = dma_done;
    wbm1_dack = fifo_empty && dma_done;
  end else begin
    if (!fifo_empty && wbm2_drq_i) begin
      wbm2_cyc = 1'b1;
      wbm2_stb = 1'b1;
      fifo_rd = wbm2_ack_i;
//      if (fifo_almostempty) wbm2_cti = 3'b111;
//      else wbm2_cti = cti2;
    end
    fifo_din = wbm1_dat_i;
    if (wbm1_adr[1]) fifo_din16 = wbm1_dat_i[31:16];
    else fifo_din16 = wbm1_dat_i[15:0];
    if (dma_ctrl[20:19] == 2'b01) fifo_din = {2{fifo_din16}};
    if (wbm1_adr[1:0] == 2'b00) fifo_din8 = wbm1_dat_i[7:0];
    else if (wbm1_adr[1:0] == 2'b01) fifo_din8 = wbm1_dat_i[15:8];
    else if (wbm1_adr[1:0] == 2'b10) fifo_din8 = wbm1_dat_i[23:16];
    else if (wbm1_adr[1:0] == 2'b11) fifo_din8 = wbm1_dat_i[31:24];
    if (dma_ctrl[20:19] == 2'b00) fifo_din = {4{fifo_din8}};

    if (!fifo_full && !dma_done && wbm1_drq_i) begin
      wbm1_cyc = 1'b1;
      wbm1_stb = !fifo_wait;
      fifo_wr = wbm1_ack_i && wbm1_stb;
//      if (fifo_almostfull || dma_last) wbm1_cti = 3'b111;
//      else wbm1_cti = cti1;
    end
    wbm1_dack = dma_done;
    wbm2_dack = fifo_empty && dma_done;
  end

  if (wbm1_noincrement) wbm1_adr_next = wbm1_adr; 
  else wbm1_adr_next = wbm1_adr + dma_ctrl[18:16];

  if (wbm2_noincrement) wbm2_adr_next = wbm2_adr;
  else wbm2_adr_next = wbm2_adr + dma_ctrl[15:13];

  case (dma_ctrl[20:19])
  2'd0: begin
    wbm1_sel = 4'b0001 << wbm1_adr[1:0];
    wbm2_sel = 4'b0001 << wbm2_adr[1:0];
  end
  2'd1: begin
    wbm1_sel = wbm1_adr[1] ? 4'b1100 : 4'b0011;
    wbm2_sel = wbm2_adr[1] ? 4'b1100 : 4'b0011;
  end
  2'd2, 2'd3: begin
    wbm1_sel = 4'b1111;
    wbm2_sel = 4'b1111;
  end
  endcase
end

endmodule 
