// Amazon FPGA Hardware Development Kit // // Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. // // Licensed under the Amazon Software License (the "License"). You may not use // this file except in compliance with the License. A copy of the License is // located at // // http://aws.amazon.com/asl/ // // or in the "license" file accompanying this file. This file is distributed on // an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or // implied. See the License for the specific language governing permissions and // limitations under the License. // This has DMA helper classes //-------------------------------------- //TODO: // - Add support for freeing, reusing pages // - Add flow control (Desc, and wb ring) //************************************************************************************ //------------------------------------------------------------------------------------ // Access class, this provides access to the DUT and testing infrastructure. We // use a class so that can move easily bewteen testbenches without changing the tests. // The access given here are: // // - BAR4 peek/poke // - Host memory write/read // //------------------------------------------------------------------------------------ //************************************************************************************ `ifdef SH_BFM_TB import tb_type_defines_pkg::*; `endif class mem_access_t; logic rnw; logic [63:0] addr; logic [31:0] data [$]; logic wc; function new(); endfunction // new endclass // mem_access_t class access_t; bit wc; //Enable write combining mailbox poke_mb; bit[63:0] bar; bit[63:0] ocl_bar; semaphore access_lock; //Semaphore to make sure read/write are exclusive. This is required for BFMs that cannot handle multiple // outstanding cycles simultaneously function new (input bit wc=0, input[63:0] bar=0, input[63:0] ocl_bar=0); this.wc = wc; this.poke_mb = new(1); this.bar = bar; this.ocl_bar = ocl_bar; access_lock = new(1); endfunction virtual task peek_verify (input[63:0] addr, output logic[31:0] data, input [31:0] exp_data, input [31:0] mask=32'hffff_ffff, output logic error, input exit_on_error = 0, input int max_iters = 10); int iter = 0; while (iter < max_iters) begin peek(.addr(addr), .data(data)); if ((data & mask) != exp_data) begin if (iter == (max_iters-1)) begin $display($time,,,"***ERROR*** : peek_verify - Addr = 0x%016x, Expected Data = 0x%08x, Actual Data = 0x%08x, Mask = 0x%08x", addr, exp_data, data, mask); error = 1; if (exit_on_error) begin $display($time,,,"***FATAL*** : peek_verify - Exiting on Error"); $finish; end // if (exit_on_error) end // if (iter >= max_iters) else begin error = 0; end // else: !if(iter >= max_iters) end // if ((data & mask) != exp_data) else return; iter++; end // while (iter < max_iters) endtask // peek_verify task start_mem_access_threads(); fork : FORK_MEM_ACCESS_THREADS mem_write_thread(); join_none endtask // start_mem_access_threads //Poke task poke (input[63:0] addr, input[31:0] data[$], input bit wc=this.wc); mem_access_t wr_txn; wr_txn = new(); wr_txn.rnw = 0; wr_txn.addr = addr; wr_txn.data = data; wr_txn.wc = wc; poke_mb.put(wr_txn); endtask // poke virtual task poke_dw (input [63:0] addr, input [31:0] data); logic [31:0] data_q [$]; data_q.push_back(data); this.poke (.addr(addr), .data(data_q), .wc(0)); endtask // poke_dw //Allocate a page. Note in sh_bfm tb don't need to do any allocation because is assoc array virtual function alloc_mem (input[63:0] addr); //tb.map_host_memory(.addr(addr)); endfunction //Read task extern virtual task peek (input[63:0] addr, output logic[31:0] data); //Mem Write processing thread extern task mem_write_thread (); //Write DW to the host from the buffer extern virtual function write_host_dw (input[63:0] addr, input[31:0] data); //Read DW from the host into buffer extern virtual function[31:0] read_host_dw (input[63:0] addr); //Write byte to the host from the buffer extern virtual function write_host_byte (input[63:0] addr, input[7:0] data); //Read byte from the host into buffer extern virtual function[7:0] read_host_byte (input[63:0] addr); //Write to OCL extern virtual task poke_ocl(input[15:0] addr, input[31:0] data); //Read from OCL extern virtual task peek_ocl(input[15:0] addr, output[31:0] data); endclass //Peek task access_t::peek (input[63:0] addr, output logic[31:0] data); tb.peek(.addr(addr), .data(data)); endtask //mem_write_thread task access_t::mem_write_thread(); mem_access_t wr_txn; forever begin poke_mb.get(wr_txn); if (!wr_txn.wc) begin for (int i=0; i0) begin //Clear out the write data cur_wdata_q = {}; //Supported DW lengths for H2C/C2H descriptors = 1DW, 4DW, 8DW this.cfg_desc_wc_max = (this.cfg_desc_wc_max > 8) ? 8 : this.cfg_desc_wc_max; this.cfg_desc_wc_min = (this.cfg_desc_wc_min > 8) ? 8 : this.cfg_desc_wc_min; if ((this.cfg_desc_wc_min == this.cfg_desc_wc_max) && (this.cfg_desc_wc_min == 1 || this.cfg_desc_wc_min == 4 || this.cfg_desc_wc_min == 8)) cur_wc_length = this.cfg_desc_wc_min; else std::randomize(cur_wc_length) with {cur_wc_length inside {1, 4, 8};}; //If amount of data is less than write combine length, use that amount for data length if (this.h2c_desc_q.size0) begin //Clear out the write data cur_wdata_q = {}; //Get a random write combine length -- note this works with no write combine as well (just takes a bit longer) //Supported DW lengths for H2C/C2H descriptors = 1DW, 4DW, 8DW this.cfg_desc_wc_max = (this.cfg_desc_wc_max > 8) ? 8 : this.cfg_desc_wc_max; this.cfg_desc_wc_min = (this.cfg_desc_wc_min > 8) ? 8 : this.cfg_desc_wc_min; if ((this.cfg_desc_wc_min == this.cfg_desc_wc_max) && (this.cfg_desc_wc_min == 1 || this.cfg_desc_wc_min == 4 || this.cfg_desc_wc_min == 8)) cur_wc_length = this.cfg_desc_wc_min; else std::randomize(cur_wc_length) with {cur_wc_length inside {1, 4, 8};}; //If amount of data is less than write combine length, use that amount for data length if (this.h2c_desc_q.size0) begin //Clear out the write data cur_wdata_q = {}; //Get a random write combine length -- note this works with no write combine as well (just takes a bit longer) //CHAKRA: cur_wc_length = $urandom_range(this.cfg_desc_wc_max, this.cfg_desc_wc_min); //Supported DW lengths for H2C/C2H descriptors = 1DW, 4DW, 8DW this.cfg_desc_wc_max = (this.cfg_desc_wc_max > 8) ? 8 : this.cfg_desc_wc_max; this.cfg_desc_wc_min = (this.cfg_desc_wc_min > 8) ? 8 : this.cfg_desc_wc_min; if ((this.cfg_desc_wc_min == this.cfg_desc_wc_max) && (this.cfg_desc_wc_min == 1 || this.cfg_desc_wc_min == 4 || this.cfg_desc_wc_min == 8)) cur_wc_length = this.cfg_desc_wc_min; else std::randomize(cur_wc_length) with {cur_wc_length inside {1, 4, 8};}; //If amount of data is less than write combine length, use that amount for data length if (this.c2h_desc_q.size0) begin //Clear out the write data cur_wdata_q = {}; //Get a random write combine length -- note this works with no write combine as well (just takes a bit longer) //Supported DW lengths for H2C/C2H descriptors = 1DW, 4DW, 8DW this.cfg_desc_wc_max = (this.cfg_desc_wc_max > 8) ? 8 : this.cfg_desc_wc_max; this.cfg_desc_wc_min = (this.cfg_desc_wc_min > 8) ? 8 : this.cfg_desc_wc_min; if ((this.cfg_desc_wc_min == this.cfg_desc_wc_max) && (this.cfg_desc_wc_min == 1 || this.cfg_desc_wc_min == 4 || this.cfg_desc_wc_min == 8)) cur_wc_length = this.cfg_desc_wc_min; else std::randomize(cur_wc_length) with {cur_wc_length inside {1, 4, 8};}; //If amount of data is less than write combine length, use that amount for data length if (this.c2h_desc_q.size= c2h_wb_desc_rd_ptr ? (curr_md_wr_ptr - c2h_wb_desc_rd_ptr) : (cfg_c2h_num_md + curr_md_wr_ptr - c2h_wb_desc_rd_ptr); #1ns; end // $display($time,,,"dma_buf_t.process_c2h_wb_thread curr_md_wr_ptr = %0d, c2h_wb_desc_rd_ptr = %0d, num_wb_to_read = %0d", curr_md_wr_ptr, c2h_wb_desc_rd_ptr, num_wb_to_read); //Get the next descriptor fields //$display($time,,,"Getting next descriptor. c2h_wb_desc_rd_ptr=0x%0x", c2h_wb_desc_rd_ptr); repeat (num_wb_to_read) begin cur_wb_desc = get_nxt_wb_desc(.wb_buf(c2h_wb_buf), .wb_ptr(c2h_wb_desc_rd_ptr)); //$display($time,,,"POLL: valid=0x%x; dw=0x%x", desc_valid, cur_wb_desc[0]); // while(cur_wb_desc.valid) // begin assert(cur_wb_desc.valid == 1'b1) else begin $display($time,,,"***ERROR*** dma_buf_t.process_c2h_wb_thread Pkt[%0d] cur_wb_desc.valid=0", num_c2h_pkts); $finish; end //If packet is not in progress, create a new buffer to assemble packet if (!pkt_inp) begin cur_asm_pkt = new(); pkt_inp = 1; accum_length = 0; end //Get the next RX buffer that was posted so can extract the packet data cur_post_buf = c2h_post_buf_q.pop_front(); //Populate the buffer from Host Memory cur_post_buf.read_host_buf(.length(cur_wb_desc.length)); //Make sure the WB length is <= buffer length if (cur_wb_desc.length>cur_post_buf.buf_size) begin $display($time,,,"***ERROR*** dma_buf_t.process_c2h_wb_thread Pkt[%0d] cur_wb_desc.length=0x%0x is greater than posted length cur_post_buf.buf_size=0x%0x", num_c2h_pkts, cur_wb_desc.length, cur_post_buf.buf_size); $finish; end //Copy the post buffer data to the assembled packet data for (int i=0; i= c2h_wb_rdptr_coalesce_cnt) begin // if (rd_ptr_pend_cnt >= (cfg_c2h_num_md-1)) begin //Update Read Pointer in HW addr = c2h_csr_base_addr + 16'h324; wr_data = c2h_wb_desc_rd_ptr; access.poke_dw (.addr(addr), .data(wr_data)); // There is a delay for writes to reach the HW// access.peek_verify (.addr(addr), .data(rd_data), .exp_data(wr_data), .error(error), .exit_on_error(1)); rd_ptr_pend_cnt = 0; c2h_wb_rdptr_coalesce_cnt = $urandom_range((cfg_c2h_num_md-1), 1); end //Read the next Descriptor //cur_wb_desc = get_nxt_wb_desc(.wb_buf(c2h_wb_buf), .wb_ptr(c2h_wb_desc_rd_ptr)); //end end // repeat (num_wb_to_read) end // forever begin endtask //Get the next WB descriptor function wb_desc_t get_nxt_wb_desc (dma_buf_t wb_buf, input int wb_ptr); wb_desc_t wb_desc; logic [29:0] dummy; wb_desc = new(); // $display("Calling read_host_buf with length = %0d", c2h_wb_desc_size); wb_buf.read_host_buf(.start_offset(wb_ptr * c2h_wb_desc_size), .length(c2h_wb_desc_size)); {wb_desc.length[31:0]} = { wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 3], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 2], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 1], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 0] }; {dummy[29:0], wb_desc.eop, wb_desc.valid} = { wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 7], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 6], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 5], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 4] }; if (this.desc_type == 0) begin {wb_desc.user_bits[32:0]} = { wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 11], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 10], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 9], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 8] }; {wb_desc.user_bits[63:32]} = { wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 15], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 14], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 13], wb_buf.data[(wb_ptr * c2h_wb_desc_size) + 12] }; end // if (this.desc_type == 0) else begin wb_desc.user_bits[63:0] = 64'd0; end get_nxt_wb_desc = wb_desc; endfunction //Clear the WB descriptor function clr_wb_desc(dma_buf_t wb_buf, input int wb_ptr); //Clear the WB descriptor for (int i=0; i= max_num_desc)) || (c2h_wb_desc_wr_ptr_next == c2h_wb_desc_rd_ptr)) begin #(this.wb_poll_interval * 1ns); c2h_cdt_limit_old = c2h_cdt_limit; c2h_cdt_limit = access.read_host_dw(cfg_c2h_cdt_limit_addr); credit_diff = c2h_cdt_limit - c2h_cdt_cons; if (c2h_cdt_limit_old != c2h_cdt_limit) begin pkt_count = access.read_host_dw(cfg_c2h_wb_pkt_cnt_addr); desc_count = access.read_host_dw(cfg_c2h_wb_desc_cnt_addr); wb_wr_ptr = access.read_host_dw(cfg_c2h_wb_status_addr); if (this.verbose) $display($time,,,"Poll C2H Status: desc_cons=0x%0x, desc_limit=0x%0x, pkt_count=0x%0x, desc_count=0x%0x, wb_wr_ptr=0x%0x", c2h_cdt_cons, c2h_cdt_limit, pkt_count, desc_count, wb_wr_ptr); end end //Randomize length/offset tmp_length = $urandom_range(pc2h_max_length, pc2h_min_length); tmp_offset = $urandom_range(pc2h_max_offset, pc2h_min_offset); //Adjust length if offset + length > page_size if ((tmp_offset + tmp_length) > page_size) tmp_length = page_size - tmp_offset; //Allocate the buffer tmp_buf = new(.access(this.access), .addr(this.alloc_page(.s("POST_C2H_DESC_THREAD")) + tmp_offset), .buf_size(tmp_length), .verbose(this.verbose)); //Post the Buffer this.post_c2h_desc(.post_buf(tmp_buf)); //Increment the consumed c2h_cdt_cons++; credit_diff = c2h_cdt_limit - c2h_cdt_cons; //50/50 chance of ringing the doorbell tmp_rnd = $urandom; //Always ring doorbell if posted max_num_desc if (tmp_rnd[0] || ((32'h64 - credit_diff) >= max_num_desc)) this.c2h_doorbell(); end endtask endclass