Set trigger vector in whole words (#5857)
Having many triggers still hits a bottleneck in LLVM leading to long compile times. Instead of setting triggers bit-wise, set them as a whole 64-bit word when possible. This improves C++ compile times by ~4x on some large designs and has minor run-time performance benefit.
This commit is contained in:
parent
aca3b1636a
commit
59cb53cfbc
|
@ -184,8 +184,11 @@ public:
|
|||
// Word at given 'wordIndex'
|
||||
uint64_t word(size_t wordIndex) const { return m_flags[wordIndex]; }
|
||||
|
||||
// Set specified flag to given value
|
||||
void set(size_t index, bool value) {
|
||||
// Set specified word to given value
|
||||
void setWord(size_t wordIndex, uint64_t value) { m_flags[wordIndex] = value; }
|
||||
|
||||
// Set specified bit to given value
|
||||
void setBit(size_t index, bool value) {
|
||||
uint64_t& w = m_flags[index / 64];
|
||||
const size_t bitIndex = index % 64;
|
||||
w &= ~(1ULL << bitIndex);
|
||||
|
|
|
@ -3018,7 +3018,8 @@ void AstCMethodHard::setPurity() {
|
|||
{"resume", false},
|
||||
{"reverse", false},
|
||||
{"rsort", false},
|
||||
{"set", false},
|
||||
{"setBit", false},
|
||||
{"setWord", false},
|
||||
{"set_randmode", false},
|
||||
{"shuffle", false},
|
||||
{"size", true},
|
||||
|
|
|
@ -501,7 +501,7 @@ struct TriggerKit final {
|
|||
void addFirstIterationTriggerAssignment(AstVarScope* flagp, uint32_t index) const {
|
||||
FileLine* const flp = flagp->fileline();
|
||||
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "setBit"};
|
||||
callp->addPinsp(new AstConst{flp, index});
|
||||
callp->addPinsp(new AstVarRef{flp, flagp, VAccess::READ});
|
||||
callp->dtypeSetVoid();
|
||||
|
@ -512,7 +512,7 @@ struct TriggerKit final {
|
|||
void addExtraTriggerAssignment(AstVarScope* extraTriggerVscp, uint32_t index) const {
|
||||
FileLine* const flp = extraTriggerVscp->fileline();
|
||||
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "setBit"};
|
||||
callp->addPinsp(new AstConst{flp, index});
|
||||
callp->addPinsp(new AstVarRef{flp, extraTriggerVscp, VAccess::READ});
|
||||
callp->dtypeSetVoid();
|
||||
|
@ -651,9 +651,9 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
}
|
||||
|
||||
// Set the given trigger to the given value
|
||||
const auto setTrig = [&](uint32_t index, AstNodeExpr* valp) {
|
||||
const auto setTrigBit = [&](uint32_t index, AstNodeExpr* valp) {
|
||||
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::WRITE};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "set"};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "setBit"};
|
||||
callp->addPinsp(new AstConst{flp, index});
|
||||
callp->addPinsp(valp);
|
||||
callp->dtypeSetVoid();
|
||||
|
@ -694,9 +694,14 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
|
||||
// Add trigger computation
|
||||
uint32_t triggerNumber = extraTriggers.size();
|
||||
uint32_t triggerBitIdx = triggerNumber;
|
||||
AstNodeStmt* initialTrigsp = nullptr;
|
||||
std::vector<uint32_t> senItemIndex2TriggerIndex;
|
||||
senItemIndex2TriggerIndex.reserve(senItemps.size());
|
||||
constexpr uint32_t TRIG_VEC_WORD_SIZE_LOG2 = 6; // 64-bits
|
||||
constexpr uint32_t TRIG_VEC_WORD_SIZE = 1 << TRIG_VEC_WORD_SIZE_LOG2;
|
||||
std::vector<AstNodeExpr*> trigExprps;
|
||||
trigExprps.reserve(TRIG_VEC_WORD_SIZE);
|
||||
for (const AstSenItem* const senItemp : senItemps) {
|
||||
UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), senItemp,
|
||||
"Cannot create trigger expression for non-clocked sensitivity");
|
||||
|
@ -706,12 +711,12 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
|
||||
// Add the trigger computation
|
||||
const auto& pair = senExprBuilder.build(senItemp);
|
||||
funcp->addStmtsp(setTrig(triggerNumber, pair.first));
|
||||
trigExprps.emplace_back(pair.first);
|
||||
|
||||
// Add initialization time trigger
|
||||
if (pair.second || v3Global.opt.xInitialEdge()) {
|
||||
initialTrigsp
|
||||
= AstNode::addNext(initialTrigsp, setTrig(triggerNumber, new AstConst{flp, 1}));
|
||||
= AstNode::addNext(initialTrigsp, setTrigBit(triggerNumber, new AstConst{flp, 1}));
|
||||
}
|
||||
|
||||
// Add a debug statement for this trigger
|
||||
|
@ -723,7 +728,47 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
|||
|
||||
//
|
||||
++triggerNumber;
|
||||
|
||||
// Add statements on every word boundary
|
||||
if (triggerNumber % TRIG_VEC_WORD_SIZE == 0) {
|
||||
if (triggerBitIdx % TRIG_VEC_WORD_SIZE != 0) {
|
||||
// Set leading triggers bit-wise
|
||||
for (AstNodeExpr* const exprp : trigExprps) {
|
||||
funcp->addStmtsp(setTrigBit(triggerBitIdx++, exprp));
|
||||
}
|
||||
} else {
|
||||
// Set whole word as a unit
|
||||
UASSERT_OBJ(triggerNumber == triggerBitIdx + TRIG_VEC_WORD_SIZE, senItemp,
|
||||
"Mismatched index");
|
||||
UASSERT_OBJ(trigExprps.size() == TRIG_VEC_WORD_SIZE, senItemp,
|
||||
"There should be TRIG_VEC_WORD_SIZE expressions");
|
||||
// Concatenate all bits in a tree
|
||||
for (uint32_t level = 0; level < TRIG_VEC_WORD_SIZE_LOG2; ++level) {
|
||||
const uint32_t stride = 1 << level;
|
||||
for (uint32_t i = 0; i < TRIG_VEC_WORD_SIZE; i += 2 * stride) {
|
||||
trigExprps[i] = new AstConcat{trigExprps[i]->fileline(),
|
||||
trigExprps[i + stride], trigExprps[i]};
|
||||
trigExprps[i + stride] = nullptr;
|
||||
}
|
||||
}
|
||||
// Set the whole word in the trigger vector
|
||||
AstVarRef* const vrefp = new AstVarRef{flp, vscp, VAccess::WRITE};
|
||||
AstCMethodHard* const callp = new AstCMethodHard{flp, vrefp, "setWord"};
|
||||
callp->addPinsp(new AstConst{flp, triggerBitIdx / TRIG_VEC_WORD_SIZE});
|
||||
callp->addPinsp(trigExprps[0]);
|
||||
callp->dtypeSetVoid();
|
||||
funcp->addStmtsp(callp->makeStmt());
|
||||
triggerBitIdx += TRIG_VEC_WORD_SIZE;
|
||||
}
|
||||
UASSERT_OBJ(triggerNumber == triggerBitIdx, senItemp, "Mismatched index");
|
||||
trigExprps.clear();
|
||||
}
|
||||
}
|
||||
// Set trailing triggers bit-wise
|
||||
for (AstNodeExpr* const exprp : trigExprps) {
|
||||
funcp->addStmtsp(setTrigBit(triggerBitIdx++, exprp));
|
||||
}
|
||||
trigExprps.clear();
|
||||
|
||||
// Construct the map from old SenTrees to new SenTrees
|
||||
for (const AstSenTree* const senTreep : senTreeps) {
|
||||
|
|
|
@ -993,7 +993,7 @@
|
|||
"stmtsp": [
|
||||
{"type":"STMTEXPR","name":"","addr":"(CP)","loc":"d,11:8,11:9",
|
||||
"exprp": [
|
||||
{"type":"CMETHODHARD","name":"set","addr":"(DP)","loc":"d,11:8,11:9","dtypep":"(CB)",
|
||||
{"type":"CMETHODHARD","name":"setBit","addr":"(DP)","loc":"d,11:8,11:9","dtypep":"(CB)",
|
||||
"fromp": [
|
||||
{"type":"VARREF","name":"__VactTriggered","addr":"(EP)","loc":"d,11:8,11:9","dtypep":"(NB)","access":"WR","varp":"(U)","varScopep":"UNLINKED","classOrPackagep":"UNLINKED"}
|
||||
],
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
#!/usr/bin/env python3
|
||||
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
|
||||
#
|
||||
# Copyright 2025 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.sim_time = 100000
|
||||
|
||||
test.scenarios('simulator')
|
||||
|
||||
test.compile()
|
||||
|
||||
test.execute()
|
||||
|
||||
test.passes()
|
|
@ -0,0 +1,55 @@
|
|||
// DESCRIPTION: Verilator: Verilog Test module
|
||||
//
|
||||
// This file ONLY is placed under the Creative Commons Public Domain, for
|
||||
// any use, without warranty, 2025 by Wilson Snyder.
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
`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);
|
||||
|
||||
module t(/*AUTOARG*/
|
||||
// Inputs
|
||||
clk
|
||||
);
|
||||
|
||||
input clk;
|
||||
|
||||
localparam int ITERATIONS = 5;
|
||||
localparam int N = 227;
|
||||
|
||||
logic [N-1:0] gclk = {N{1'b0}};
|
||||
|
||||
// Not actually used, but creates an extra internal trigger
|
||||
export "DPI-C" function toggle;
|
||||
function void toggle();
|
||||
gclk = ~gclk;
|
||||
endfunction
|
||||
|
||||
int cyc = 0;
|
||||
always @(posedge clk) begin
|
||||
if (~|gclk) begin
|
||||
gclk[0] = 1'b1;
|
||||
end else begin
|
||||
gclk = {gclk[N-2:0], gclk[N-1]};
|
||||
end
|
||||
|
||||
cyc <= cyc + 32'd1;
|
||||
if (cyc == ITERATIONS*N - 1) begin
|
||||
$display("cyc");
|
||||
$write("*-* All Finished *-*\n");
|
||||
$finish;
|
||||
end
|
||||
end
|
||||
|
||||
for (genvar n = 0; n < N; n++) begin : gen
|
||||
int cnt = 0;
|
||||
always @(posedge gclk[n]) cnt <= cnt + 1;
|
||||
|
||||
wire int cnt_plus_one = cnt + 1;
|
||||
|
||||
final begin
|
||||
`checkh(cnt_plus_one, ITERATIONS + 1);
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
|
@ -601,7 +601,7 @@
|
|||
<cfunc loc="a,0,0,0,0" name="_eval_settle"/>
|
||||
<cfunc loc="a,0,0,0,0" name="_eval_triggers__act">
|
||||
<stmtexpr loc="d,11,8,11,9">
|
||||
<cmethodhard loc="d,11,8,11,9" name="set" dtype_id="7">
|
||||
<cmethodhard loc="d,11,8,11,9" name="setBit" dtype_id="7">
|
||||
<varref loc="d,11,8,11,9" name="__VactTriggered" dtype_id="9"/>
|
||||
<const loc="d,11,8,11,9" name="32'h0" dtype_id="14"/>
|
||||
<and loc="d,61,14,61,21" dtype_id="9">
|
||||
|
|
Loading…
Reference in New Issue