| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| // FPV CSR read and write assertions auto-generated by `reggen` containing data structure |
| // Do Not Edit directly |
| // TODO: This automation currently only support register without HW write access |
| <% |
| from reggen import (gen_fpv) |
| from reggen.register import Register |
| |
| from topgen import lib |
| |
| lblock = block.name.lower() |
| |
| # This template shouldn't be instantiated if the device interface |
| # doesn't actually have any registers. |
| assert rb.flat_regs |
| |
| %>\ |
| <%def name="construct_classes(block)">\ |
| |
| `include "prim_assert.sv" |
| |
| `ifndef FPV_ON |
| `define REGWEN_PATH tb.dut.${reg_block_path} |
| `else |
| `define REGWEN_PATH ${lblock}.${reg_block_path} |
| `endif |
| |
| // Block: ${lblock} |
| module ${mod_base}_csr_assert_fpv import tlul_pkg::*; |
| import top_pkg::*;( |
| input clk_i, |
| input rst_ni, |
| |
| // tile link ports |
| input tl_h2d_t h2d, |
| input tl_d2h_t d2h |
| ); |
| <% |
| addr_width = rb.get_addr_width() |
| addr_msb = addr_width - 1 |
| hro_regs_list = [r for r in rb.flat_regs if (not r.is_hw_writable() and not r.shadowed)] |
| num_hro_regs = len(hro_regs_list) |
| hro_map = {r.offset: (idx, r) for idx, r in enumerate(hro_regs_list)} |
| max_reg_addr = rb.flat_regs[-1].offset |
| windows = rb.windows |
| %>\ |
| |
| `ifdef UVM |
| import uvm_pkg::*; |
| `endif |
| |
| // Currently FPV csr assertion only support HRO registers. |
| % if num_hro_regs > 0: |
| `ifndef VERILATOR |
| `ifndef SYNTHESIS |
| |
| logic oob_addr_err; |
| |
| parameter bit[3:0] MAX_A_SOURCE = 10; // used for FPV only to reduce runtime |
| |
| typedef struct packed { |
| logic [TL_DW-1:0] wr_data; |
| logic [TL_AW-1:0] addr; |
| logic wr_pending; |
| logic rd_pending; |
| } pend_item_t; |
| |
| bit disable_sva; |
| |
| // mask register to convert byte to bit |
| logic [TL_DW-1:0] a_mask_bit; |
| |
| assign a_mask_bit[7:0] = h2d.a_mask[0] ? '1 : '0; |
| assign a_mask_bit[15:8] = h2d.a_mask[1] ? '1 : '0; |
| assign a_mask_bit[23:16] = h2d.a_mask[2] ? '1 : '0; |
| assign a_mask_bit[31:24] = h2d.a_mask[3] ? '1 : '0; |
| |
| bit [${addr_msb}:0] hro_idx; // index for exp_vals |
| bit [${addr_msb}:0] normalized_addr; |
| |
| // Map register address with hro_idx in exp_vals array. |
| always_comb begin: decode_hro_addr_to_idx |
| unique case (pend_trans[d2h.d_source].addr) |
| % for idx, r in hro_map.values(): |
| ${r.offset}: hro_idx <= ${idx}; |
| % endfor |
| // If the register is not a HRO register, the write data will all update to this default idx. |
| default: hro_idx <= ${num_hro_regs}; |
| endcase |
| end |
| |
| // store internal expected values for HW ReadOnly registers |
| logic [TL_DW-1:0] exp_vals[${num_hro_regs + 1}]; |
| |
| `ifdef FPV_ON |
| pend_item_t [MAX_A_SOURCE:0] pend_trans; |
| `else |
| pend_item_t [2**TL_AIW-1:0] pend_trans; |
| `endif |
| |
| // Word-align the incoming TLUL a_address to obtain the normalized address. |
| % if addr_msb > 2: |
| assign normalized_addr = {h2d.a_address[${addr_msb}:2], 2'b0}; |
| % else: |
| assign normalized_addr = '0; |
| % endif |
| |
| % if num_hro_regs > 0: |
| // Assign regwen to registers. If the register does not have regwen, it will default to value 1. |
| logic [${num_hro_regs}-1:0] regwen; |
| % for hro_reg in hro_regs_list: |
| <% regwen = hro_reg.regwen %>\ |
| % if regwen == None: |
| assign regwen[${hro_map.get(hro_reg.offset)[0]}] = 1; |
| % else: |
| assign regwen[${hro_map.get(hro_reg.offset)[0]}] = `REGWEN_PATH.${regwen.lower()}_qs; |
| % endif |
| % endfor |
| |
| typedef enum bit { |
| FpvDefault, |
| FpvRw0c |
| } fpv_reg_access_e; |
| fpv_reg_access_e access_policy [${num_hro_regs}]; |
| |
| // for write HRO registers, store the write data into exp_vals |
| always_ff @(negedge clk_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| oob_addr_err <= 1'b0; |
| pend_trans <= '0; |
| % for hro_reg in hro_regs_list: |
| exp_vals[${hro_map.get(hro_reg.offset)[0]}] <= 'h${f'{hro_reg.resval:0x}'}; |
| % if len(hro_reg.fields) == 1 and hro_reg.fields[0].swaccess.key.lower() == "rw0c": |
| access_policy[${hro_map.get(hro_reg.offset)[0]}] <= FpvRw0c; |
| % else: |
| access_policy[${hro_map.get(hro_reg.offset)[0]}] <= FpvDefault; |
| % endif |
| % endfor |
| end else begin |
| oob_addr_err <= 1'b0; |
| if (h2d.a_valid && d2h.a_ready) begin |
| if ((normalized_addr inside {[0:${max_reg_addr}]}) |
| % for window in windows: |
| || (normalized_addr inside {[${window.offset}: (${window.offset}+${window.items}*8)]}) |
| % endfor |
| ) begin |
| pend_trans[h2d.a_source].addr <= normalized_addr; |
| if (h2d.a_opcode inside {PutFullData, PutPartialData}) begin |
| pend_trans[h2d.a_source].wr_data <= h2d.a_data & a_mask_bit; |
| pend_trans[h2d.a_source].wr_pending <= 1'b1; |
| end else if (h2d.a_opcode == Get) begin |
| pend_trans[h2d.a_source].rd_pending <= 1'b1; |
| end |
| end else begin |
| oob_addr_err <= 1'b1; |
| end |
| end |
| if (d2h.d_valid) begin |
| if (pend_trans[d2h.d_source].wr_pending == 1) begin |
| if (!d2h.d_error && regwen[hro_idx]) begin |
| if (access_policy[hro_idx] == FpvRw0c) begin |
| // Assume FpvWr0c policy only has one field that is wr0c. |
| exp_vals[hro_idx] <= exp_vals[hro_idx][0] == 0 ? 0 : pend_trans[d2h.d_source].wr_data; |
| end else begin |
| exp_vals[hro_idx] <= pend_trans[d2h.d_source].wr_data; |
| end |
| end |
| pend_trans[d2h.d_source].wr_pending <= 1'b0; |
| end |
| if (h2d.d_ready && pend_trans[d2h.d_source].rd_pending == 1) begin |
| pend_trans[d2h.d_source].rd_pending <= 1'b0; |
| end |
| end |
| end |
| end |
| |
| // for read HRO registers, assert read out values by access policy and exp_vals |
| % for hro_reg in hro_regs_list: |
| <% |
| r_name = hro_reg.name.lower() |
| reg_addr = hro_reg.offset |
| reg_addr_hex = format(reg_addr, 'x') |
| reg_mask = 0 |
| f_size = len(hro_reg.fields) |
| |
| for f in hro_reg.get_field_list(): |
| f_access = f.swaccess.key.lower() |
| if f_access == "rw" or (f_access == "rw0c" and f_size == 1): |
| reg_mask = reg_mask | f.bits.bitmask() |
| %>\ |
| % if reg_mask != 0: |
| <% reg_mask_hex = format(reg_mask, 'x') %>\ |
| `ASSERT(${r_name}_rd_A, d2h.d_valid && pend_trans[d2h.d_source].rd_pending && |
| pend_trans[d2h.d_source].addr == ${addr_width}'h${reg_addr_hex} |-> |
| d2h.d_error || |
| (d2h.d_data & 'h${reg_mask_hex}) == (exp_vals[${hro_map.get(reg_addr)[0]}] & 'h${reg_mask_hex})) |
| |
| % endif |
| % endfor |
| % endif |
| |
| `ASSERT(TlulOOBAddrErr_A, oob_addr_err |-> s_eventually(d2h.d_valid && d2h.d_error)) |
| |
| // This FPV only assumption is to reduce the FPV runtime. |
| `ASSUME_FPV(TlulSource_M, h2d.a_source >= 0 && h2d.a_source <= MAX_A_SOURCE, clk_i, !rst_ni) |
| |
| `ifdef UVM |
| initial forever begin |
| bit csr_assert_en; |
| uvm_config_db#(bit)::wait_modified(null, "%m", "csr_assert_en"); |
| if (!uvm_config_db#(bit)::get(null, "%m", "csr_assert_en", csr_assert_en)) begin |
| `uvm_fatal("csr_assert", "Can't find csr_assert_en") |
| end |
| disable_sva = !csr_assert_en; |
| end |
| `endif |
| |
| `endif |
| `endif |
| % endif |
| endmodule |
| |
| `undef REGWEN_PATH |
| </%def>\ |
| ${construct_classes(block)} |