module dmac_regmap_tb;
  parameter VCD_FILE = {`__FILE__,"cd"};

  `define TIMEOUT 1000000
  `include "tb_base.v"

  localparam DMA_LENGTH_WIDTH = 24;
  localparam DMA_LENGTH_ALIGN = 3;
  localparam BYTES_PER_BEAT = 1;
  localparam DMA_AXI_ADDR_WIDTH = 32;

  localparam LENGTH_ALIGN = 2;
  localparam LENGTH_MASK = {DMA_LENGTH_WIDTH{1'b1}};
  localparam LENGTH_ALIGN_MASK = {LENGTH_ALIGN{1'b1}};
  localparam STRIDE_MASK = {{DMA_LENGTH_WIDTH-BYTES_PER_BEAT{1'b1}},{BYTES_PER_BEAT{1'b0}}};
  localparam ADDR_MASK = {{DMA_AXI_ADDR_WIDTH-BYTES_PER_BEAT{1'b1}},{BYTES_PER_BEAT{1'b0}}};

  localparam VAL_DBG_SRC_ADDR = 32'h76543210;
  localparam VAL_DBG_DEST_ADDR = 32'hfedcba98;
  localparam VAL_DBG_STATUS = 8'ha5;
  localparam VAL_DBG_IDS0 = 32'h01234567;
  localparam VAL_DBG_IDS1 = 32'h89abcdef;

  localparam AW = 12;
  localparam NUM_REGS = 'h200;

  wire s_axi_aclk = clk;
  wire s_axi_aresetn = ~reset;

  reg s_axi_awvalid = 1'b0;
  reg s_axi_wvalid = 1'b0;
  reg [AW-1:0] s_axi_awaddr = 'h00;
  reg [31:0] s_axi_wdata = 'h00;
  wire [1:0] s_axi_bresp;
  wire s_axi_awready;
  wire s_axi_wready;
  wire s_axi_bready = 1'b1;
  wire [3:0] s_axi_wstrb = 4'b1111;
  wire [2:0] s_axi_awprot = 3'b000;
  wire [2:0] s_axi_arprot = 3'b000;
  wire [1:0] s_axi_rresp;
  wire [31:0] s_axi_rdata;

  task write_reg;
  input [31:0] addr;
  input [31:0] value;
  begin
    @(posedge s_axi_aclk)
    s_axi_awvalid <= 1'b1;
    s_axi_wvalid <= 1'b1;
    s_axi_awaddr <= addr;
    s_axi_wdata <= value;
    @(posedge s_axi_aclk)
    while (s_axi_awvalid || s_axi_wvalid) begin
      @(posedge s_axi_aclk)
      if (s_axi_awready)
        s_axi_awvalid <= 1'b0;
      if (s_axi_wready)
        s_axi_wvalid <= 1'b0;
    end
  end
  endtask

  reg [31:0] expected_reg_mem[0:NUM_REGS-1];

  reg [AW-1:0] s_axi_araddr = 'h0;
  reg s_axi_arvalid = 'h0;
  reg s_axi_rready = 'h0;
  wire s_axi_arready;
  wire s_axi_rvalid;

  task read_reg;
  input [31:0] addr;
  output [31:0] value;
  begin
    s_axi_arvalid <= 1'b1;
    s_axi_araddr <= addr;
    s_axi_rready <= 1'b1;
    @(posedge s_axi_aclk) #0;
    while (s_axi_arvalid) begin
      if (s_axi_arready == 1'b1) begin
        s_axi_arvalid <= 1'b0;
      end
      @(posedge s_axi_aclk) #0;
    end

    while (s_axi_rready) begin
      if (s_axi_rvalid == 1'b1) begin
        value <= s_axi_rdata;
        s_axi_rready <= 1'b0;
      end
      @(posedge s_axi_aclk) #0;
    end
  end
  endtask

  task read_reg_check;
  input [31:0] addr;
  output match;
  reg [31:0] value;
  reg [31:0] expected;
  input [255:0] message;
  begin
    read_reg(addr, value);
    expected = expected_reg_mem[addr[11:2]];
    match <= value === expected;
    if (value !== expected) begin
      $display("%0s: Register mismatch for %x. Expected %x, got %x",
        message, addr, expected, value);
    end
  end
  endtask

  reg read_match = 1'b1;

  always @(posedge clk) begin
    if (read_match == 1'b0) begin
      failed <= 1'b1;
    end
  end


  task set_reset_reg_value;
  input [31:0] addr;
  input [31:0] value;
  begin
    expected_reg_mem[addr[AW-1:2]] <= value;
  end
  endtask

  task initialize_expected_reg_mem;
  integer i;
  begin
    for (i = 0; i < NUM_REGS; i = i + 1)
      expected_reg_mem[i] <= 'h00;
    /* Non zero power-on-reset values */
    set_reset_reg_value('h00, 32'h00040161); /* PCORE version register */
    set_reset_reg_value('h0c, 32'h444d4143); /* PCORE magic register */
    set_reset_reg_value('h80, 'h3); /* IRQ mask */

    set_reset_reg_value('h40c, 'h3); /* Flags */
    set_reset_reg_value('h418, LENGTH_ALIGN_MASK); /* Length alignment */

    set_reset_reg_value('h434, VAL_DBG_DEST_ADDR);
    set_reset_reg_value('h438, VAL_DBG_SRC_ADDR);
    set_reset_reg_value('h43c, VAL_DBG_STATUS);
    set_reset_reg_value('h440, VAL_DBG_IDS0);
    set_reset_reg_value('h444, VAL_DBG_IDS1);
  end
  endtask

  task check_all_registers;
  input [255:0] message;
  integer i;
  begin
    for (i = 0; i < NUM_REGS*4; i = i + 4) begin
      read_reg_check(i, read_match, message);
    end
  end
  endtask

  task write_reg_and_update;
  input [31:0] addr;
  input [31:0] value;
  integer i;
  begin
    write_reg(addr, value);
    expected_reg_mem[addr[AW-1:2]] <= value;
  end
  endtask

  task invert_register;
  input [31:0] addr;
  reg [31:0] value;
  begin
    read_reg(addr, value);
    write_reg(addr, ~value);
  end
  endtask

  task invert_all_registers;
  integer i;
  begin
    for (i = 0; i < NUM_REGS*4; i = i + 4) begin
      invert_register(i);
    end
  end
  endtask

  reg request_ready = 1'b0;
  wire [31:BYTES_PER_BEAT] request_dest_address;
  wire [31:BYTES_PER_BEAT] request_src_address;
  wire [DMA_LENGTH_WIDTH-1:0] request_x_length;
  wire [DMA_LENGTH_WIDTH-1:0] request_y_length;
  wire [DMA_LENGTH_WIDTH-1:0] request_dest_stride;
  wire [DMA_LENGTH_WIDTH-1:0] request_src_stride;
  wire request_last;

  reg response_eot = 1'b0;

  integer i;
  initial begin
    initialize_expected_reg_mem();
    @(posedge s_axi_aresetn)
    check_all_registers("Initial");

    /* Check scratch */
    write_reg_and_update('h08, 32'h12345678);
    check_all_registers("Scratch");

    /* Check IRQ mask */
    write_reg_and_update('h80, 32'h0);
    check_all_registers("IRQ mask");

    /* Check transfer registers */
    write_reg_and_update('h40c, 'h3);
    write_reg_and_update('h410, ADDR_MASK);
    write_reg_and_update('h414, ADDR_MASK);
    write_reg_and_update('h418, LENGTH_MASK);
    write_reg_and_update('h41c, LENGTH_MASK);
    write_reg_and_update('h420, STRIDE_MASK);
    write_reg_and_update('h424, STRIDE_MASK);

    check_all_registers("Transfer setup 1");

    /* Check transfer registers */
    write_reg_and_update('h40c, {$random} & 'h3);
    write_reg_and_update('h410, {$random} & ADDR_MASK);
    write_reg_and_update('h414, {$random} & ADDR_MASK);
    write_reg_and_update('h418, {$random} & LENGTH_MASK | LENGTH_ALIGN_MASK);
    write_reg_and_update('h41c, {$random} & LENGTH_MASK);
    write_reg_and_update('h420, {$random} & STRIDE_MASK);
    write_reg_and_update('h424, {$random} & STRIDE_MASK);

    check_all_registers("Transfer setup 2");

    /* Start transfer */
    write_reg_and_update('h400, 'h01);
    write_reg_and_update('h408, 'h01);

    check_all_registers("Transfer submitted");

    @(posedge clk) request_ready <= 1'b1;

    /* Interrupt pending */
    set_reset_reg_value('h84, 'h01);
    set_reset_reg_value('h88, 'h01);

    /* Transfer ID */
    set_reset_reg_value('h404, 'h01);

    /* Tansfer pending */
    set_reset_reg_value('h408, 'h00);

    check_all_registers("Transfer accepted");

    @(posedge clk) response_eot <= 1'b1;
    @(posedge clk) response_eot <= 1'b0;

    /* Interrupt registers */
    set_reset_reg_value('h84, 'h03);
    set_reset_reg_value('h88, 'h03);

    /* Transfer done */
    set_reset_reg_value('h428, 'h01);

    /* Active ID */
    set_reset_reg_value('h42c, 'h01);

    check_all_registers("Transfer completed");

    /* Clear interrupts */
    write_reg('h84, 'h01);
    set_reset_reg_value('h84, 'h02);
    set_reset_reg_value('h88, 'h02);

    check_all_registers("Clear interrupts 1");

    write_reg('h84, 'h02);
    set_reset_reg_value('h84, 'h00);
    set_reset_reg_value('h88, 'h00);

    check_all_registers("Clear interrupts 2");

    /* Check that reset works for all registers */
    do_trigger_reset();
    initialize_expected_reg_mem();
    check_all_registers("Reset 1");
    invert_all_registers();
    do_trigger_reset();
    check_all_registers("Reset 2");
  end

  axi_dmac_regmap #(
    .ID(0),
    .BYTES_PER_BEAT_WIDTH_DEST(BYTES_PER_BEAT),
    .BYTES_PER_BEAT_WIDTH_SRC(BYTES_PER_BEAT),
    .DMA_AXI_ADDR_WIDTH(DMA_AXI_ADDR_WIDTH),
    .DMA_LENGTH_WIDTH(DMA_LENGTH_WIDTH),
    .DMA_LENGTH_ALIGN(DMA_LENGTH_ALIGN),
    .DMA_CYCLIC(1),
    .HAS_DEST_ADDR(1),
    .HAS_SRC_ADDR(1),
    .DMA_2D_TRANSFER(1)
  ) i_axi (
    .s_axi_aclk(s_axi_aclk),
    .s_axi_aresetn(s_axi_aresetn),
    .s_axi_awvalid(s_axi_awvalid),
    .s_axi_awaddr(s_axi_awaddr),
    .s_axi_awready(s_axi_awready),
    .s_axi_awprot(s_axi_awprot),
    .s_axi_wvalid(s_axi_wvalid),
    .s_axi_wdata(s_axi_wdata),
    .s_axi_wstrb(s_axi_wstrb),
    .s_axi_wready(s_axi_wready),
    .s_axi_bvalid(s_axi_bvalid),
    .s_axi_bresp(s_axi_bresp),
    .s_axi_bready(s_axi_bready),
    .s_axi_arvalid(s_axi_arvalid),
    .s_axi_araddr(s_axi_araddr),
    .s_axi_arready(s_axi_arready),
    .s_axi_arprot(s_axi_arprot),
    .s_axi_rvalid(s_axi_rvalid),
    .s_axi_rready(s_axi_rready),
    .s_axi_rresp(s_axi_rresp),
    .s_axi_rdata(s_axi_rdata),

    .request_valid(request_valid),
    .request_ready(request_ready),
    .request_dest_address(request_dest_address),
    .request_src_address(request_src_address),
    .request_x_length(request_x_length),
    .request_y_length(request_y_length),
    .request_dest_stride(request_dest_stride),
    .request_src_stride(request_src_stride),
    .request_last(request_last),

    .response_eot(response_eot),

    .dbg_src_addr(VAL_DBG_SRC_ADDR),
    .dbg_dest_addr(VAL_DBG_DEST_ADDR),
    .dbg_status(VAL_DBG_STATUS),
    .dbg_ids0(VAL_DBG_IDS0),
    .dbg_ids1(VAL_DBG_IDS1)
  );

endmodule