Add VPI array indexing support in signal names (#7097) (#7187)

This commit is contained in:
Christian Hecken 2026-03-05 01:37:06 +01:00 committed by GitHub
parent 1bf2ea7643
commit 84f3cc4f48
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 677 additions and 15 deletions

View File

@ -33,6 +33,7 @@
#include "vltstd/vpi_user.h"
#include <algorithm>
#include <cstdarg>
#include <cstdio>
#include <list>
@ -365,6 +366,7 @@ class VerilatedVpioVar VL_NOT_FINAL : public VerilatedVpioVarBase {
} m_mask; // memoized variable mask
uint32_t m_entSize = 0; // memoized variable size
uint32_t m_bitOffset = 0;
int32_t m_partselBits = -1; // Part-select width, -1 means no part-select active
protected:
void* m_varDatap = nullptr; // varp()->datap() adjusted for array entries
@ -384,6 +386,7 @@ public:
m_entSize = varp->m_entSize;
m_varDatap = varp->m_varDatap;
m_index = varp->m_index;
m_partselBits = varp->m_partselBits;
// Not copying m_prevDatap, must be nullptr
} else {
m_mask.u32 = 0;
@ -397,10 +400,50 @@ public:
return dynamic_cast<VerilatedVpioVar*>(reinterpret_cast<VerilatedVpio*>(h));
}
uint32_t bitOffset() const override { return m_bitOffset; }
uint32_t bitSize() const {
if (m_partselBits >= 0) return static_cast<uint32_t>(m_partselBits);
return VerilatedVpioVarBase::bitSize();
}
uint32_t size() const override {
if (m_partselBits >= 0) return static_cast<uint32_t>(m_partselBits);
return VerilatedVpioVarBase::size();
}
uint32_t mask() const { return m_mask.u32; }
uint8_t mask_byte(int idx) const { return m_mask.u8[idx & 3]; }
uint32_t entSize() const { return m_entSize; }
const std::vector<int32_t>& index() const { return m_index; }
// Create a part-selected view of this variable with the given bit range [hi:lo].
VerilatedVpioVar* withPartSelect(int32_t hi, int32_t lo) const {
if (isIndexedDimUnpacked()) return nullptr;
// Need a packed range to select from
const VerilatedRange* range = get_range();
if (!range) return nullptr;
// Normalize so sel_lo <= sel_hi
const int32_t sel_lo = std::min(hi, lo);
const int32_t sel_hi = std::max(hi, lo);
const int32_t decl_left = range->left();
const int32_t decl_right = range->right();
const int32_t decl_lo = std::min(decl_left, decl_right);
const int32_t decl_hi = std::max(decl_left, decl_right);
if (sel_lo < decl_lo || sel_hi > decl_hi) return nullptr;
const int32_t width = sel_hi - sel_lo + 1;
// Convert to storage bit position
int32_t normalized_lo;
if (decl_left > decl_right) // descending [31:0]
normalized_lo = sel_lo - decl_lo;
else // ascending [0:31]
normalized_lo = decl_right - sel_hi;
auto* ret = new VerilatedVpioVar{this};
ret->m_bitOffset += normalized_lo;
ret->m_partselBits = width;
return ret;
}
VerilatedVpioVar* withIndex(int32_t index) const {
if (VL_UNLIKELY(indexedDim() + 1 >= varp()->dims())) return nullptr;
@ -2174,6 +2217,88 @@ void vpi_get_systf_info(vpiHandle /*object*/, p_vpi_systf_data /*systf_data_p*/)
VL_VPI_UNIMP_();
}
// Bit range information extracted from a name string by vl_vpi_parse_indices.
struct VlVpiBitRange final {
int32_t hi = 0;
int32_t lo = 0;
bool valid = false;
};
// Parse multi-dimensional array indices and an optional trailing bit range from a name string.
// e.g., "mem[0][3][2]" -> name becomes "mem", indices = {0, 3, 2}
// e.g., "mem[0][3][15:8]" -> name becomes "mem", indices = {0, 3}, bitRange = {15, 8}
// e.g., "signal[31:0]" -> name becomes "signal", indices = {}, bitRange = {31, 0}
// Returns true if any brackets were parsed successfully, false otherwise.
static bool vl_vpi_parse_indices(std::string& name, std::vector<PLI_INT32>& indices,
VlVpiBitRange* bitRange = nullptr) {
if (name.empty() || name.back() != ']') return false;
// Collapse consecutive spaces into single spaces
name.erase(
std::unique(name.begin(), name.end(), [](char a, char b) { return a == ' ' && b == ' '; }),
name.end());
// Only parse brackets after the last escaped identifier's terminating space
size_t escapeSpacePos = std::string::npos;
const size_t backslashPos = name.rfind('\\');
if (backslashPos != std::string::npos) escapeSpacePos = name.find(' ', backslashPos);
indices.clear();
size_t end = name.length();
bool first = true;
while (end > 0 && name[end - 1] == ']') {
const size_t close = end - 1;
// Search backward for matching '['
size_t open = close;
while (open > 0 && name[open - 1] != '[') --open;
if (open == 0) return false; // No matching '['
--open; // Points to '['
// For escaped identifiers: skip brackets that come before the terminating space
if (escapeSpacePos != std::string::npos && open < escapeSpacePos) break;
const std::string content = name.substr(open + 1, close - open - 1);
if (content.empty()) return false; // Empty brackets []
// On the first (rightmost) bracket, check for bit range [hi:lo]
if (first && bitRange) {
const size_t colon = content.find(':');
if (colon != std::string::npos) {
char* endp = nullptr;
const long hi_val = std::strtol(content.c_str(), &endp, 10);
if (!endp || *endp != ':') return false;
const long lo_val = std::strtol(endp + 1, &endp, 10);
if (!endp || *endp != '\0') return false;
bitRange->hi = static_cast<int32_t>(hi_val);
bitRange->lo = static_cast<int32_t>(lo_val);
bitRange->valid = true;
end = open;
first = false;
continue;
}
}
first = false;
// Parse as integer index
char* endp = nullptr;
const long val = std::strtol(content.c_str(), &endp, 10);
if (!endp || *endp != '\0') return false;
indices.push_back(static_cast<PLI_INT32>(val));
end = open;
}
if (indices.empty() && !(bitRange && bitRange->valid)) return false;
// Reverse indices to get them in forward order [0][3][2] -> {0, 3, 2}
std::reverse(indices.begin(), indices.end());
// Truncate name to remove the indices
name.erase(end);
return true;
}
// for obtaining handles
vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
@ -2181,22 +2306,29 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
VL_VPI_ERROR_RESET_();
if (VL_UNLIKELY(!namep)) return nullptr;
VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_handle_by_name %s %p\n", namep, scope););
// Parse any array indices and optional bit range from the name
// e.g., "mem[0][3][2]" or "signal[15:8]" or "mem[0][3][15:8]"
std::string scopeAndName = namep;
static thread_local std::vector<PLI_INT32> indices;
VlVpiBitRange bitRange;
const bool hasIndices = vl_vpi_parse_indices(scopeAndName, indices, &bitRange);
const VerilatedVar* varp = nullptr;
const VerilatedScope* scopep;
const VerilatedVpioScope* const voScopep = VerilatedVpioScope::castp(scope);
std::string scopeAndName = namep;
if (0 == std::strncmp(namep, "$root.", std::strlen("$root."))) {
namep += std::strlen("$root.");
scopeAndName = namep;
if (0 == std::strncmp(scopeAndName.c_str(), "$root.", std::strlen("$root."))) {
scopeAndName.erase(0, std::strlen("$root."));
} else if (voScopep) {
const bool scopeIsPackage = VerilatedVpioPackage::castp(scope) != nullptr;
scopeAndName = std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + namep;
namep = const_cast<PLI_BYTE8*>(scopeAndName.c_str());
scopeAndName
= std::string{voScopep->fullname()} + (scopeIsPackage ? "" : ".") + scopeAndName;
}
{
// This doesn't yet follow the hierarchy in the proper way
bool isPackage = false;
scopep = Verilated::threadContextp()->scopeFind(namep);
scopep = Verilated::threadContextp()->scopeFind(scopeAndName.c_str());
if (scopep) { // Whole thing found as a scope
if (scopep->type() == VerilatedScope::SCOPE_MODULE) {
return (new VerilatedVpioModule{scopep})->castVpiHandle();
@ -2248,11 +2380,30 @@ vpiHandle vpi_handle_by_name(PLI_BYTE8* namep, vpiHandle scope) {
}
if (!varp) return nullptr;
// Create the initial variable handle
vpiHandle resultHandle;
if (varp->isParam()) {
return (new VerilatedVpioParam{varp, scopep})->castVpiHandle();
resultHandle = (new VerilatedVpioParam{varp, scopep})->castVpiHandle();
} else {
return (new VerilatedVpioVar{varp, scopep})->castVpiHandle();
resultHandle = (new VerilatedVpioVar{varp, scopep})->castVpiHandle();
}
// If we have indices, apply them using vpi_handle_by_multi_index
if (hasIndices && !indices.empty()) {
resultHandle = vpi_handle_by_multi_index(resultHandle, indices.size(), indices.data());
if (!resultHandle) return nullptr;
}
// If we have a bit range part-select, apply it
if (bitRange.valid) {
VerilatedVpioVar* const varop = VerilatedVpioVar::castp(resultHandle);
if (!varop) return nullptr;
VerilatedVpioVar* const partsel = varop->withPartSelect(bitRange.hi, bitRange.lo);
if (!partsel) return nullptr;
resultHandle = partsel->castVpiHandle();
}
return resultHandle;
}
vpiHandle vpi_handle_by_index(vpiHandle object, PLI_INT32 indx) {
@ -4022,8 +4173,20 @@ PLI_INT32 vpi_control(PLI_INT32 operation, ...) {
}
}
vpiHandle vpi_handle_by_multi_index(vpiHandle /*obj*/, PLI_INT32 /*num_index*/,
PLI_INT32* /*index_array*/) {
VL_VPI_UNIMP_();
return nullptr;
vpiHandle vpi_handle_by_multi_index(vpiHandle obj, PLI_INT32 num_index, PLI_INT32* index_array) {
VL_DEBUG_IF_PLI(VL_DBG_MSGF("- vpi: vpi_handle_by_multi_index %p %d\n", obj, num_index););
VerilatedVpiImp::assertOneCheck();
VL_VPI_ERROR_RESET_();
if (VL_UNLIKELY(!obj)) return nullptr;
if (VL_UNLIKELY(!index_array)) return nullptr;
if (VL_UNLIKELY(num_index <= 0)) return nullptr;
vpiHandle result_handle = obj;
for (PLI_INT32 i = 0; i < num_index; ++i) {
result_handle = vpi_handle_by_index(result_handle, index_array[i]);
if (VL_UNLIKELY(!result_handle)) { return nullptr; }
}
return result_handle;
}

View File

@ -178,7 +178,6 @@ module t;
`checkh(wide_asc, 80'h1234_56789abc_dcb0ffe5);
end
`ifndef VERILATOR // Unsupported
begin : memory_1d
$display("= uvm_hdl_read/deposit 1D memory");
i = uvm_hdl_check_path("t.mem1d[0]");
@ -221,7 +220,6 @@ module t;
`checkh(i, 1);
`checkh(mem2d[2][3], 32'h2300);
end
`endif
begin : t_deposit_bad
$display("= uvm_hdl_deposit bad ranges");

View File

@ -10,6 +10,8 @@ uvm_dpi_get_tool_name_c() = Verilator
= uvm_hdl_deposit multi-bit
= uvm_hdl_read/deposit wide decending
= uvm_hdl_read/deposit wide ascending
= uvm_hdl_read/deposit 1D memory
= uvm_hdl_read/deposit 2D memory
= uvm_hdl_deposit bad ranges
===
UVM Report expected on next line:

View File

@ -10,6 +10,8 @@ uvm_dpi_get_tool_name_c() = Verilator
= uvm_hdl_deposit multi-bit
= uvm_hdl_read/deposit wide decending
= uvm_hdl_read/deposit wide ascending
= uvm_hdl_read/deposit 1D memory
= uvm_hdl_read/deposit 2D memory
= uvm_hdl_deposit bad ranges
===
UVM Report expected on next line:

View File

@ -1031,6 +1031,403 @@ int _mon_check_vlog_info() {
return 0;
}
int _mon_check_multi_index() {
s_vpi_value v;
v.format = vpiIntVal;
// vpi_handle_by_multi_index tests
// Basic tests for vpi_handle_by_multi_index
{
// 1D unpacked array: quads[2:3] with 62-bit elements
TestVpiHandle vh_1d_base = vpi_handle_by_name((PLI_BYTE8*)"t.quads", nullptr);
CHECK_RESULT_NZ(vh_1d_base);
PLI_INT32 idx_1d[1] = {2};
TestVpiHandle vh_1d = vpi_handle_by_multi_index(vh_1d_base, 1, idx_1d);
CHECK_RESULT_NZ(vh_1d);
CHECK_RESULT(vpi_get(vpiType, vh_1d), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_1d), 62);
// 2D unpacked array: mem_2d[3:0][7:0] with 8-bit elements
TestVpiHandle vh_2d_base = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d", nullptr);
CHECK_RESULT_NZ(vh_2d_base);
PLI_INT32 idx_2d[2] = {1, 3};
TestVpiHandle vh_2d = vpi_handle_by_multi_index(vh_2d_base, 2, idx_2d);
CHECK_RESULT_NZ(vh_2d);
CHECK_RESULT(vpi_get(vpiType, vh_2d), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_2d), 8);
vpi_get_value(vh_2d, &v);
CHECK_RESULT(v.value.integer, 11); // 1*8 + 3
// 3D unpacked array: mem_3d[0:1][1:0][0:1] with 96-bit elements
TestVpiHandle vh_3d_base = vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d", nullptr);
CHECK_RESULT_NZ(vh_3d_base);
PLI_INT32 idx_3d[3] = {1, 1, 1};
TestVpiHandle vh_3d = vpi_handle_by_multi_index(vh_3d_base, 3, idx_3d);
CHECK_RESULT_NZ(vh_3d);
CHECK_RESULT(vpi_get(vpiType, vh_3d), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_3d), 96);
vpi_get_value(vh_3d, &v);
CHECK_RESULT(v.value.integer, 7); // (1*4) + (1*2) + 1
// Verify multi_index matches sequential vpi_handle_by_index
TestVpiHandle vh_seq_base = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d", nullptr);
CHECK_RESULT_NZ(vh_seq_base);
TestVpiHandle vh_seq_1 = vpi_handle_by_index(vh_seq_base, 1);
CHECK_RESULT_NZ(vh_seq_1);
TestVpiHandle vh_seq_2 = vpi_handle_by_index(vh_seq_1, 3);
CHECK_RESULT_NZ(vh_seq_2);
vpi_get_value(vh_seq_2, &v);
CHECK_RESULT(v.value.integer, 11);
}
// Error handling for vpi_handle_by_multi_index
{
// Null handle
PLI_INT32 idx_null[1] = {0};
TestVpiHandle vh_null_hdl = vpi_handle_by_multi_index(nullptr, 1, idx_null);
CHECK_RESULT_Z(vh_null_hdl);
// Null index array
TestVpiHandle vh_base = vpi_handle_by_name((PLI_BYTE8*)"t.quads", nullptr);
CHECK_RESULT_NZ(vh_base);
TestVpiHandle vh_null_idx = vpi_handle_by_multi_index(vh_base, 1, nullptr);
CHECK_RESULT_Z(vh_null_idx);
// Zero num_index
PLI_INT32 idx_zero[1] = {0};
TestVpiHandle vh_zero = vpi_handle_by_multi_index(vh_base, 0, idx_zero);
CHECK_RESULT_Z(vh_zero);
// Negative num_index
PLI_INT32 idx_neg[1] = {0};
TestVpiHandle vh_neg = vpi_handle_by_multi_index(vh_base, -1, idx_neg);
CHECK_RESULT_Z(vh_neg);
}
// Bound checking
{
// Out of bounds on 1D array (quads is [2:3])
TestVpiHandle vh_1d = vpi_handle_by_name((PLI_BYTE8*)"t.quads", nullptr);
CHECK_RESULT_NZ(vh_1d);
PLI_INT32 idx_oob_1d[1] = {99};
TestVpiHandle vh_oob_1d = vpi_handle_by_multi_index(vh_1d, 1, idx_oob_1d);
CHECK_RESULT_Z(vh_oob_1d);
// Out of bounds on 2D array (mem_2d[3:0][7:0])
TestVpiHandle vh_2d = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d", nullptr);
CHECK_RESULT_NZ(vh_2d);
PLI_INT32 idx_oob_2d[2] = {0, 99};
TestVpiHandle vh_oob_2d = vpi_handle_by_multi_index(vh_2d, 2, idx_oob_2d);
CHECK_RESULT_Z(vh_oob_2d);
}
// Boundary: lowest and highest valid indices on 2D array
{
TestVpiHandle vh1 = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d", nullptr);
CHECK_RESULT_NZ(vh1);
PLI_INT32 lo[2] = {0, 0};
TestVpiHandle vh2 = vpi_handle_by_multi_index(vh1, 2, lo);
CHECK_RESULT_NZ(vh2);
vpi_get_value(vh2, &v);
CHECK_RESULT(v.value.integer, 0);
PLI_INT32 hi[2] = {3, 7};
TestVpiHandle vh3 = vpi_handle_by_multi_index(vh1, 2, hi);
CHECK_RESULT_NZ(vh3);
vpi_get_value(vh3, &v);
CHECK_RESULT(v.value.integer, 31); // 3*8 + 7
}
// Packed dimension indexing: mem_3d fully indexed gives 96-bit element (VLWide)
{
PLI_INT32 indices[3] = {0, 0, 0};
TestVpiHandle vh_elem = vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d", nullptr);
CHECK_RESULT_NZ(vh_elem);
TestVpiHandle vh_3d = vpi_handle_by_multi_index(vh_elem, 3, indices);
CHECK_RESULT_NZ(vh_3d);
// Bit selection within element
TestVpiHandle vh_3d_bit0 = vpi_handle_by_index(vh_3d, 0);
CHECK_RESULT_NZ(vh_3d_bit0);
CHECK_RESULT(vpi_get(vpiSize, vh_3d_bit0), 1);
// Out of range bit
TestVpiHandle vh_3d_oob = vpi_handle_by_index(vh_3d, 96);
CHECK_RESULT_Z(vh_3d_oob);
}
// vpi_handle_by_name indexing tests
{
// Escaped identifier with literal brackets in the name
TestVpiHandle vh_esc
= vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped_with_brackets[3] ", nullptr);
CHECK_RESULT_NZ(vh_esc);
CHECK_RESULT(vpi_get(vpiType, vh_esc), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_esc), 8);
vpi_get_value(vh_esc, &v);
CHECK_RESULT(v.value.integer, 0x5a);
// Escaped identifier with whitespace and trailing part-select
// 0x5a = 0b01011010, [7:4] = 0b0101 = 5
TestVpiHandle vh_esc_ps
= vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped_with_brackets[3] [7:4]", nullptr);
CHECK_RESULT_NZ(vh_esc_ps);
CHECK_RESULT(vpi_get(vpiType, vh_esc_ps), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_esc_ps), 4);
vpi_get_value(vh_esc_ps, &v);
CHECK_RESULT(v.value.integer, 0x5);
// Escaped identifier with multiple whitespaces and trailing part-select
// 0x5a = 0b01011010, [3:0] = 0b1010 = 0xa
TestVpiHandle vh_esc_ps_multispace
= vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped_with_brackets[3] [3:0]", nullptr);
CHECK_RESULT_NZ(vh_esc_ps_multispace);
CHECK_RESULT(vpi_get(vpiType, vh_esc_ps_multispace), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_esc_ps_multispace), 4);
vpi_get_value(vh_esc_ps_multispace, &v);
CHECK_RESULT(v.value.integer, 0xa);
// Escaped instance name (with brackets as part of identifier) accessed through hierarchy
TestVpiHandle vh_escaped_inst_sig
= vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped.inst[0] .sig", nullptr);
CHECK_RESULT_NZ(vh_escaped_inst_sig);
CHECK_RESULT(vpi_get(vpiType, vh_escaped_inst_sig), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_escaped_inst_sig), 8);
// Escaped instance name with part-select
TestVpiHandle vh_escaped_inst_sig_ps
= vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped.inst[0] .sig[3:0]", nullptr);
CHECK_RESULT_NZ(vh_escaped_inst_sig_ps);
CHECK_RESULT(vpi_get(vpiType, vh_escaped_inst_sig_ps), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_escaped_inst_sig_ps), 4);
// Two escaped identifiers in the path: escaped instance + escaped signal name
TestVpiHandle vh_two_escapes
= vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped.inst[0] .\\escaped_sig[1] ", nullptr);
CHECK_RESULT_NZ(vh_two_escapes);
CHECK_RESULT(vpi_get(vpiType, vh_two_escapes), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_two_escapes), 8);
// Two escaped identifiers with part-select and consecutive spaces
TestVpiHandle vh_two_escapes_ps = vpi_handle_by_name(
(PLI_BYTE8*)"t.\\escaped.inst[0] .\\escaped_sig[1] [3:0]", nullptr);
CHECK_RESULT_NZ(vh_two_escapes_ps);
CHECK_RESULT(vpi_get(vpiType, vh_two_escapes_ps), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_two_escapes_ps), 4);
}
// vpi_handle_by_name with array indexing
{
TestVpiHandle vh_1d = vpi_handle_by_name((PLI_BYTE8*)"t.quads[2]", nullptr);
CHECK_RESULT_NZ(vh_1d);
CHECK_RESULT(vpi_get(vpiType, vh_1d), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_1d), 62);
TestVpiHandle vh_2d = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[1][3]", nullptr);
CHECK_RESULT_NZ(vh_2d);
CHECK_RESULT(vpi_get(vpiSize, vh_2d), 8);
vpi_get_value(vh_2d, &v);
CHECK_RESULT(v.value.integer, 11);
TestVpiHandle vh_3d = vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1]", nullptr);
CHECK_RESULT_NZ(vh_3d);
CHECK_RESULT(vpi_get(vpiSize, vh_3d), 96);
vpi_get_value(vh_3d, &v);
CHECK_RESULT(v.value.integer, 7);
}
// Packed dimension indexing: quads[2] bit selection
{
TestVpiHandle vh_arr = vpi_handle_by_name((PLI_BYTE8*)"t.quads[2]", nullptr);
CHECK_RESULT_NZ(vh_arr);
TestVpiHandle vh_bit0 = vpi_handle_by_index(vh_arr, 0);
CHECK_RESULT_NZ(vh_bit0);
CHECK_RESULT(vpi_get(vpiType, vh_bit0), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_bit0), 1);
// Try to index into a single bit
TestVpiHandle vh_invalid = vpi_handle_by_index(vh_bit0, 0);
CHECK_RESULT_Z(vh_invalid);
TestVpiHandle vh_bit32 = vpi_handle_by_index(vh_arr, 32);
CHECK_RESULT_NZ(vh_bit32);
CHECK_RESULT(vpi_get(vpiSize, vh_bit32), 1);
// Out of range bit should fail
TestVpiHandle vh_oob = vpi_handle_by_index(vh_arr, 100);
CHECK_RESULT_Z(vh_oob);
}
// Multiple packed dimensions: multi_packed is [3:0][7:0] multi_packed[2:0]
{
TestVpiHandle vh1 = vpi_handle_by_name((PLI_BYTE8*)"t.multi_packed[1]", nullptr);
CHECK_RESULT_NZ(vh1);
CHECK_RESULT(vpi_get(vpiSize, vh1), 32); // 4*8
// Index into first packed dim -> 8-bit sub-word
TestVpiHandle vh2 = vpi_handle_by_index(vh1, 2);
CHECK_RESULT_NZ(vh2);
CHECK_RESULT(vpi_get(vpiSize, vh2), 8);
// Further into bit level
TestVpiHandle vh3 = vpi_handle_by_index(vh2, 3);
CHECK_RESULT_NZ(vh3);
CHECK_RESULT(vpi_get(vpiSize, vh3), 1);
}
// Partial indexing (not all unpacked dimensions)
{
// mem_2d[1] partially indexes -> remaining [0:7] array
TestVpiHandle vh_2d_part = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[1]", nullptr);
CHECK_RESULT_NZ(vh_2d_part);
CHECK_RESULT(vpi_get(vpiType, vh_2d_part), vpiRegArray);
CHECK_RESULT(vpi_get(vpiSize, vh_2d_part), 8);
TestVpiHandle vh_2d_elem = vpi_handle_by_index(vh_2d_part, 3);
CHECK_RESULT_NZ(vh_2d_elem);
CHECK_RESULT(vpi_get(vpiType, vh_2d_elem), vpiReg);
CHECK_RESULT(vpi_get(vpiSize, vh_2d_elem), 8);
// mem_3d[0] partially indexes -> remaining [1:0][0:1] = 2*2=4 elements
TestVpiHandle vh_3d_part = vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[0]", nullptr);
CHECK_RESULT_NZ(vh_3d_part);
CHECK_RESULT(vpi_get(vpiType, vh_3d_part), vpiRegArray);
CHECK_RESULT(vpi_get(vpiSize, vh_3d_part), 4);
}
// Invalid syntax in vpi_handle_by_name
{
// Trailing text after indices
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][0]bar", nullptr));
// Non-integer / non-decimal index values
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][abc]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0x2][3]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[-1][0]", nullptr));
// Structural bracket errors
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0[0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][1", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[[0]][1]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][1]]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d0][1]", nullptr));
// Whitespace in indices (unsupported)
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[ 0 ][ 1 ]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][1] ", nullptr));
// Plain identifier with whitespace before part-select (unsupported; only escaped
// identifiers may have whitespace before a trailing part-select)
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.\\escaped_inst[0] .sig [3:0]", nullptr));
// Indexing non-array signals
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.onebit[0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.twoone[0]", nullptr));
// Part-select on unpacked-only array
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.unpacked_only[3:0]", nullptr));
// Range/slice syntax in non-last position or on unpacked dimensions
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][1:3]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0:2][0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0:2][1:4]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0+:2][0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0-:2][0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0:2]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[0:1][0][0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[0][0:1][0]", nullptr));
// Part-select with remaining unpacked dims (not fully indexed)
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[0][7:0]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[0][0][7:0]", nullptr));
// Part-select out of range: mem_2d[0][0] is 8 bits [7:0]
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][0][15:8]", nullptr));
CHECK_RESULT_Z(vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[0][0][8:7]", nullptr));
}
// Bit-range part-select via vpi_handle_by_name
{
// Descending-range element: mem_2d[3][0] = 8'(((3 * 8) + 0)) = 24 = 0x18
TestVpiHandle vh_desc_mid = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[3][0][7:4]", nullptr);
CHECK_RESULT_NZ(vh_desc_mid);
CHECK_RESULT(vpi_get(vpiSize, vh_desc_mid), 4);
vpi_get_value(vh_desc_mid, &v);
CHECK_RESULT(v.value.integer, 0x1); // [7:4] of 0x18
TestVpiHandle vh_desc_full
= vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[3][0][7:0]", nullptr);
CHECK_RESULT_NZ(vh_desc_full);
CHECK_RESULT(vpi_get(vpiSize, vh_desc_full), 8);
vpi_get_value(vh_desc_full, &v);
CHECK_RESULT(v.value.integer, 24); // 0x18
// Ascending packed range behavior is explicit:
// mem_3d has packed declaration [0:95], so [3:0] selects the MSB-end nibble,
// while [92:95] selects the LSB-end nibble where value 7 resides.
TestVpiHandle vh_asc_lsb
= vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1][92:95]", nullptr);
CHECK_RESULT_NZ(vh_asc_lsb);
CHECK_RESULT(vpi_get(vpiSize, vh_asc_lsb), 4);
vpi_get_value(vh_asc_lsb, &v);
CHECK_RESULT(v.value.integer, 7); // [92:95] of 0x...000007
// Select order [95:92] is also accepted and refers to the same bit set as [92:95].
TestVpiHandle vh_asc_lsb_rev
= vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1][95:92]", nullptr);
CHECK_RESULT_NZ(vh_asc_lsb_rev);
CHECK_RESULT(vpi_get(vpiSize, vh_asc_lsb_rev), 4);
vpi_get_value(vh_asc_lsb_rev, &v);
CHECK_RESULT(v.value.integer, 7);
TestVpiHandle vh_asc_mid
= vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1][90:94]", nullptr);
CHECK_RESULT_NZ(vh_asc_mid);
CHECK_RESULT(vpi_get(vpiSize, vh_asc_mid), 5);
vpi_get_value(vh_asc_mid, &v);
CHECK_RESULT(v.value.integer, 3); // 5-bit window containing 0b00011
TestVpiHandle vh_asc_mid_rev
= vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1][95:91]", nullptr);
CHECK_RESULT_NZ(vh_asc_mid_rev);
CHECK_RESULT(vpi_get(vpiSize, vh_asc_mid_rev), 5);
vpi_get_value(vh_asc_mid_rev, &v);
CHECK_RESULT(v.value.integer, 7);
// Cross-order select on ascending declaration is allowed and maps by declared indices.
TestVpiHandle vh_asc_msb
= vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1][3:0]", nullptr);
CHECK_RESULT_NZ(vh_asc_msb);
CHECK_RESULT(vpi_get(vpiSize, vh_asc_msb), 4);
vpi_get_value(vh_asc_msb, &v);
CHECK_RESULT(v.value.integer, 0); // [3:0] is MSB-end for [0:95]
// Part-select combined with array index: mem_2d[2][3] = 19 = 0x13
TestVpiHandle vh_2d_arr = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[2][3][3:0]", nullptr);
CHECK_RESULT_NZ(vh_2d_arr);
CHECK_RESULT(vpi_get(vpiSize, vh_2d_arr), 4);
vpi_get_value(vh_2d_arr, &v);
CHECK_RESULT(v.value.integer, 0x3);
// Equivalent ascending-order spelling of the MSB-end nibble
TestVpiHandle vh_3d_ps = vpi_handle_by_name((PLI_BYTE8*)"t.mem_3d[1][1][1][0:3]", nullptr);
CHECK_RESULT_NZ(vh_3d_ps);
CHECK_RESULT(vpi_get(vpiSize, vh_3d_ps), 4);
vpi_get_value(vh_3d_ps, &v);
CHECK_RESULT(v.value.integer, 0);
}
// Part-select write: write 0x2 to mem_2d[3][0][7:4], verify only those bits change
{
TestVpiHandle vh_ps = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[3][0][7:4]", nullptr);
CHECK_RESULT_NZ(vh_ps);
s_vpi_value put_val;
put_val.format = vpiIntVal;
put_val.value.integer = 0x2;
s_vpi_time time_s = {vpiSimTime, 0, 0, 0.0};
vpi_put_value(vh_ps, &put_val, &time_s, vpiNoDelay);
// Read back full element
TestVpiHandle vh_full = vpi_handle_by_name((PLI_BYTE8*)"t.mem_2d[3][0]", nullptr);
CHECK_RESULT_NZ(vh_full);
vpi_get_value(vh_full, &v);
CHECK_RESULT(v.value.integer, 0x28); // 0x18 with bits [7:4] changed to 0x2
// Restore original value
put_val.value.integer = 0x1;
vpi_put_value(vh_ps, &put_val, &time_s, vpiNoDelay);
}
return 0;
}
extern "C" int mon_check() {
// Callback from initial block in monitor
#ifdef TEST_VERBOSE
@ -1054,6 +1451,7 @@ extern "C" int mon_check() {
if (int status = _mon_check_string()) return status;
if (int status = _mon_check_putget_str(NULL)) return status;
if (int status = _mon_check_vlog_info()) return status;
if (int status = _mon_check_multi_index()) return status;
if (int status = _mon_check_delayed()) return status;
if (int status = _mon_check_too_big()) return status;
#ifndef IS_VPI

View File

@ -44,6 +44,14 @@ extern "C" int mon_check();
reg [31:0] half_count /*verilator public_flat_rd */ = 0;
reg [31:0] delayed /*verilator public_flat_rw */;
reg [31:0] delayed_mem [16] /*verilator public_flat_rw */;
reg [7:0] \escaped_with_brackets[3] /*verilator public_flat_rw */;
reg [7:0] mem_2d[3:0][7:0] /*verilator public_flat_rw */; // Descending indices
// verilator lint_off ASCRANGE
reg [0:95] mem_3d[0:1][1:0][0:1] /*verilator public_flat_rw */; // Mixed: asc, desc, asc
// verilator lint_on ASCRANGE
reg [3:0] [7:0] multi_packed[2:0] /*verilator public_flat_rw */;
reg unpacked_only[7:0];
reg [7:0] text_byte /*verilator public_flat_rw @(posedge clk) */;
reg [15:0] text_half /*verilator public_flat_rw @(posedge clk) */;
@ -91,9 +99,30 @@ extern "C" int mon_check();
long1 = 123;
real1 = 1.0;
str1 = "hello";
\escaped_with_brackets[3] = 8'h5a;
rev = 12'habc;
for (int i = 0; i < 4; i++) begin
for (int j = 0; j < 8; j++) begin
mem_2d[i][j] = 8'(((i * 8) + j));
end
end
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 2; j++) begin
for (int k = 0; k < 2; k++) begin
mem_3d[i][j][k] = 96'(((i * 4) + (j * 2) + k));
end
end
end
for (int i = 0; i < 3; i++) begin
for (int j = 0; j < 4; j++) begin
multi_packed[i][j] = 8'((i * 4) + j);
end
end
`ifdef VERILATOR
status = $c32("mon_check()");
`endif
@ -147,6 +176,8 @@ extern "C" int mon_check();
end
endgenerate
arr #(.LENGTH(8)) \escaped.inst[0] ();
endmodule : t
module sub;
@ -164,6 +195,7 @@ module arr;
reg [LENGTH-1:0] sig /*verilator public_flat_rw*/;
reg [LENGTH-1:0] rfr /*verilator public_flat_rw*/;
reg [LENGTH-1:0] \escaped_sig[1] /*verilator public_flat_rw*/;
reg check /*verilator public_flat_rw*/;
reg verbose /*verilator public_flat_rw*/;
@ -171,6 +203,7 @@ module arr;
initial begin
sig = {LENGTH{1'b0}};
rfr = {LENGTH{1'b0}};
\escaped_sig[1] = {LENGTH{1'b0}};
end
always @(posedge check) begin

View File

@ -59,6 +59,15 @@ extern "C" int mon_check();
/*verilator public_flat_rw_on*/
reg [31:0] delayed;
reg [31:0] delayed_mem [16];
reg [7:0] \escaped_with_brackets[3] ;
reg [7:0] mem_2d[3:0][7:0]; // Descending indices
// verilator lint_off ASCRANGE
reg [0:95] mem_3d[0:1][1:0][0:1]; // Mixed: asc, desc, asc
// verilator lint_on ASCRANGE
// Signal with multiple packed dimensions
reg [3:0] [7:0] multi_packed[2:0];
reg unpacked_only[7:0];
/*verilator public_off*/
reg invisible2;
@ -108,9 +117,30 @@ extern "C" int mon_check();
long1 = 123;
real1 = 1.0;
str1 = "hello";
\escaped_with_brackets[3] = 8'h5a;
rev = 12'habc;
for (int i = 0; i < 4; i++) begin
for (int j = 0; j < 8; j++) begin
mem_2d[i][j] = 8'(((i * 8) + j));
end
end
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 2; j++) begin
for (int k = 0; k < 2; k++) begin
mem_3d[i][j][k] = 96'(((i * 4) + (j * 2) + k));
end
end
end
for (int i = 0; i < 3; i++) begin
for (int j = 0; j < 4; j++) begin
multi_packed[i][j] = 8'((i * 4) + j);
end
end
`ifdef VERILATOR
status = $c32("mon_check()");
`endif
@ -164,6 +194,8 @@ extern "C" int mon_check();
end
endgenerate
arr #(.LENGTH(8)) \escaped.inst[0] ();
endmodule : t
module sub;
@ -182,6 +214,7 @@ module arr;
/*verilator public_flat_rw_on*/
reg [LENGTH-1:0] sig;
reg [LENGTH-1:0] rfr;
reg [LENGTH-1:0] \escaped_sig[1] /*verilator public_flat_rw*/;
reg check;
reg verbose;

View File

@ -44,6 +44,15 @@ extern "C" int mon_check();
reg [31:0] half_count;
reg [31:0] delayed;
reg [31:0] delayed_mem [16];
reg [7:0] \escaped_with_brackets[3] ;
reg [7:0] mem_2d[3:0][7:0]; // Descending indices
// verilator lint_off ASCRANGE
reg [0:95] mem_3d[0:1][1:0][0:1]; // Mixed: asc, desc, asc
// verilator lint_on ASCRANGE
// Signal with multiple packed dimensions
reg [3:0] [7:0] multi_packed[2:0];
reg unpacked_only[7:0];
reg [7:0] text_byte;
reg [15:0] text_half;
@ -88,9 +97,30 @@ extern "C" int mon_check();
long1 = 123;
real1 = 1.0;
str1 = "hello";
\escaped_with_brackets[3] = 8'h5a;
rev = 12'habc;
for (int i = 0; i < 4; i++) begin
for (int j = 0; j < 8; j++) begin
mem_2d[i][j] = 8'(((i * 8) + j));
end
end
for (int i = 0; i < 2; i++) begin
for (int j = 0; j < 2; j++) begin
for (int k = 0; k < 2; k++) begin
mem_3d[i][j][k] = 96'(((i * 4) + (j * 2) + k));
end
end
end
for (int i = 0; i < 3; i++) begin
for (int j = 0; j < 4; j++) begin
multi_packed[i][j] = 8'((i * 4) + j);
end
end
`ifdef VERILATOR
status = $c32("mon_check()");
`endif
@ -144,6 +174,8 @@ extern "C" int mon_check();
end
endgenerate
arr #(.LENGTH(8)) \escaped.inst[0] ();
endmodule : t
module sub;
@ -161,6 +193,7 @@ module arr;
reg [LENGTH-1:0] sig;
reg [LENGTH-1:0] rfr;
reg [LENGTH-1:0] \escaped_sig[1] /*verilator public_flat_rw*/;
reg check;
reg verbose;