mirror of
https://github.com/verilator/verilator.git
synced 2026-10-05 17:43:31 +02:00
Optimize DFG temporary sharing across module instances (#8456)
This commit is contained in:
+27
-19
@@ -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<double>(m_tempDeclarationsReused));
|
||||
forEachVertex([&](DfgVertex& vtx) { vtx.unlinkDelete(*this); });
|
||||
}
|
||||
|
||||
@@ -98,26 +101,31 @@ void DfgGraph::mergeGraphs(std::vector<std::unique_ptr<DfgGraph>>&& 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<std::string, uint32_t> 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<std::string, uint32_t> 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
|
||||
|
||||
+20
-8
@@ -46,6 +46,7 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
@@ -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<AstScope*, size_t> m_scopeCounts; // Next slot for each instance
|
||||
std::vector<AstVar*> m_declps; // Declarations indexed by slot
|
||||
};
|
||||
std::map<AstNodeModule*, std::map<std::pair<std::string, AstNodeDType*>, 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<std::unique_ptr<DfgGraph>>&& 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).
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
+3
-10
@@ -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<DfgVarPacked>();
|
||||
// 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<DfgVarPacked>();
|
||||
@@ -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<DfgVarArray>();
|
||||
}();
|
||||
|
||||
++nTables;
|
||||
++ctx.m_decodersCreated;
|
||||
|
||||
// Initialize 'tab' and 'pre' variables statically
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+6
-17
@@ -58,8 +58,6 @@ DfgArraySel* makeVertex<DfgArraySel, AstArraySel>(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<DfgVertexVar>() && !vtxp->is<DfgConst>()) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Executable
+19
@@ -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()
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user