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'
|
// Word at given 'wordIndex'
|
||||||
uint64_t word(size_t wordIndex) const { return m_flags[wordIndex]; }
|
uint64_t word(size_t wordIndex) const { return m_flags[wordIndex]; }
|
||||||
|
|
||||||
// Set specified flag to given value
|
// Set specified word to given value
|
||||||
void set(size_t index, bool 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];
|
uint64_t& w = m_flags[index / 64];
|
||||||
const size_t bitIndex = index % 64;
|
const size_t bitIndex = index % 64;
|
||||||
w &= ~(1ULL << bitIndex);
|
w &= ~(1ULL << bitIndex);
|
||||||
|
|
|
||||||
|
|
@ -3018,7 +3018,8 @@ void AstCMethodHard::setPurity() {
|
||||||
{"resume", false},
|
{"resume", false},
|
||||||
{"reverse", false},
|
{"reverse", false},
|
||||||
{"rsort", false},
|
{"rsort", false},
|
||||||
{"set", false},
|
{"setBit", false},
|
||||||
|
{"setWord", false},
|
||||||
{"set_randmode", false},
|
{"set_randmode", false},
|
||||||
{"shuffle", false},
|
{"shuffle", false},
|
||||||
{"size", true},
|
{"size", true},
|
||||||
|
|
|
||||||
|
|
@ -501,7 +501,7 @@ struct TriggerKit final {
|
||||||
void addFirstIterationTriggerAssignment(AstVarScope* flagp, uint32_t index) const {
|
void addFirstIterationTriggerAssignment(AstVarScope* flagp, uint32_t index) const {
|
||||||
FileLine* const flp = flagp->fileline();
|
FileLine* const flp = flagp->fileline();
|
||||||
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
|
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 AstConst{flp, index});
|
||||||
callp->addPinsp(new AstVarRef{flp, flagp, VAccess::READ});
|
callp->addPinsp(new AstVarRef{flp, flagp, VAccess::READ});
|
||||||
callp->dtypeSetVoid();
|
callp->dtypeSetVoid();
|
||||||
|
|
@ -512,7 +512,7 @@ struct TriggerKit final {
|
||||||
void addExtraTriggerAssignment(AstVarScope* extraTriggerVscp, uint32_t index) const {
|
void addExtraTriggerAssignment(AstVarScope* extraTriggerVscp, uint32_t index) const {
|
||||||
FileLine* const flp = extraTriggerVscp->fileline();
|
FileLine* const flp = extraTriggerVscp->fileline();
|
||||||
AstVarRef* const vrefp = new AstVarRef{flp, m_vscp, VAccess::WRITE};
|
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 AstConst{flp, index});
|
||||||
callp->addPinsp(new AstVarRef{flp, extraTriggerVscp, VAccess::READ});
|
callp->addPinsp(new AstVarRef{flp, extraTriggerVscp, VAccess::READ});
|
||||||
callp->dtypeSetVoid();
|
callp->dtypeSetVoid();
|
||||||
|
|
@ -651,9 +651,9 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the given trigger to the given value
|
// 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};
|
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(new AstConst{flp, index});
|
||||||
callp->addPinsp(valp);
|
callp->addPinsp(valp);
|
||||||
callp->dtypeSetVoid();
|
callp->dtypeSetVoid();
|
||||||
|
|
@ -694,9 +694,14 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
|
|
||||||
// Add trigger computation
|
// Add trigger computation
|
||||||
uint32_t triggerNumber = extraTriggers.size();
|
uint32_t triggerNumber = extraTriggers.size();
|
||||||
|
uint32_t triggerBitIdx = triggerNumber;
|
||||||
AstNodeStmt* initialTrigsp = nullptr;
|
AstNodeStmt* initialTrigsp = nullptr;
|
||||||
std::vector<uint32_t> senItemIndex2TriggerIndex;
|
std::vector<uint32_t> senItemIndex2TriggerIndex;
|
||||||
senItemIndex2TriggerIndex.reserve(senItemps.size());
|
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) {
|
for (const AstSenItem* const senItemp : senItemps) {
|
||||||
UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), senItemp,
|
UASSERT_OBJ(senItemp->isClocked() || senItemp->isHybrid(), senItemp,
|
||||||
"Cannot create trigger expression for non-clocked sensitivity");
|
"Cannot create trigger expression for non-clocked sensitivity");
|
||||||
|
|
@ -706,12 +711,12 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
|
|
||||||
// Add the trigger computation
|
// Add the trigger computation
|
||||||
const auto& pair = senExprBuilder.build(senItemp);
|
const auto& pair = senExprBuilder.build(senItemp);
|
||||||
funcp->addStmtsp(setTrig(triggerNumber, pair.first));
|
trigExprps.emplace_back(pair.first);
|
||||||
|
|
||||||
// Add initialization time trigger
|
// Add initialization time trigger
|
||||||
if (pair.second || v3Global.opt.xInitialEdge()) {
|
if (pair.second || v3Global.opt.xInitialEdge()) {
|
||||||
initialTrigsp
|
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
|
// Add a debug statement for this trigger
|
||||||
|
|
@ -723,7 +728,47 @@ const TriggerKit createTriggers(AstNetlist* netlistp, AstCFunc* const initFuncp,
|
||||||
|
|
||||||
//
|
//
|
||||||
++triggerNumber;
|
++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
|
// Construct the map from old SenTrees to new SenTrees
|
||||||
for (const AstSenTree* const senTreep : senTreeps) {
|
for (const AstSenTree* const senTreep : senTreeps) {
|
||||||
|
|
|
||||||
|
|
@ -993,7 +993,7 @@
|
||||||
"stmtsp": [
|
"stmtsp": [
|
||||||
{"type":"STMTEXPR","name":"","addr":"(CP)","loc":"d,11:8,11:9",
|
{"type":"STMTEXPR","name":"","addr":"(CP)","loc":"d,11:8,11:9",
|
||||||
"exprp": [
|
"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": [
|
"fromp": [
|
||||||
{"type":"VARREF","name":"__VactTriggered","addr":"(EP)","loc":"d,11:8,11:9","dtypep":"(NB)","access":"WR","varp":"(U)","varScopep":"UNLINKED","classOrPackagep":"UNLINKED"}
|
{"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_settle"/>
|
||||||
<cfunc loc="a,0,0,0,0" name="_eval_triggers__act">
|
<cfunc loc="a,0,0,0,0" name="_eval_triggers__act">
|
||||||
<stmtexpr loc="d,11,8,11,9">
|
<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"/>
|
<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"/>
|
<const loc="d,11,8,11,9" name="32'h0" dtype_id="14"/>
|
||||||
<and loc="d,61,14,61,21" dtype_id="9">
|
<and loc="d,61,14,61,21" dtype_id="9">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue