From 2c441d853c93a755610938f157bda0a669cddf4b Mon Sep 17 00:00:00 2001 From: Michael Bedford Taylor Date: Sun, 27 Sep 2026 16:46:27 -0700 Subject: [PATCH] Optimize DFG temporary sharing across module instances (#8456) --- src/V3Dfg.cpp | 46 +++++++----- src/V3Dfg.h | 28 +++++-- src/V3DfgBreakCycles.cpp | 7 +- src/V3DfgPasses.cpp | 13 +--- src/V3DfgPeephole.cpp | 4 +- src/V3DfgRegularize.cpp | 6 +- src/V3DfgSynthesize.cpp | 23 ++---- test_regress/t/t_dfg_break_cycles.v | 9 +++ test_regress/t/t_dfg_temp_sharing.py | 19 +++++ test_regress/t/t_dfg_temp_sharing.v | 108 +++++++++++++++++++++++++++ 10 files changed, 196 insertions(+), 67 deletions(-) create mode 100755 test_regress/t/t_dfg_temp_sharing.py create mode 100644 test_regress/t/t_dfg_temp_sharing.v diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index bed124ac6..378c54170 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -21,6 +21,7 @@ #include "V3Ast.h" #include "V3EmitV.h" #include "V3File.h" +#include "V3Stats.h" VL_DEFINE_DEBUG_FUNCTIONS; @@ -31,6 +32,8 @@ DfgGraph::DfgGraph(const string& name) : m_name{name} {} DfgGraph::~DfgGraph() { + V3Stats::addStatSum("Optimizations, DFG, temporary declarations reused", + static_cast(m_tempDeclarationsReused)); forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); }); } @@ -98,26 +101,31 @@ void DfgGraph::mergeGraphs(std::vector>&& otherps) { } } -std::string DfgGraph::makeUniqueName(const std::string& prefix, size_t n) { - // Construct the tmpNameStub if we have not done so yet - if (m_tmpNameStub.empty()) { - // Use the hash of the graph name (avoid long names and non-identifiers) - const std::string hash = V3Hash{m_name}.toString(); - // We need to keep every variable globally unique, and graph hashed - // names might not be, so keep a static table to track multiplicity - static std::unordered_map s_multiplicity; - m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_'; - } - // Assemble the globally unique name - return "__Vdfg" + prefix + m_tmpNameStub + std::to_string(n); -} - -DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& name, +DfgVertexVar* DfgGraph::makeNewVar(FileLine* flp, const std::string& prefix, const DfgDataType& dtype, AstScope* scopep) { - // Create AstVar - AstVar* const varp = new AstVar{flp, VVarType::MODULETEMP, name, dtype.astDtypep()}; - // Add AstVar to the scope's module - scopep->modp()->addStmtsp(varp); + // AstVar declarations outlive all DFG graphs. Splitting or merging graphs + // does not transfer slots: each graph creates globally unique declarations. + TempDeclarations& temps = m_temporaries[scopep->modp()][{prefix, dtype.astDtypep()}]; + const size_t slot = temps.m_scopeCounts[scopep]++; + AstVar* varp; + if (slot == temps.m_declps.size()) { + // Construct the name stub on the first new declaration in this graph + if (m_tmpNameStub.empty()) { + // Use the hash of the graph name (avoid long names and non-identifiers) + const std::string hash = V3Hash{m_name}.toString(); + // Graph hashes may collide, so track multiplicity to keep names globally unique + static std::unordered_map s_multiplicity; + m_tmpNameStub += '_' + hash + '_' + std::to_string(s_multiplicity[hash]++) + '_'; + } + const std::string varName + = "__Vdfg" + prefix + m_tmpNameStub + std::to_string(m_tmpNameCount++); + varp = new AstVar{flp, VVarType::MODULETEMP, varName, dtype.astDtypep()}; + scopep->modp()->addStmtsp(varp); + temps.m_declps.emplace_back(varp); + } else { + varp = temps.m_declps[slot]; + ++m_tempDeclarationsReused; + } // Create AstVarScope AstVarScope* const vscp = new AstVarScope{flp, scopep, varp}; // Add to scope diff --git a/src/V3Dfg.h b/src/V3Dfg.h index 503913e70..ea63ebb36 100644 --- a/src/V3Dfg.h +++ b/src/V3Dfg.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -429,6 +430,19 @@ class DfgGraph final { size_t m_size = 0; // Number of vertices in the graph const std::string m_name; // Name of graph - need not be unique std::string m_tmpNameStub{""}; // Name stub for temporary variables - computed lazy + size_t m_tmpNameCount = 0; // Sequence number for newly created temporary declarations + + // Slots are local to this graph and keyed by module, prefix and type. + // Different prefixes may carry different AstVar attributes, but temporaries + // with the same prefix may share an AstVar, so they must have identical ones. + // Each scope consumes each slot at most once. + struct TempDeclarations final { + std::map m_scopeCounts; // Next slot for each instance + std::vector m_declps; // Declarations indexed by slot + }; + std::map, TempDeclarations>> + m_temporaries; // Shared slots indexed by module, purpose, and type + uint64_t m_tempDeclarationsReused = 0; // Declarations shared across instance scopes // The only way to access thes is via DfgUserMap, so mutable is appropriate, // the map can change while the graph is const. @@ -527,14 +541,12 @@ public: // DfgVertexVar instances representing the same Ast variable are unified. void mergeGraphs(std::vector>&& otherps) VL_MT_DISABLED; - // Genarete a unique name. The provided 'prefix' and 'n' values will be part of the name, and - // must be unique (as a pair) in each invocation for this graph. - std::string makeUniqueName(const std::string& prefix, size_t n) VL_MT_DISABLED; - - // Create a new variable with the given name and data type. For a Scoped - // Dfg, the AstScope where the corresponding AstVarScope will be inserted - // must be provided - DfgVertexVar* makeNewVar(FileLine*, const std::string& name, const DfgDataType&, + // Create a new scoped variable. Instances of a module share temporary + // declarations of the same prefix and type, but have independent storage. + // Each scope uses a declaration at most once; new declarations get unique names. + // As the AstVar may be shared, callers must set identical AstVar attributes + // on all temporaries created with the same prefix. + DfgVertexVar* makeNewVar(FileLine*, const std::string& prefix, const DfgDataType&, AstScope*) VL_MT_DISABLED; // Split this graph into individual components (unique sub-graphs with no edges between them). diff --git a/src/V3DfgBreakCycles.cpp b/src/V3DfgBreakCycles.cpp index 6c4366470..64f9c1d0d 100644 --- a/src/V3DfgBreakCycles.cpp +++ b/src/V3DfgBreakCycles.cpp @@ -239,12 +239,10 @@ class TraceDriver final : public DfgVisitor { // Create temporary capable of holding the result of 'vtxp' DfgVertexVar* createTmp(const char* prefix, DfgVertex* vtxp) { - AstNode* nodep = v3Global.rootp(); - const std::string name = m_dfg.makeUniqueName(prefix, nodep->user2Inc()); FileLine* const flp = vtxp->fileline(); DfgVertex::ScopeCache scopeCache; AstScope* const scopep = vtxp->scopep(scopeCache); - DfgVertexVar* const varp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); + DfgVertexVar* const varp = m_dfg.makeNewVar(flp, prefix, vtxp->dtype(), scopep); varp->vscp()->varp()->isInternal(true); varp->tmpForp(varp->vscp()); m_sccInfo.add(*varp, 0); @@ -1654,9 +1652,6 @@ void breakCycles(DfgGraph& dfg, V3DfgBreakCyclesContext& ctx) { if (dumpDfgLevel() >= level) dfg.dumpDotFilePrefixed("breakCycles-" + name); }; - // AstNetlist/AstNodeModule user2 used as sequence numbers for temporaries - const VNUser2InUse user2InUse; - // Show input for debugging dump(7, dfg, "input"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index ce43cd565..ddef7f314 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -344,9 +344,6 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) { // Fast path exit if we surely don't need to convet anything if (nTerms < TERM_LIMIT) return; - // Sequence numbers for name generation - size_t nTables = 0; - DfgVertex::ScopeCache scopeCache; // Create decoders for each srcp @@ -419,8 +416,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) { // If there is an existing result variable, use that if (DfgVertexVar* const vp = srcp->getResultVar()) return vp->as(); // Otherwise create a new variable - const std::string name = dfg.makeUniqueName("BinToOneHot_Idx", nTables); - DfgVertexVar* const vtxp = dfg.makeNewVar(flp, name, idxDType, scopep); + DfgVertexVar* const vtxp = dfg.makeNewVar(flp, "BinToOneHot_Idx", idxDType, scopep); vtxp->vscp()->varp()->isInternal(true); vtxp->srcp(srcp); return vtxp->as(); @@ -428,8 +424,7 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) { AstVarScope* const idxVscp = idxVtxp->vscp(); // The previous index variable - we don't need a vertex for this AstVarScope* const preVscp = [&]() { - const std::string name = dfg.makeUniqueName("BinToOneHot_Pre", nTables); - DfgVertexVar* const vtxp = dfg.makeNewVar(flp, name, idxDType, scopep); + DfgVertexVar* const vtxp = dfg.makeNewVar(flp, "BinToOneHot_Pre", idxDType, scopep); AstVarScope* const vscp = vtxp->vscp(); VL_DO_DANGLING(vtxp->unlinkDelete(dfg), vtxp); vscp->varp()->isInternal(true); @@ -439,15 +434,13 @@ void V3DfgPasses::binToOneHot(DfgGraph& dfg, V3DfgBinToOneHotContext& ctx) { }(); // The table variable DfgVarArray* const tabVtxp = [&]() { - const std::string name = dfg.makeUniqueName("BinToOneHot_Tab", nTables); - DfgVertexVar* const varp = dfg.makeNewVar(flp, name, tabDType, scopep); + DfgVertexVar* const varp = dfg.makeNewVar(flp, "BinToOneHot_Tab", tabDType, scopep); varp->vscp()->varp()->isInternal(true); varp->vscp()->varp()->noReset(true); varp->setHasModWrRefs(); return varp->as(); }(); - ++nTables; ++ctx.m_decodersCreated; // Initialize 'tab' and 'pre' variables statically diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f00402946..b9d28fd95 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -209,7 +209,6 @@ class V3DfgPeephole final : public DfgVisitor { DfgVertex* m_vtxp = nullptr; // Currently considered vertex size_t m_currentGeneration = 0; // Current generation number size_t m_lastId = 0; // Last unique vertex ID assigned - size_t m_nTemps = 0; // Number of temporary variables created // Scope for transient temporariy variables cerated in this pass. They should all be // eliminated wihtin this pass, so anything should be ok, pick the top scope as easy to find. AstScope* const m_tmpScopep = v3Global.rootp()->topScopep()->scopep(); @@ -2181,9 +2180,8 @@ class V3DfgPeephole final : public DfgVisitor { DfgSplicePacked* const sp = new DfgSplicePacked{m_dfg, flp, vtxp->dtype()}; m_vInfo[sp].m_id = ++m_lastId; sp->addDriver(catp, lsb, flp); - const std::string name = m_dfg.makeUniqueName("PeepholeNarrow", m_nTemps++); DfgVertexVar* const varp - = m_dfg.makeNewVar(flp, name, vtxp->dtype(), m_tmpScopep); + = m_dfg.makeNewVar(flp, "PeepholeNarrow", vtxp->dtype(), m_tmpScopep); varp->tmpForp(varp->vscp()); m_vInfo[varp].m_id = ++m_lastId; varp->vscp()->varp()->isInternal(true); diff --git a/src/V3DfgRegularize.cpp b/src/V3DfgRegularize.cpp index a657159a3..27ea7da2d 100644 --- a/src/V3DfgRegularize.cpp +++ b/src/V3DfgRegularize.cpp @@ -30,7 +30,6 @@ class DfgRegularize final { // STATE DfgGraph& m_dfg; // The graph being processed V3DfgRegularizeContext& m_ctx; // The optimization context for stats - size_t m_nTmps = 0; // Number of temporaries added to this graph - for variable names only VNDeleter m_deleter; // Deletes replacement nodes at the end // METHODS @@ -208,11 +207,10 @@ class DfgRegularize final { if (!needsTemporary(*vtxp, *vtxp)) continue; // Need to create an intermediate variable ++m_ctx.m_temporariesIntroduced; - const std::string name = m_dfg.makeUniqueName("Regularize", m_nTmps); FileLine* const flp = vtxp->fileline(); AstScope* const scopep = vtxp->scopep(scopeCache); - DfgVertexVar* const newp = m_dfg.makeNewVar(flp, name, vtxp->dtype(), scopep); - ++m_nTmps; + DfgVertexVar* const newp + = m_dfg.makeNewVar(flp, "Regularize", vtxp->dtype(), scopep); // Replace vertex with the variable, make it drive the variable vtxp->replaceWith(newp); newp->srcp(vtxp); diff --git a/src/V3DfgSynthesize.cpp b/src/V3DfgSynthesize.cpp index cdbe05814..6773bafb0 100644 --- a/src/V3DfgSynthesize.cpp +++ b/src/V3DfgSynthesize.cpp @@ -58,8 +58,6 @@ DfgArraySel* makeVertex(const AstArraySel* nodep, DfgG class AstToDfgConverter final : public VNVisitor { // NODE STATE // AstNodeExpr/AstVar/AstVarScope::user2p -> DfgVertex* for this Node - // AstVar::user3() -> int temporary counter for variable - const VNUser3InUse m_user3InUse; // STATE DfgGraph& m_dfg; // The graph being built @@ -74,8 +72,6 @@ class AstToDfgConverter final : public VNVisitor { bool m_foundUnhandled = false; // Found node not implemented as DFG or not implemented 'visit' bool m_converting = false; // We are trying to convert some logic at the moment - size_t m_nUnpack = 0; // Sequence numbers for temporaries - // METHODS // Allocate a new non-variable vertex, add it to the currently synthesized logic @@ -273,8 +269,7 @@ class AstToDfgConverter final : public VNVisitor { // Assigning compound expressions to a concatenated LHS requires a temporary // to avoid multiple use of the expression if (VN_IS(lhsp, Concat) && !vtxp->is() && !vtxp->is()) { - const size_t n = ++m_nUnpack; - DfgVertexVar* const tmpp = createTmp(*m_logicp, flp, vtxp->dtype(), "Unpack", n); + DfgVertexVar* const tmpp = createTmp(*m_logicp, flp, vtxp->dtype(), "Unpack"); tmpp->srcp(vtxp); vtxp = tmpp; } @@ -496,9 +491,8 @@ public: // Create temporay variable capable of holding the given type DfgVertexVar* createTmp(DfgLogic& logic, FileLine* flp, const DfgDataType& dtype, - const std::string& prefix, size_t tmpCount) { - const std::string name = m_dfg.makeUniqueName(prefix, tmpCount); - DfgVertexVar* const vtxp = m_dfg.makeNewVar(flp, name, dtype, logic.scopep()); + const std::string& prefix) { + DfgVertexVar* const vtxp = m_dfg.makeNewVar(flp, prefix, dtype, logic.scopep()); logic.synth().emplace_back(vtxp); vtxp->vscp()->varp()->isInternal(true); vtxp->tmpForp(vtxp->vscp()); @@ -511,8 +505,7 @@ public: FileLine* const flp = astVarp->fileline(); const DfgDataType& dtype = *DfgDataType::fromAst(astVarp->dtypep()); const std::string prfx = prefix + "_" + astVarp->name(); - const size_t tmpCount = astVarp->user3Inc(); - DfgVertexVar* const vtxp = createTmp(logic, flp, dtype, prfx, tmpCount); + DfgVertexVar* const vtxp = createTmp(logic, flp, dtype, prfx); vtxp->tmpForp(vscp); return vtxp; } @@ -624,8 +617,6 @@ class AstToDfgSynthesize final { DfgGraph& m_dfg; // The graph being built V3DfgSynthesisContext& m_ctx; // The context for stats AstToDfgConverter m_converter; // The convert instance to use for each construct - size_t m_nBranchCond = 0; // Sequence numbers for temporaries - size_t m_nPathPred = 0; // Sequence numbers for temporaries DfgWorklist m_toRevert{m_dfg}; // We need a worklist for reverting synthesis // STATE - for current DfgLogic being synthesized @@ -1539,12 +1530,11 @@ class AstToDfgSynthesize final { return resp; }(); - size_t n = m_nPathPred++; // Sequence number for temporaries const DfgDataType& dtype = predp->dtype(); const auto mkTmp = [&](FileLine* flp, const char* name, DfgVertex* srcp) { const std::string prefix = "_BB" + std::to_string(bb.id()) + "_" + name; - DfgVertexVar* const tmpp = m_converter.createTmp(*m_logicp, flp, dtype, prefix, n); + DfgVertexVar* const tmpp = m_converter.createTmp(*m_logicp, flp, dtype, prefix); tmpp->srcp(srcp); return tmpp; }; @@ -1717,8 +1707,7 @@ class AstToDfgSynthesize final { FileLine* const flp = condp->fileline(); const DfgDataType& dtype = condp->dtype(); const std::string prefix = "_BB" + std::to_string(bb.id()) + "_Cond"; - const size_t n = m_nBranchCond++; - DfgVertexVar* const vp = m_converter.createTmp(*m_logicp, flp, dtype, prefix, n); + DfgVertexVar* const vp = m_converter.createTmp(*m_logicp, flp, dtype, prefix); vp->srcp(condp); m_bbToCondp[bb] = vp; } diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 51fdb6d0f..579fafceb 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -262,6 +262,15 @@ module t ( `signal(ARRAY_DEFAULT, 21); assign ARRAY_DEFAULT = {array_default[2], array_default[1], array_default[0]}; + // Element 0 is partially driven, with bits 5:4 undriven, so its packed splice + // is not coalesced. It sits on the cycle boundary and is traced via a temporary. + wire [7:0] array_splice[2]; // UNOPTFLAT + assign array_splice[0][3:0] = rand_a[3:0]; + assign array_splice[0][7:6] = rand_a[7:6]; + assign array_splice[1] = {array_splice[0][7:6], 2'd0, array_splice[0][3:0]} + 8'd1; + `signal(ARRAY_SPLICE, 8); + assign ARRAY_SPLICE = array_splice[1]; + `signal(ADD_A, 8); // UNOPTFLAT `signal(ADD_B, 8); `signal(ADD_C, 8); diff --git a/test_regress/t/t_dfg_temp_sharing.py b/test_regress/t/t_dfg_temp_sharing.py new file mode 100755 index 000000000..219d1b259 --- /dev/null +++ b/test_regress/t/t_dfg_temp_sharing.py @@ -0,0 +1,19 @@ +#!/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('vlt', 'vltmt') + +# Disable V3Gate, which would otherwise inline the temporaries, so the +# self-checks also exercise the shared declarations in the executed model. +test.compile(verilator_flags2=['--binary', '--stats', '-fno-gate']) +test.execute() +test.file_grep(test.stats, r'Optimizations, DFG, temporary declarations reused\s+(\d+)', 36) +test.passes() diff --git a/test_regress/t/t_dfg_temp_sharing.v b/test_regress/t/t_dfg_temp_sharing.v new file mode 100644 index 000000000..27fde05b2 --- /dev/null +++ b/test_regress/t/t_dfg_temp_sharing.v @@ -0,0 +1,108 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Michael Taylor +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module temp_leaf #( + parameter W = 7 +) ( + input clk_i, + input [W-1:0] a_i, + b_i, + c_i, + d_i, + output [W-1:0] comb0_o, + comb1_o, + output [W-3:0] comb2_o, + output logic [W-1:0] state_o = 0 +); + /* verilator no_inline_module */ + typedef logic [W-1:0] word_t; + word_t a_r = 0, b_r = 0, c_r = 0, d_r = 0; + always_ff @(posedge clk_i) begin + a_r <= a_i; + b_r <= b_i; + c_r <= c_i; + d_r <= d_i; + end + // Two live intermediates of the same type must occupy distinct slots. + assign comb0_o = ((a_r + b_r) ^ c_r) + ((a_r + b_r) & d_r); + assign comb1_o = ((a_r ^ b_r) + c_r) ^ ((a_r ^ b_r) | d_r); + // A different type in the same module needs a distinct declaration name, + // even when both types allocate the same slot number. + assign comb2_o = ((a_r[W-3:0] + c_r[W-3:0]) ^ b_r[W-3:0]) + + ((a_r[W-3:0] + c_r[W-3:0]) & d_r[W-3:0]); + always_ff @(negedge clk_i) state_o <= (a_r + b_r) ^ (state_o + c_r); +endmodule + +module t; + for (genvar n = 0; n < 16; ++n) begin : g + localparam W = (n % 4 == 0) ? 7 : (n % 4 == 1) ? 33 : (n % 4 == 2) ? 65 : 95; + typedef logic [W-1:0] word_t; + typedef logic [W-3:0] narrow_t; + bit clk = 0; + word_t a = 0, b = 0, c = 0, d = 0; + wire [W-1:0] comb0, comb1, state_value; + wire [W-3:0] comb2; + temp_leaf #( + .W(W) + ) leaf ( + .clk_i(clk), + .a_i(a), + .b_i(b), + .c_i(c), + .d_i(d), + .comb0_o(comb0), + .comb1_o(comb1), + .comb2_o(comb2), + .state_o(state_value) + ); + initial begin + automatic word_t expected0 = 0, expected1 = 0, expected_state = 0; + automatic narrow_t expected2 = 0; + word_t sum, xored; + narrow_t narrow_sum; + for (int cycle = 0; cycle < 200; ++cycle) begin + a = W'({$random, $random, $random}); + b = W'({$random, $random, $random}); + c = W'({$random, $random, $random}); + d = W'({$random, $random, $random}); + #1; + `checkh(comb0, expected0); + `checkh(comb1, expected1); + `checkh(comb2, expected2); + `checkh(state_value, expected_state); + clk = 1; + sum = a + b; + xored = a ^ b; + expected0 = (sum ^ c) + (sum & d); + expected1 = (xored + c) ^ (xored | d); + narrow_sum = narrow_t'(a) + narrow_t'(c); + expected2 = (narrow_sum ^ narrow_t'(b)) + (narrow_sum & narrow_t'(d)); + #1; + `checkh(comb0, expected0); + `checkh(comb1, expected1); + `checkh(comb2, expected2); + `checkh(state_value, expected_state); + clk = 0; + expected_state = sum ^ (expected_state + c); + #1; + `checkh(comb0, expected0); + `checkh(comb1, expected1); + `checkh(comb2, expected2); + `checkh(state_value, expected_state); + end + end + end + initial begin + #601; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule