Fix constrained random for > 64-bit associative arrays (#5670) (#5682)

This commit is contained in:
Yilou Wang 2025-01-09 14:33:38 +01:00 committed by GitHub
parent 44f49669a3
commit 0380a36c76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 312 additions and 82 deletions

View File

@ -282,6 +282,7 @@ std::string parseNestedSelect(const std::string& nested_select_expr,
indices.push_back(idx);
return name;
}
//======================================================================
// VlRandomizer:: Methods
@ -467,10 +468,19 @@ bool VlRandomizer::parseSolution(std::iostream& f) {
"hex_index contains invalid format");
continue;
}
const long long index = std::stoll(hex_index.substr(start + 2), nullptr, 16);
oss << "[" << index << "]";
std::string trimmed_hex = hex_index.substr(start + 2);
if (trimmed_hex.size() <= 8) { // Small numbers: <= 32 bits
// Convert to decimal and output directly
oss << "[" << std::to_string(std::stoll(trimmed_hex, nullptr, 16)) << "]";
} else { // Large numbers: > 32 bits
// Trim leading zeros and handle empty case
trimmed_hex.erase(0, trimmed_hex.find_first_not_of('0'));
oss << "[" << (trimmed_hex.empty() ? "0" : trimmed_hex) << "]";
}
}
const std::string indexed_name = oss.str();
const auto it = std::find_if(m_arr_vars.begin(), m_arr_vars.end(),
[&indexed_name](const auto& entry) {
return entry.second->m_name == indexed_name;

View File

@ -27,8 +27,10 @@
#include "verilated.h"
#include <iomanip>
#include <iostream>
#include <ostream>
#include <sstream>
//=============================================================================
// VlRandomExpr and subclasses represent expressions for the constraint solver.
@ -38,10 +40,10 @@ public:
m_name; // Name of the array variable, including index notation (e.g., arr[2][1])
void* const m_datap; // Reference to the array variable data
const int m_index; // Flattened (1D) index of the array element
const std::vector<size_t> m_indices; // Multi-dimensional indices of the array element
const std::vector<IData> m_indices; // Multi-dimensional indices of the array element
const std::vector<size_t> m_idxWidths; // Multi-dimensional indices' bit widths
ArrayInfo(const std::string& name, void* datap, int index, const std::vector<size_t>& indices,
ArrayInfo(const std::string& name, void* datap, int index, const std::vector<IData>& indices,
const std::vector<size_t>& idxWidths)
: m_name(name)
, m_datap(datap)
@ -110,16 +112,31 @@ public:
return nullptr;
}
}
void emitSelect(std::ostream& s, const std::vector<size_t>& indices,
void emitHexs(std::ostream& s, const std::vector<IData>& indices, const size_t bit_width,
size_t idx) const {
for (int j = bit_width - 4; j >= 0; j -= 4) {
s << "0123456789abcdef"[(indices[idx] >> j) & 0xf];
}
}
void emitSelect(std::ostream& s, const std::vector<IData>& indices,
const std::vector<size_t>& idxWidths) const {
for (size_t idx = 0; idx < indices.size(); ++idx) s << "(select ";
const size_t num_indices = idxWidths.size();
size_t wide_size = 0;
for (size_t idx = 0; idx < num_indices; ++idx) s << "(select ";
s << name();
for (size_t idx = 0; idx < indices.size(); ++idx) {
s << " #x";
for (size_t idx = 0; idx < num_indices; ++idx) {
const size_t bit_width = idxWidths[idx];
for (int j = bit_width - 4; j >= 0; j -= 4) {
s << "0123456789abcdef"[(indices[idx] >> j) & 0xf];
s << " #x";
const size_t emit_count = (bit_width > 32) ? (idxWidths[idx] / 32) : 1;
for (size_t i = 0; i < emit_count; ++i) {
emitHexs(s, indices, (bit_width > 32) ? 32 : bit_width, wide_size + i);
}
wide_size += (idxWidths[idx] > 32) ? (idxWidths[idx] / 32) : 1;
s << ")";
}
}
@ -129,7 +146,7 @@ public:
const std::string indexed_name = name() + std::to_string(i);
const auto it = m_arrVarsRefp->find(indexed_name);
if (it != m_arrVarsRefp->end()) {
const std::vector<size_t>& indices = it->second->m_indices;
const std::vector<IData>& indices = it->second->m_indices;
const std::vector<size_t>& idxWidths = it->second->m_idxWidths;
emitSelect(s, indices, idxWidths);
} else {
@ -165,7 +182,7 @@ public:
const std::string indexed_name = name() + std::to_string(j);
const auto it = m_arrVarsRefp->find(indexed_name);
if (it != m_arrVarsRefp->end()) {
const std::vector<size_t>& indices = it->second->m_indices;
const std::vector<IData>& indices = it->second->m_indices;
const std::vector<size_t>& idxWidths = it->second->m_idxWidths;
emitSelect(s, indices, idxWidths);
} else {
@ -176,7 +193,6 @@ public:
};
//=============================================================================
// VlRandomizer is the object holding constraints and variable references.
class VlRandomizer final {
// MEMBERS
std::vector<std::string> m_constraints; // Solver-dependent constraints
@ -200,45 +216,89 @@ public:
bool next(VlRNG& rngr);
template <typename T_Key>
typename std::enable_if<std::is_integral<T_Key>::value>::type
process_key(const T_Key& key, std::string& indexed_name, size_t& integral_index,
typename std::enable_if<std::is_integral<T_Key>::value && (sizeof(T_Key) <= 4)>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& integral_index,
const std::string& base_name, size_t& idx_width) {
integral_index = static_cast<size_t>(key);
indexed_name = base_name + "[" + std::to_string(integral_index) + "]";
integral_index.push_back(static_cast<size_t>(key));
indexed_name
= base_name + "[" + std::to_string(integral_index[integral_index.size() - 1]) + "]";
idx_width = sizeof(T_Key) * 8;
}
template <typename T_Key>
typename std::enable_if<std::is_same<T_Key, std::string>::value>::type
process_key(const T_Key& key, std::string& indexed_name, size_t& integral_index,
typename std::enable_if<std::is_integral<T_Key>::value && (sizeof(T_Key) > 4)>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& integral_index,
const std::string& base_name, size_t& idx_width) {
integral_index = string_to_integral(key);
indexed_name = base_name + "[" + std::to_string(integral_index) + "]";
idx_width = 64; // 64-bit mask
constexpr size_t segment_bits = 32;
constexpr T_Key mask = (static_cast<T_Key>(1) << segment_bits) - 1;
integral_index.push_back(static_cast<size_t>(key >> segment_bits));
integral_index.push_back(static_cast<size_t>(key & mask));
std::ostringstream hex_stream;
hex_stream << std::hex << key;
std::string index_string = hex_stream.str();
index_string.erase(0, index_string.find_first_not_of('0'));
index_string = index_string.empty() ? "0" : index_string;
indexed_name = base_name + "[" + index_string + "]";
idx_width = sizeof(T_Key) * 8;
}
template <typename T_Key>
typename std::enable_if<VlIsVlWide<T_Key>::value>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& integral_index,
const std::string& base_name, size_t& idx_width) {
std::ostringstream hex_stream;
for (size_t i = key.size(); i > 0; --i) {
const size_t segment_value = key.at(i - 1);
hex_stream << std::hex << segment_value;
integral_index.push_back(segment_value);
}
std::string index_string = hex_stream.str();
index_string.erase(0, index_string.find_first_not_of('0'));
index_string = index_string.empty() ? "0" : index_string;
indexed_name = base_name + "[" + index_string + "]";
idx_width = key.size() * 32;
}
template <typename T_Key>
typename std::enable_if<std::is_same<T_Key, std::string>::value>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& integral_index,
const std::string& base_name, size_t& idx_width) {
// Convert the input string to its ASCII hexadecimal representation
std::ostringstream oss;
for (unsigned char c : key) {
oss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c);
}
std::string hex_str = oss.str();
// Ensure the hex string is exactly 128 bits (32 hex characters)
hex_str = hex_str.size() > 32 ? hex_str.substr(0, 32)
: std::string(32 - hex_str.size(), '0') + hex_str;
// Split the hex string into 4 segments (32-bit per segment)
integral_index.clear();
for (size_t i = 0; i < hex_str.size(); i += 8) {
integral_index.push_back(std::stoul(hex_str.substr(i, 8), nullptr, 16));
}
indexed_name = base_name + "["
+ (hex_str.find_first_not_of('0') == std::string::npos
? "0"
: hex_str.substr(hex_str.find_first_not_of('0')))
+ "]";
idx_width = 128;
}
template <typename T_Key>
typename std::enable_if<!std::is_integral<T_Key>::value
&& !std::is_same<T_Key, std::string>::value>::type
process_key(const T_Key& key, std::string& indexed_name, size_t& integral_index,
&& !std::is_same<T_Key, std::string>::value
&& !VlIsVlWide<T_Key>::value>::type
process_key(const T_Key& key, std::string& indexed_name, std::vector<size_t>& integral_index,
const std::string& base_name, size_t& idx_width) {
VL_FATAL_MT(__FILE__, __LINE__, "randomize",
"Unsupported: Only integral and string index of associative array is "
"supported currently.");
}
uint64_t string_to_integral(const std::string& str) {
uint64_t result = 0;
for (char c : str) { result = (result << 8) | static_cast<uint64_t>(c); }
#ifdef VL_DEBUG
if (seen_values.count(result) > 0 && seen_values[result] != str)
VL_WARN_MT(__FILE__, __LINE__, "randomize",
"Conflict detected: Different strings mapped to the same 64-bit index.");
seen_values[result] = str;
#endif
return result;
}
template <typename T>
void write_var(T& var, int width, const char* name, int dimension,
std::uint32_t randmodeIdx = std::numeric_limits<std::uint32_t>::max()) {
@ -296,14 +356,14 @@ public:
template <typename T>
void record_arr_table(T& var, const std::string name, int dimension,
std::vector<size_t> indices, std::vector<size_t> idxWidths) {
std::vector<IData> indices, std::vector<size_t> idxWidths) {
const std::string key = generateKey(name, idx);
m_arr_vars[key] = std::make_shared<ArrayInfo>(name, &var, idx, indices, idxWidths);
++idx;
}
template <typename T>
void record_arr_table(VlQueue<T>& var, const std::string name, int dimension,
std::vector<size_t> indices, std::vector<size_t> idxWidths) {
std::vector<IData> indices, std::vector<size_t> idxWidths) {
if ((dimension > 0) && (var.size() != 0)) {
idxWidths.push_back(32);
for (size_t i = 0; i < var.size(); ++i) {
@ -316,7 +376,7 @@ public:
}
template <typename T, std::size_t N_Depth>
void record_arr_table(VlUnpacked<T, N_Depth>& var, const std::string name, int dimension,
std::vector<size_t> indices, std::vector<size_t> idxWidths) {
std::vector<IData> indices, std::vector<size_t> idxWidths) {
if ((dimension > 0) && (N_Depth != 0)) {
idxWidths.push_back(32);
for (size_t i = 0; i < N_Depth; ++i) {
@ -330,20 +390,27 @@ public:
}
template <typename T_Key, typename T_Value>
void record_arr_table(VlAssocArray<T_Key, T_Value>& var, const std::string name, int dimension,
std::vector<size_t> indices, std::vector<size_t> idxWidths) {
std::vector<IData> indices, std::vector<size_t> idxWidths) {
if ((dimension > 0) && (var.size() != 0)) {
for (auto it = var.begin(); it != var.end(); ++it) {
const T_Key& key = it->first;
const T_Value& value = it->second;
std::string indexed_name;
size_t integral_index;
size_t idx_width;
std::vector<size_t> integral_index;
size_t idx_width = 0;
process_key(key, indexed_name, integral_index, name, idx_width);
// Update indices and widths
idxWidths.push_back(idx_width);
indices.push_back(integral_index);
indices.insert(indices.end(), integral_index.begin(), integral_index.end());
record_arr_table(var.at(key), indexed_name, dimension - 1, indices, idxWidths);
// Cleanup indices and widths
idxWidths.pop_back();
indices.pop_back();
indices.resize(indices.size() - integral_index.size());
}
}
}

View File

@ -451,6 +451,7 @@ struct VlWide final {
// METHODS
const EData& at(size_t index) const { return m_storage[index]; }
EData& at(size_t index) { return m_storage[index]; }
size_t size() const { return N_Words; }
WData* data() { return &m_storage[0]; }
const WData* data() const { return &m_storage[0]; }
bool operator<(const VlWide<N_Words>& rhs) const {

View File

@ -710,15 +710,19 @@ class ConstraintExprVisitor final : public VNVisitor {
if (editFormat(nodep)) return;
FileLine* const fl = nodep->fileline();
if (VN_IS(nodep->bitp(), CvtPackString)) {
// Extract and truncate the string index to fit within 64 bits
AstCvtPackString* const stringp = VN_AS(nodep->bitp(), CvtPackString);
const size_t stringSize = VN_AS(stringp->lhsp(), Const)->width();
if (stringSize > 128) {
stringp->v3warn(
CONSTRAINTIGN,
"Unsupported: Constrained randomization of associative array keys of "
<< stringSize << "bits, limit is 128 bits");
}
VNRelinker handle;
AstNodeExpr* const strIdxp = new AstSFormatF{
fl, "#x%16x", false,
new AstAnd{fl, stringp->lhsp()->unlinkFrBack(&handle),
new AstConst(fl, AstConst::Unsized64{}, 0xFFFFFFFFFFFFFFFF)}};
handle.relink(strIdxp);
editSMT(nodep, nodep->fromp(), strIdxp);
AstNodeExpr* const idxp
= new AstSFormatF{fl, "#x%32x", false, stringp->lhsp()->unlinkFrBack(&handle)};
handle.relink(idxp);
editSMT(nodep, nodep->fromp(), idxp);
} else {
VNRelinker handle;
const int actual_width = nodep->bitp()->width();
@ -728,16 +732,10 @@ class ConstraintExprVisitor final : public VNVisitor {
fmt = "#x%2x";
} else if (actual_width <= 16) {
fmt = "#x%4x";
} else if (actual_width <= 32) {
fmt = "#x%8x";
} else if (actual_width <= 64) {
fmt = "#x%16x";
} else {
nodep->v3warn(CONSTRAINTIGN,
"Unsupported: Associative array index "
"widths of more than 64 bits during constraint randomization.");
return;
fmt = "#x%" + std::to_string(VL_WORDS_I(actual_width) * 8) + "x";
}
AstNodeExpr* const idxp
= new AstSFormatF{fl, fmt, false, nodep->bitp()->unlinkFrBack(&handle)};
handle.relink(idxp);
@ -1450,11 +1448,8 @@ class RandomizeVisitor final : public VNVisitor {
AstNodeStmt* stmtsp = nullptr;
auto createLoopIndex = [&](AstNodeDType* tempDTypep) {
if (VN_IS(tempDTypep, AssocArrayDType)) {
return new AstVar{
fl, VVarType::VAR, uniqueNamep->get(""),
dtypep->findBasicDType(
((AstBasicDType*)VN_AS(tempDTypep, AssocArrayDType)->keyDTypep())
->keyword())};
return new AstVar{fl, VVarType::VAR, uniqueNamep->get(""),
VN_AS(tempDTypep, AssocArrayDType)->keyDTypep()};
}
return new AstVar{fl, VVarType::VAR, uniqueNamep->get(""),
dtypep->findBasicDType(VBasicDTypeKwd::UINT32)};

View File

@ -1,9 +1,6 @@
%Warning-CONSTRAINTIGN: t/t_constraint_assoc_arr_bad.v:14:22: Unsupported: Associative array index widths of more than 64 bits during constraint randomization.
14 | bit_index_arr[79'd66] == 65;
| ^
%Warning-CONSTRAINTIGN: t/t_constraint_assoc_arr_bad.v:12:20: Unsupported: Constrained randomization of associative array keys of 144bits, limit is 128 bits
12 | string_arr["a_very_long_string"] == 65;
| ^~~~~~~~~~~~~~~~~~~~
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
%Warning-CONSTRAINTIGN: t/t_constraint_assoc_arr_bad.v:15:24: Unsupported: Associative array index widths of more than 64 bits during constraint randomization.
15 | logic_index_arr[65'd3] == 70;
| ^
%Error: Exiting due to

10
test_regress/t/t_constraint_assoc_arr_bad.v Normal file → Executable file
View File

@ -4,19 +4,15 @@
// any use, without warranty, 2024 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class AssocArrayWarningTest;
rand int bit_index_arr [bit[78:0]];
rand int logic_index_arr [logic[64:0]];
rand int string_arr [string];
constraint c {
bit_index_arr[79'd66] == 65;
logic_index_arr[65'd3] == 70;
string_arr["a_very_long_string"] == 65;
}
function new();
bit_index_arr = '{79'd66:0};
logic_index_arr = '{65'd3:0};
string_arr["a_very_long_string"] = 0;
endfunction
endclass

View File

@ -147,11 +147,33 @@ class constrained_2d_associative_array;
/* verilator lint_off SIDEEFFECT */
endclass
class AssocArray64bitMoreTest;
rand int bit_index_arr [bit[78:0]];
rand int logic_index_arr [logic[64:0]];
constraint c {
bit_index_arr[79'd66] == 65;
logic_index_arr[65'd3] == 70;
}
function new();
bit_index_arr = '{79'd66:0};
logic_index_arr = '{65'd3:0};
endfunction
function void self_check();
if (bit_index_arr[79'd66] != 65) $stop;
if (logic_index_arr[65'd3] != 70) $stop;
endfunction
endclass
module t_constraint_assoc_arr_basic;
constrained_associative_array_basic my_array;
constrained_1d_associative_array my_1d_array;
constrained_2d_associative_array my_2d_array;
AssocArray64bitMoreTest test_obj;
int success;
initial begin
@ -165,10 +187,16 @@ module t_constraint_assoc_arr_basic;
if (success == 0) $stop;
my_1d_array.self_check();
my_1d_array = new();
success = my_1d_array.randomize();
my_2d_array = new();
success = my_2d_array.randomize();
if (success == 0) $stop;
my_1d_array.self_check();
my_2d_array.self_check();
test_obj = new();
repeat(2) begin
success = test_obj.randomize();
if (success != 1) $stop;
end
// my_1d_array.debug_display();
// my_2d_array.debug_display();

View File

@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2024 by Wilson Snyder. This program is free software; you
# can redistribute it and/or modify it under the terms of either the GNU
# Lesser General Public License Version 3 or the Perl Artistic License
# Version 2.0.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,115 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2025 by PlanV GmbH.
// SPDX-License-Identifier: CC0-1.0
class AssocIntegralWide;
rand bit [31:0] assoc_array[bit[64:0]];
rand bit [31:0] assoc_array_128[bit[128:0]];
rand bit [31:0] assoc_array_2d[bit[64:0]][bit[128:0]];
constraint valid_entries {
assoc_array[65'd6] == 32'd8;
assoc_array[65'h1FFFFFFFFFFFFFFFF] == 32'hDEADBEEF;
assoc_array_128[129'd6] == 32'd16;
assoc_array_128[129'h1FFFFFFFFFFFFFFFFFFFFFFFF] == 32'hCAFEBABE;
assoc_array_2d[65'd6][129'd6] == 32'd32;
assoc_array_2d[65'h1FFFFFFFFFFFFFFFF][129'h1FFFFFFFFFFFFFFFFFFFFFFFF] == 32'hBADF00D;
}
// Constructor to initialize arrays
function new();
assoc_array[65'd0] = 32'd0;
assoc_array[65'd6] = 32'd0;
assoc_array[65'h1FFFFFFFFFFFFFFFF] = 32'd0;
assoc_array_128[129'd0] = 32'd0;
assoc_array_128[129'd6] = 32'd0;
assoc_array_128[129'h1FFFFFFFFFFFFFFFFFFFFFFFF] = 32'd0;
assoc_array_2d[65'd6][129'd6] = 32'd0;
assoc_array_2d[65'h1FFFFFFFFFFFFFFFF][129'h1FFFFFFFFFFFFFFFFFFFFFFFF] = 32'd0;
endfunction
// Self-check function to verify constraints
function void self_check();
if (assoc_array[65'd6] != 32'd8)
$stop;
if (assoc_array[65'h1FFFFFFFFFFFFFFFF] != 32'hDEADBEEF)
$stop;
if (assoc_array_128[129'd6] != 32'd16)
$stop;
if (assoc_array_128[129'h1FFFFFFFFFFFFFFFFFFFFFFFF] != 32'hCAFEBABE)
$stop;
if (assoc_array_2d[65'd6][129'd6] != 32'd32)
$stop;
if (assoc_array_2d[65'h1FFFFFFFFFFFFFFFF][129'h1FFFFFFFFFFFFFFFFFFFFFFFF] != 32'hBADF00D)
$stop;
endfunction
endclass
class AssocStringWide;
rand bit [31:0] array_32[string];
rand bit [31:0] array_64[string];
rand bit [31:0] array_96[string];
rand bit [31:0] array_128[string];
constraint valid_entries {
// <= 32 bits
array_32["pv"] == 32'd10;
// > 32 and <= 64 bits
array_64["verilog"] == 32'd20;
// > 32 and <= 64 bits
array_96["verilator"] == 32'd30;
// > 64 and <= 96 bits
array_128["systemverilog"] == 32'd40;
}
function new();
array_32["pv"] = 32'd0;
array_64["verilog"] = 32'd0;
array_96["verilator"] = 32'd0;
array_128["systemverilog"] = 32'd0;
endfunction
function void self_check();
if (array_32["pv"] != 32'd10) $stop;
if (array_64["verilog"] != 32'd20) $stop;
if (array_96["verilator"] != 32'd30) $stop;
if (array_128["systemverilog"] != 32'd40) $stop;
endfunction
endclass
module t_constraint_assoc_arr_wide;
AssocIntegralWide integral_wide;
AssocStringWide string_wide;
int success;
initial begin
integral_wide = new();
string_wide = new();
success = integral_wide.randomize();
if (success != 1) $stop;
integral_wide.self_check();
success = string_wide.randomize();
if (success != 1) $stop;
string_wide.self_check();
$write("*-* All Finished *-*\n");
$finish;
end
endmodule