diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index a70a81ed7..e87b78f7c 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -23,6 +23,7 @@ #include "verilated_random.h" +#include #include #include #include @@ -367,13 +368,90 @@ void VlRandomizer::randomConstraint(std::ostream& os, VlRNG& rngr, int bits) { os << ')'; } +size_t VlRandomizer::hashConstraints() const { + size_t h = 0; + for (const auto& c : m_constraints) { + h ^= std::hash{}(c) + 0x9e3779b9 + (h << 6) + (h >> 2); + } + return h; +} + +void VlRandomizer::enumerateRandcValues(const std::string& varName, VlRNG& rngr) { + std::vector values; + const auto varIt = m_vars.find(varName); + if (varIt == m_vars.end()) return; + const int width = varIt->second->width(); + + std::iostream& os = getSolver(); + if (!os) return; + + // Set up a single incremental solver session for enumeration + os << "(set-option :produce-models true)\n"; + os << "(set-logic QF_ABV)\n"; + os << "(define-fun __Vbv ((b Bool)) (_ BitVec 1) (ite b #b1 #b0))\n"; + os << "(define-fun __Vbool ((v (_ BitVec 1))) Bool (= #b1 v))\n"; + + // Declare all variables (solver needs full context for cross-var constraints) + for (const auto& var : m_vars) { + if (var.second->dimension() > 0) { + auto arrVarsp = std::make_shared(m_arr_vars); + var.second->setArrayInfo(arrVarsp); + } + os << "(declare-fun " << var.first << " () "; + var.second->emitType(os); + os << ")\n"; + } + + // Assert all user constraints + for (const std::string& constraint : m_constraints) { + os << "(assert (= #b1 " << constraint << "))\n"; + } + + // Incrementally enumerate all valid values for this randc variable + while (true) { + os << "(check-sat)\n"; + std::string sat; + do { std::getline(os, sat); } while (sat.empty()); + if (sat != "sat") break; + + // Read just this variable's value + os << "(get-value (" << varName << "))\n"; + char c; + os >> c; // '(' + os >> c; // '(' + std::string name, value; + os >> name; // Consume variable name token from solver output + (void)name; + std::getline(os, value, ')'); + os >> c; // ')' + + // Parse the SMT value to uint64_t + VlWide qowp; + VL_SET_WQ(qowp, 0ULL); + if (!parseSMTNum(width, qowp, value)) break; + const uint64_t numVal = (width <= 32) ? qowp[0] : VL_SET_QW(qowp); + + values.push_back(numVal); + + // Exclude this value for next iteration (incremental) + os << "(assert (not (= " << varName << " (_ bv" << numVal << " " << width << "))))\n"; + } + + os << "(reset)\n"; + + // Shuffle using Fisher-Yates + for (size_t i = values.size(); i > 1; --i) { + const size_t j = VL_RANDOM_RNG_I(rngr) % i; + std::swap(values[i - 1], values[j]); + } + + m_randcValueQueues[varName] = std::deque(values.begin(), values.end()); +} + bool VlRandomizer::next(VlRNG& rngr) { if (m_vars.empty() && m_unique_arrays.empty()) return true; for (const std::string& baseName : m_unique_arrays) { const auto it = m_vars.find(baseName); - - // Look up the actual size we stored earlier - // const uint32_t size = m_unique_array_sizes[baseName]; const uint32_t size = m_unique_array_sizes.at(baseName); if (it != m_vars.end()) { @@ -388,6 +466,30 @@ bool VlRandomizer::next(VlRNG& rngr) { } } + // Randc queue-based cycling: enumerate valid values once, then pop per call + if (!m_randcVarNames.empty()) { + const size_t currentHash = hashConstraints(); + // Invalidate queues if constraints changed (e.g., constraint_mode toggled) + if (currentHash != m_randcConstraintHash) { + m_randcValueQueues.clear(); + m_randcConstraintHash = currentHash; + } + // Refill empty queues (start of new cycle) + for (const auto& name : m_randcVarNames) { + auto& queue = m_randcValueQueues[name]; + if (queue.empty()) enumerateRandcValues(name, rngr); + } + } + + // Pop randc values from queues (will be pinned in solver) + std::map randcPinned; + for (const auto& name : m_randcVarNames) { + auto& queue = m_randcValueQueues[name]; + if (queue.empty()) return false; // No valid values exist + randcPinned[name] = queue.front(); + queue.pop_front(); + } + std::iostream& os = getSolver(); if (!os) return false; @@ -408,6 +510,13 @@ bool VlRandomizer::next(VlRNG& rngr) { for (const std::string& constraint : m_constraints) { os << "(assert (= #b1 " << constraint << "))\n"; } + + // Pin randc values from pre-enumerated queues + for (const auto& pair : randcPinned) { + const int w = m_vars.at(pair.first)->width(); + os << "(assert (= " << pair.first << " (_ bv" << pair.second << " " << w << ")))\n"; + } + os << "(check-sat)\n"; bool sat = parseSolution(os, true); @@ -620,8 +729,13 @@ void VlRandomizer::clearConstraints() { void VlRandomizer::clearAll() { m_constraints.clear(); m_vars.clear(); + m_randcVarNames.clear(); + m_randcValueQueues.clear(); + m_randcConstraintHash = 0; } +void VlRandomizer::markRandc(const char* name) { m_randcVarNames.insert(name); } + #ifdef VL_DEBUG void VlRandomizer::dump() const { for (const auto& var : m_vars) { diff --git a/include/verilated_random.h b/include/verilated_random.h index c37fbec33..31aea0fc7 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -28,6 +28,7 @@ #include "verilated.h" +#include #include #include #include @@ -209,10 +210,16 @@ class VlRandomizer VL_NOT_FINAL { std::map m_unique_array_sizes; const VlQueue* m_randmodep = nullptr; // rand_mode state; int m_index = 0; // Internal counter for key generation + std::set m_randcVarNames; // Names of randc variables for cyclic tracking + std::map> + m_randcValueQueues; // Remaining values per randc var (queue-based cycling) + size_t m_randcConstraintHash = 0; // Hash of constraints when queues were built // PRIVATE METHODS void randomConstraint(std::ostream& os, VlRNG& rngr, int bits); bool parseSolution(std::iostream& file, bool log = false); + void enumerateRandcValues(const std::string& varName, VlRNG& rngr); + size_t hashConstraints() const; public: // CONSTRUCTORS @@ -585,6 +592,7 @@ public: const char* source = ""); void clearConstraints(); void clearAll(); // Clear both constraints and variables + void markRandc(const char* name); // Mark variable as randc for cyclic tracking void set_randmode(const VlQueue& randmode) { m_randmodep = &randmode; } #ifdef VL_DEBUG void dump() const; diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index a35525dea..097e6f56a 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -815,6 +815,7 @@ public: RANDOMIZER_CLEARALL, RANDOMIZER_HARD, RANDOMIZER_UNIQUE, + RANDOMIZER_MARK_RANDC, RANDOMIZER_WRITE_VAR, RNG_GET_RANDSTATE, RNG_SET_RANDSTATE, @@ -947,6 +948,7 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {RANDOMIZER_CLEARALL, "clearAll", false}, \ {RANDOMIZER_HARD, "hard", false}, \ {RANDOMIZER_UNIQUE, "rand_unique", false}, \ + {RANDOMIZER_MARK_RANDC, "markRandc", false}, \ {RANDOMIZER_WRITE_VAR, "write_var", false}, \ {RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \ {RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \ diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 83309b453..94e694485 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1135,6 +1135,20 @@ class ConstraintExprVisitor final : public VNVisitor { } } initTaskp->addStmtsp(methodp->makeStmt()); + // If randc, also emit markRandc() for cyclic tracking + if (varp->isRandC()) { + AstCMethodHard* const markp = new AstCMethodHard{ + varp->fileline(), + new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), + m_genp, VAccess::READWRITE}, + VCMethod::RANDOMIZER_MARK_RANDC}; + markp->dtypeSetVoid(); + AstNodeExpr* const nameExprp = new AstCExpr{ + varp->fileline(), AstCExpr::Pure{}, "\"" + smtName + "\"", varp->width()}; + nameExprp->dtypep(varp->dtypep()); + markp->addPinsp(nameExprp); + initTaskp->addStmtsp(markp->makeStmt()); + } } } else { // Variable already written, clean up cloned membersel if any @@ -3194,7 +3208,8 @@ class RandomizeVisitor final : public VNVisitor { stmtp = stmtp->nextp()) { bool foundClearConstraints = false; stmtp->foreach([&](AstCMethodHard* methodp) { - if (methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR) { + if (methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR + || methodp->method() == VCMethod::RANDOMIZER_MARK_RANDC) { randomizep->addStmtsp(stmtp->cloneTree(false)); } else if (methodp->method() == VCMethod::RANDOMIZER_CLEARCONSTRAINTS) { @@ -3544,7 +3559,8 @@ class RandomizeVisitor final : public VNVisitor { for (AstNode* stmtp = mainRandomizep->stmtsp(); stmtp; stmtp = stmtp->nextp()) { bool foundClearConstraints = false; stmtp->foreach([&](AstCMethodHard* methodp) { - if (methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR) { + if (methodp->method() == VCMethod::RANDOMIZER_WRITE_VAR + || methodp->method() == VCMethod::RANDOMIZER_MARK_RANDC) { randomizeFuncp->addStmtsp(stmtp->cloneTree(false)); } else if (methodp->method() == VCMethod::RANDOMIZER_CLEARCONSTRAINTS) { foundClearConstraints = true; diff --git a/test_regress/t/t_randc_constraint.py b/test_regress/t/t_randc_constraint.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randc_constraint.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# 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_randc_constraint.v b/test_regress/t/t_randc_constraint.v new file mode 100644 index 000000000..a6a8f1309 --- /dev/null +++ b/test_regress/t/t_randc_constraint.v @@ -0,0 +1,272 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// Test: randc variables with additional constraints limiting values +// IEEE 1800 Section 18.4.2: randc cyclic behavior over constrained domain + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +// verilog_format: on + +class RandcRange; + randc bit [3:0] value; // 4-bit: unconstrained domain = 0-15 + + constraint c_range { + value >= 3; + value <= 10; + } + + function void test_cyclic; + automatic int count[16]; + automatic int domain_size = 8; // values 3..10 + automatic int randomize_result; + $display("Test randc with range constraint [3:10]"); + // Run 3 full cycles + for (int trial = 0; trial < 3; ++trial) begin + for (int i = 0; i < domain_size; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `check_range(value, 3, 10); + ++count[value]; + end + end + // After 3 full cycles, each value in [3,10] should appear exactly 3 times + for (int v = 3; v <= 10; ++v) begin + `checkd(count[v], 3); + end + // Values outside [3,10] should never appear + for (int v = 0; v < 3; ++v) begin + `checkd(count[v], 0); + end + for (int v = 11; v < 16; ++v) begin + `checkd(count[v], 0); + end + endfunction +endclass + +class RandcSmall; + randc bit [1:0] val; // 4 possible values: 0,1,2,3 + constraint c_exclude { val != 0; } // 3 valid values: 1,2,3 + + function void test_cyclic; + automatic int count[4]; + automatic int domain_size = 3; + automatic int randomize_result; + $display("Test randc with exclude constraint (val != 0)"); + // Run 4 full cycles + for (int trial = 0; trial < 4; ++trial) begin + for (int i = 0; i < domain_size; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `checkd(val == 0, 0); + ++count[val]; + end + end + // After 4 full cycles, each of 1,2,3 should appear exactly 4 times + `checkd(count[0], 0); + for (int v = 1; v <= 3; ++v) begin + `checkd(count[v], 4); + end + endfunction +endclass + +// Test 3: Inheritance - parent randc with constraint, child inherits +class RandcParent; + randc bit [2:0] code; // 8 values: 0-7 + constraint c_positive { code > 0; } // 7 valid values: 1-7 + + function void test_cyclic; + automatic int count[8]; + automatic int domain_size = 7; + automatic int randomize_result; + $display("Test randc parent with constraint (code > 0)"); + for (int trial = 0; trial < 3; ++trial) begin + for (int i = 0; i < domain_size; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `checkd(code == 0, 0); + ++count[code]; + end + end + for (int v = 1; v <= 7; ++v) begin + `checkd(count[v], 3); + end + `checkd(count[0], 0); + endfunction +endclass + +class RandcChild extends RandcParent; + // Inherits randc code and c_positive constraint + constraint c_upper { code <= 5; } // Further restrict: 1-5 (5 values) + + function void test_cyclic; + automatic int count[8]; + automatic int domain_size = 5; + automatic int randomize_result; + $display("Test randc child with inherited + additional constraint (1 <= code <= 5)"); + for (int trial = 0; trial < 4; ++trial) begin + for (int i = 0; i < domain_size; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `check_range(code, 1, 5); + ++count[code]; + end + end + for (int v = 1; v <= 5; ++v) begin + `checkd(count[v], 4); + end + for (int v = 6; v <= 7; ++v) begin + `checkd(count[v], 0); + end + endfunction +endclass + +// Test 5: constraint_mode() interaction +// Verifies randc cyclic behavior adapts when constraints are toggled at runtime. +// Note: randc cycles are continuous, not per-phase, so we verify constraint +// enforcement and that all valid values eventually appear rather than exact counts. +class RandcModeSwitch; + randc bit [1:0] x; // 4 values: 0-3 + constraint c_nonzero { x != 0; } // 3 valid values: 1,2,3 + + function void test_mode_switch; + automatic int randomize_result; + automatic bit seen_zero; + + // Phase 1: constraint ON -> x should never be 0, and all of {1,2,3} should appear + $display("Test constraint_mode: phase 1 (constraint ON)"); + begin + automatic bit seen[4] = '{0, 0, 0, 0}; + // Run 2 full cycles (6 calls) to ensure all constrained values appear + for (int i = 0; i < 6; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `checkd(x == 0, 0); + seen[x] = 1; + end + for (int v = 1; v <= 3; ++v) begin + `checkd(seen[v], 1); + end + end + + // Phase 2: constraint OFF -> x=0 should eventually appear + $display("Test constraint_mode: phase 2 (constraint OFF)"); + c_nonzero.constraint_mode(0); + seen_zero = 0; + // Run enough calls (2 full cycles of 4 + margin) to see all values + for (int i = 0; i < 12; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + if (x == 0) seen_zero = 1; + end + `checkd(seen_zero, 1); + + // Phase 3: constraint back ON -> x should never be 0 again + $display("Test constraint_mode: phase 3 (constraint ON again)"); + c_nonzero.constraint_mode(1); + for (int i = 0; i < 9; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `checkd(x == 0, 0); + end + endfunction +endclass + +// Test 6: Enum randc with constraint excluding some values +// Use a 2-bit enum where all bit values are valid enum members, +// so the solver domain matches the enum domain exactly. +class RandcEnumConstrained; + typedef enum bit [1:0] { + RED = 0, + GREEN = 1, + BLUE = 2, + WHITE = 3 + } color_t; + + randc color_t color; + constraint c_no_white { color != WHITE; } // 3 valid: RED, GREEN, BLUE + + function void test_cyclic; + automatic int count[4] = '{0, 0, 0, 0}; + automatic int domain_size = 3; + automatic int randomize_result; + $display("Test randc enum with constraint (exclude WHITE)"); + for (int trial = 0; trial < 4; ++trial) begin + for (int i = 0; i < domain_size; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + `checkd(color == WHITE, 0); + ++count[color]; + end + end + for (int v = 0; v <= 2; ++v) begin + `checkd(count[v], 4); + end + `checkd(count[3], 0); + endfunction +endclass + +// Test 7: Deep cyclic - full 4-bit range 0:15 (16 values, no constraint) +class RandcDeep; + randc bit [3:0] val; + + function void test_cyclic; + automatic int count[16]; + automatic int domain_size = 16; + automatic int randomize_result; + $display("Test randc deep cyclic [0:15] (16 values)"); + // Run 3 full cycles + for (int trial = 0; trial < 3; ++trial) begin + for (int i = 0; i < domain_size; ++i) begin + randomize_result = randomize(); + `checkd(randomize_result, 1); + ++count[val]; + end + end + // Each value 0..15 should appear exactly 3 times + for (int v = 0; v < 16; ++v) begin + `checkd(count[v], 3); + end + endfunction +endclass + +module t; + RandcRange rr; + RandcSmall rs; + RandcParent rp; + RandcChild rc; + RandcModeSwitch rms; + RandcEnumConstrained rec; + RandcDeep rd; + + initial begin + rr = new; + rr.test_cyclic(); + + rs = new; + rs.test_cyclic(); + + rp = new; + rp.test_cyclic(); + + rc = new; + rc.test_cyclic(); + + rms = new; + rms.test_mode_switch(); + + rec = new; + rec.test_cyclic(); + + rd = new; + rd.test_cyclic(); + + $write("*-* All Finished *-*\n"); + $finish(); + end +endmodule