diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 324e6cdbe..13c6a058a 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -22,6 +22,7 @@ #include "verilated_random.h" +#include #include #include #include @@ -281,37 +282,6 @@ std::string parseNestedSelect(const std::string& nested_select_expr, indices.push_back(idx); return name; } - -std::string flattenIndices(const std::vector& indices, const VlRandomVar* const var) { - int flattenedIndex = 0; - int multiplier = 1; - for (int i = indices.size() - 1; i >= 0; --i) { - int indexValue = 0; - std::string trimmedIndex = indices[i]; - - trimmedIndex.erase(0, trimmedIndex.find_first_not_of(" \t")); - trimmedIndex.erase(trimmedIndex.find_last_not_of(" \t") + 1); - - if (trimmedIndex.find("#x") == 0) { - indexValue = std::strtoul(trimmedIndex.substr(2).c_str(), nullptr, 16); - } else if (trimmedIndex.find("#b") == 0) { - indexValue = std::strtoul(trimmedIndex.substr(2).c_str(), nullptr, 2); - } else { - indexValue = std::strtoul(trimmedIndex.c_str(), nullptr, 10); - } - const int length = var->getLength(i); - if (length == -1) { - VL_WARN_MT(__FILE__, __LINE__, "randomize", - "Internal: Wrong Call: Only RandomArray can call getLength()"); - break; - } - flattenedIndex += indexValue * multiplier; - multiplier *= length; - } - std::string hexString = std::to_string(flattenedIndex); - while (hexString.size() < 8) { hexString.insert(0, "0"); } - return "#x" + hexString; -} //====================================================================== // VlRandomizer:: Methods @@ -404,7 +374,11 @@ bool VlRandomizer::next(VlRNG& rngr) { f << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n"; f << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n"; for (const auto& var : m_vars) { - f << "(declare-fun " << var.second->name() << " () "; + if (var.second->dimension() > 0) { + auto arrVarsp = std::make_shared(m_arr_vars); + var.second->setArrayInfo(arrVarsp); + } + f << "(declare-fun " << var.first << " () "; var.second->emitType(f); f << ")\n"; } @@ -444,9 +418,14 @@ bool VlRandomizer::parseSolution(std::iostream& f) { } f << "(get-value ("; - for (const auto& var : m_vars) var.second->emitGetValue(f); + for (const auto& var : m_vars) { + if (var.second->dimension() > 0) { + auto arrVarsp = std::make_shared(m_arr_vars); + var.second->setArrayInfo(arrVarsp); + } + var.second->emitGetValue(f); + } f << "))\n"; - // Quasi-parse S-expression of the form ((x #xVALUE) (y #bVALUE) (z #xVALUE)) char c; f >> c; @@ -455,7 +434,6 @@ bool VlRandomizer::parseSolution(std::iostream& f) { "Internal: Unable to parse solver's response: invalid S-expression"); return false; } - while (true) { f >> c; if (c == ')') break; @@ -471,7 +449,6 @@ bool VlRandomizer::parseSolution(std::iostream& f) { if (name == "(select") { const std::string selectExpr = readUntilBalanced(f); name = parseNestedSelect(selectExpr, indices); - idx = indices[0]; } std::getline(f, value, ')'); const auto it = m_vars.find(name); @@ -480,12 +457,34 @@ bool VlRandomizer::parseSolution(std::iostream& f) { if (m_randmode && !varr.randModeIdxNone()) { if (!(m_randmode->at(varr.randModeIdx()))) continue; } - if (indices.size() > 1) { - const std::string flattenedIndex = flattenIndices(indices, &varr); - varr.set(flattenedIndex, value); - } else { - varr.set(idx, value); + if (!indices.empty()) { + std::ostringstream oss; + oss << varr.name(); + for (const auto& hex_index : indices) { + const size_t start = hex_index.find_first_not_of(" "); + if (start == std::string::npos || hex_index.substr(start, 2) != "#x") { + VL_FATAL_MT(__FILE__, __LINE__, "randomize", + "Error: hex_index contains invalid format"); + continue; + } + const int index = std::stoi(hex_index.substr(start + 2), nullptr, 16); + oss << "[" << index << "]"; + } + 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; + }); + if (it != m_arr_vars.end()) { + std::ostringstream ss; + ss << "#x" << std::hex << std::setw(8) << std::setfill('0') << it->second->m_index; + idx = ss.str(); + } else { + VL_FATAL_MT(__FILE__, __LINE__, "randomize", + "Error: indexed_name not found in m_arr_vars"); + } } + varr.set(idx, value); } return true; } diff --git a/include/verilated_random.h b/include/verilated_random.h index 2ae2dcb22..d679f3e0e 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -27,10 +27,25 @@ #include "verilated.h" +#include #include - //============================================================================= // VlRandomExpr and subclasses represent expressions for the constraint solver. +class ArrayInfo final { +public: + const std::string + 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 m_indices; // Multi-dimensional indices of the array element + + ArrayInfo(const std::string& name, void* datap, int index, const std::vector& indices) + : m_name(name) + , m_datap(datap) + , m_index(index) + , m_indices(indices) {} +}; +using ArrayInfoMap = std::map>; class VlRandomVar VL_NOT_FINAL { const char* const m_name; // Variable name @@ -58,7 +73,22 @@ public: virtual void emitExtract(std::ostream& s, int i) const; virtual void emitType(std::ostream& s) const; virtual int totalWidth() const; - virtual int getLength(int dimension) const { return -1; } + mutable std::shared_ptr m_arrVarsRefp; + void setArrayInfo(const std::shared_ptr& arrVarsRefp) const { + m_arrVarsRefp = arrVarsRefp; + } + mutable std::map count_cache; + int countMatchingElements(const ArrayInfoMap& arr_vars, const std::string& base_name) const { + if (VL_LIKELY(count_cache.find(base_name) != count_cache.end())) + return count_cache[base_name]; + int count = 0; + for (int index = 0; arr_vars.find(base_name + std::to_string(index)) != arr_vars.end(); + ++index) { + ++count; + } + count_cache[base_name] = count; + return count; + } }; template @@ -68,52 +98,12 @@ public: std::uint32_t randModeIdx) : VlRandomVar{name, width, datap, dimension, randModeIdx} {} void* datap(int idx) const override { + const std::string indexed_name = name() + std::to_string(idx); + const auto it = m_arrVarsRefp->find(indexed_name); + if (it != m_arrVarsRefp->end()) return it->second->m_datap; return &static_cast(VlRandomVar::datap(idx))->atWrite(idx); } - void emitSelect(std::ostream& s, int i) const { - s << " (select " << name() << " #x"; - for (int j = 28; j >= 0; j -= 4) s << "0123456789abcdef"[(i >> j) & 0xf]; - s << ')'; - } - void emitGetValue(std::ostream& s) const override { - const int length = static_cast(VlRandomVar::datap(0))->size(); - for (int i = 0; i < length; i++) emitSelect(s, i); - } - void emitType(std::ostream& s) const override { - s << "(Array (_ BitVec 32) (_ BitVec " << width() << "))"; - } - int totalWidth() const override { - const int length = static_cast(VlRandomVar::datap(0))->size(); - return width() * length; - } - void emitExtract(std::ostream& s, int i) const override { - const int j = i / width(); - i = i % width(); - s << " ((_ extract " << i << ' ' << i << ')'; - emitSelect(s, j); - s << ')'; - } -}; - -template -class VlRandomArrayVar final : public VlRandomVar { -public: - VlRandomArrayVar(const char* name, int width, void* datap, int dimension, - std::uint32_t randModeIdx) - : VlRandomVar{name, width, datap, dimension, randModeIdx} {} - - void* datap(int idx) const override { - if (idx < 0) return &static_cast(VlRandomVar::datap(0))->operator[](0); - std::vector indices(dimension()); - for (int dim = dimension() - 1; dim >= 0; --dim) { - const int length = getLength(dim); - indices[dim] = idx % length; - idx /= length; - } - return &static_cast(VlRandomVar::datap(0))->find_element(indices); - } - - void emitSelect(std::ostream& s, const std::vector& indices) const { + void emitSelect(std::ostream& s, const std::vector& indices) const { for (size_t idx = 0; idx < indices.size(); ++idx) s << "(select "; s << name(); for (size_t idx = 0; idx < indices.size(); ++idx) { @@ -124,33 +114,17 @@ public: s << ")"; } } - - int getLength(int dimension) const override { - const auto var = static_cast(datap(-1)); - const int lenth = var->find_length(dimension); - return lenth; - } - void emitGetValue(std::ostream& s) const override { - const int total_dimensions = dimension(); - std::vector lengths; - for (int dim = 0; dim < total_dimensions; dim++) { - const int len = getLength(dim); - lengths.push_back(len); - } - std::vector indices(total_dimensions, 0); - while (true) { - emitSelect(s, indices); - int currentDimension = total_dimensions - 1; - while (currentDimension >= 0 - && ++indices[currentDimension] >= lengths[currentDimension]) { - indices[currentDimension] = 0; - --currentDimension; + const int elementCounts = countMatchingElements(*m_arrVarsRefp, name()); + for (int i = 0; i < elementCounts; i++) { + 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& indices = it->second->m_indices; + emitSelect(s, indices); } - if (currentDimension < 0) break; } } - void emitType(std::ostream& s) const override { if (dimension() > 0) { for (int i = 0; i < dimension(); ++i) s << "(Array (_ BitVec 32) "; @@ -158,29 +132,79 @@ public: for (int i = 0; i < dimension(); ++i) s << ")"; } } - int totalWidth() const override { - int totalLength = 1; - for (int dim = 0; dim < dimension(); ++dim) { - const int length = getLength(dim); - if (length == -1) return 0; - totalLength *= length; - } - return width() * totalLength; + const int elementCounts = countMatchingElements(*m_arrVarsRefp, name()); + return width() * elementCounts; } - void emitExtract(std::ostream& s, int i) const override { const int j = i / width(); i = i % width(); - std::vector indices(dimension()); - int idx = j; - for (int dim = dimension() - 1; dim >= 0; --dim) { - int length = getLength(dim); - indices[dim] = idx % length; - idx /= length; - } s << " ((_ extract " << i << ' ' << i << ')'; - emitSelect(s, indices); + 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& indices = it->second->m_indices; + emitSelect(s, indices); + } + s << ')'; + } +}; + +template +class VlRandomArrayVar final : public VlRandomVar { +public: + VlRandomArrayVar(const char* name, int width, void* datap, int dimension, + std::uint32_t randModeIdx) + : VlRandomVar{name, width, datap, dimension, randModeIdx} {} + void* datap(int idx) const override { + const std::string indexed_name = name() + std::to_string(idx); + const auto it = m_arrVarsRefp->find(indexed_name); + if (it != m_arrVarsRefp->end()) return it->second->m_datap; + return &static_cast(VlRandomVar::datap(idx))->operator[](idx); + } + void emitSelect(std::ostream& s, const std::vector& indices) const { + for (size_t idx = 0; idx < indices.size(); ++idx) s << "(select "; + s << name(); + for (size_t idx = 0; idx < indices.size(); ++idx) { + s << " #x"; + for (int j = 28; j >= 0; j -= 4) { + s << "0123456789abcdef"[(indices[idx] >> j) & 0xf]; + } + s << ")"; + } + } + void emitGetValue(std::ostream& s) const override { + const int elementCounts = countMatchingElements(*m_arrVarsRefp, name()); + for (int i = 0; i < elementCounts; i++) { + 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& indices = it->second->m_indices; + emitSelect(s, indices); + } + } + } + void emitType(std::ostream& s) const override { + if (dimension() > 0) { + for (int i = 0; i < dimension(); ++i) s << "(Array (_ BitVec 32) "; + s << "(_ BitVec " << width() << ")"; + for (int i = 0; i < dimension(); ++i) s << ")"; + } + } + int totalWidth() const override { + const int elementCounts = countMatchingElements(*m_arrVarsRefp, name()); + return width() * elementCounts; + } + void emitExtract(std::ostream& s, int i) const override { + const int j = i / width(); + i = i % width(); + s << " ((_ extract " << i << ' ' << i << ')'; + 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& indices = it->second->m_indices; + emitSelect(s, indices); + } s << ')'; } }; @@ -192,6 +216,7 @@ class VlRandomizer final { std::vector m_constraints; // Solver-dependent constraints std::map> m_vars; // Solver-dependent // variables + ArrayInfoMap m_arr_vars; // Tracks each element in array structures for iteration const VlQueue* m_randmode; // rand_mode state; // PRIVATE METHODS @@ -220,6 +245,10 @@ public: if (m_vars.find(name) != m_vars.end()) return; m_vars[name] = std::make_shared>>( name, width, &var, dimension, randmodeIdx); + if (dimension > 0) { + idx = 0; + record_arr_table(var, name, dimension, {}); + } } template void write_var(VlUnpacked& var, int width, const char* name, int dimension, @@ -227,6 +256,60 @@ public: if (m_vars.find(name) != m_vars.end()) return; m_vars[name] = std::make_shared>>( name, width, &var, dimension, randmodeIdx); + if (dimension > 0) { + idx = 0; + record_arr_table(var, name, dimension, {}); + } + } + int idx = 0; + std::string generateKey(const std::string& name, int idx) { + if (!name.empty() && name[0] == '\\') { + const size_t space_pos = name.find(' '); + return (space_pos != std::string::npos ? name.substr(0, space_pos) : name) + + std::to_string(idx); + } + const size_t bracket_pos = name.find('['); + return (bracket_pos != std::string::npos ? name.substr(0, bracket_pos) : name) + + std::to_string(idx); + } + template + void record_arr_table(T& var, const std::string name, int dimension, + std::vector indices) { + const std::string key = generateKey(name, idx); + m_arr_vars[key] = std::make_shared(name, &var, idx, indices); + idx += 1; + } + template + void record_arr_table(VlQueue& var, const std::string name, int dimension, + std::vector indices) { + if ((dimension > 0) && (var.size() != 0)) { + for (size_t i = 0; i < var.size(); ++i) { + const std::string indexed_name = name + "[" + std::to_string(i) + "]"; + indices.push_back(i); + record_arr_table(var.atWrite(i), indexed_name, dimension - 1, indices); + indices.pop_back(); + } + } else { + const std::string key = generateKey(name, idx); + m_arr_vars[key] = std::make_shared(name, &var, idx, indices); + ++idx; + } + } + template + void record_arr_table(VlUnpacked& var, const std::string name, int dimension, + std::vector indices) { + if ((dimension > 0) && (N != 0)) { + for (size_t i = 0; i < N; ++i) { + const std::string indexed_name = name + "[" + std::to_string(i) + "]"; + indices.push_back(i); + record_arr_table(var.operator[](i), indexed_name, dimension - 1, indices); + indices.pop_back(); + } + } else { + const std::string key = generateKey(name, idx); + m_arr_vars[key] = std::make_shared(name, &var, idx, indices); + idx += 1; + } } void hard(std::string&& constraint); void clear(); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index dfece340b..24a5a41bb 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -618,7 +618,8 @@ class ConstraintExprVisitor final : public VNVisitor { VAccess::READWRITE}, "write_var"}; uint32_t dimension = 0; - if (VN_IS(varp->dtypep(), UnpackArrayDType)) { + if (VN_IS(varp->dtypep(), UnpackArrayDType) || VN_IS(varp->dtypep(), DynArrayDType) + || VN_IS(varp->dtypep(), QueueDType)) { const std::pair dims = varp->dtypep()->dimensions(/*includeBasic=*/true); const uint32_t unpackedDimensions = dims.second; @@ -631,8 +632,11 @@ class ConstraintExprVisitor final : public VNVisitor { varRefp->classOrPackagep(classOrPackagep); methodp->addPinsp(varRefp); size_t width = varp->width(); - if (VN_IS(varp->dtypep(), DynArrayDType) || VN_IS(varp->dtypep(), QueueDType)) - width = varp->dtypep()->subDTypep()->width(); + AstNodeDType* tmpDtypep = varp->dtypep(); + while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType) + || VN_IS(tmpDtypep, QueueDType)) + tmpDtypep = tmpDtypep->subDTypep(); + width = tmpDtypep->width(); methodp->addPinsp( new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{}, width}); AstNodeExpr* const varnamep diff --git a/test_regress/t/t_constraint_dyn_queue_basic.py b/test_regress/t/t_constraint_dyn_queue_basic.py new file mode 100755 index 000000000..a2b131082 --- /dev/null +++ b/test_regress/t/t_constraint_dyn_queue_basic.py @@ -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() diff --git a/test_regress/t/t_constraint_dyn_queue_basic.v b/test_regress/t/t_constraint_dyn_queue_basic.v new file mode 100755 index 000000000..f115be10c --- /dev/null +++ b/test_regress/t/t_constraint_dyn_queue_basic.v @@ -0,0 +1,113 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by PlanV GmbH. +// SPDX-License-Identifier: CC0-1.0 + +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); + +class ConstrainedDynamicQueueArray; + rand int queue_1d[$]; + rand int queue[$][$]; + rand int dyn[][]; + rand int queue_dyn[$][]; + rand int dyn_queue[][$]; + rand int queue_unp[$][3]; + rand int unp_queue[3][$]; + rand int \array_w[ith_es]cape [3][2]; + + // Constraints for the queues and dynamic arrays + constraint queue_constraints { + foreach (queue_1d[i]) queue_1d[i] == i + 2; + foreach (queue[i, j]) queue[i][j] == (2 * i) + j; + } + + constraint dyn_constraints { + dyn[0][0] == 10; + dyn[1][0] inside {20, 30, 40}; + dyn[1][1] > 50; + dyn[0][1] < 100; + dyn[0][2] inside {5, 15, 25}; + } + + constraint queue_dyn_constraints { + foreach (queue_dyn[i, j]) queue_dyn[i][j] == i + j + 3; + } + + constraint dyn_queue_constraints { + foreach (dyn_queue[i, j]) dyn_queue[i][j] == (3 * i) + j + 2; + } + + constraint unp_queue_constraints { + foreach (unp_queue[i, j]) unp_queue[i][j] == (i * 5) + j + 1; + } + + constraint array_with_escape_constraints { + \array_w[ith_es]cape [0][0] == 6; + } + + // Constructor + function new(); + queue_1d = {1, 2, 3, 4}; + queue = '{ '{1, 2}, '{3, 4, 5}, '{6}}; + dyn = new[2]; + dyn[0] = new[3]; + dyn[1] = new[4]; + + queue_dyn = {}; + queue_dyn[0] = new[3]; + queue_dyn[1] = new[4]; + + dyn_queue = new[2]; + dyn_queue[0] = {7, 8, 9}; + dyn_queue[1] = {10}; + + queue_unp = {}; + + unp_queue[0] = {17, 18}; + unp_queue[1] = {19}; + unp_queue[2] = {20}; + endfunction + + // Self-check function + function void check(); + foreach (queue_1d[i]) `checkh(queue_1d[i], i + 2) + + foreach (queue[i, j]) `checkh(queue[i][j], (2 * i) + j) + + `checkh(dyn[0][0], 10) + `checkh(dyn[1][0] inside {20, 30, 40}, 1'b1) + `checkh(dyn[1][1] > 50, 1'b1) + `checkh(dyn[0][1] < 100, 1'b1) + `checkh(dyn[0][2] inside {5, 15, 25}, 1'b1) + + foreach (queue_dyn[i, j]) `checkh(queue_dyn[i][j], i + j + 3) + + foreach (dyn_queue[i, j]) `checkh(dyn_queue[i][j], (3 * i) + j + 2) + + `checkh(unp_queue[0][0], (0 * 5) + 0 + 1) + `checkh(unp_queue[0][1], (0 * 5) + 1 + 1) + `checkh(unp_queue[1][0], (1 * 5) + 0 + 1) + `checkh(unp_queue[2][0], (2 * 5) + 0 + 1) + + `checkh(\array_w[ith_es]cape [0][0], 6) + endfunction +endclass + +module t_constraint_dyn_queue_basic; + ConstrainedDynamicQueueArray array_test; + int success; + initial begin + $display("Test: Randomization for dynamic and mixed queues and arrays:"); + array_test = new(); + repeat(2) begin + success = array_test.randomize(); + `checkh(success, 1) + array_test.check(); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule