Internals: Refactor AstNodeBlock representation (#6280) (#6588)

Internals: Refactor AstNodeBlock representation (#6280)

AstNodeBlock now has 2 child lists: 'declsp' to hold declarations within
the block, and 'stmtsp' to hold the procedural statements.

AstBegin is then just a simple subtype of AstNodeBlock.

AstFork is a proper superset of AstNodeBlock (and also AstBegin), and
adds 'forksp' which hold the parallel statements. Having the sequential
'stmtsp' in AstFork is required to properly implement variable
initializers in fork blocks (IEEE 1800-2023 9.3.2), this makes that
clear, while also separating the non AstNodeStmt declarations
(for #6280). The actual fork branches in 'AstFork::forkps()' are all
AstBegin nodes. This is required as lowering stages will introduce
additional statements in each parallel branch. (We used to wrap AstFork
statements into AstBegin in 3 different places, now they always are
AstBegin and this is enforced via the type checker/V3Broken).

Also fixes incorrect disabling of forked processes from within the `fork`.
This commit is contained in:
Geza Lore
2025-10-24 14:00:07 +01:00
committed by GitHub
parent 65c5071246
commit d864057a60
25 changed files with 467 additions and 379 deletions
+21 -22
View File
@@ -731,7 +731,7 @@ class TimingControlVisitor final : public VNVisitor {
= createTemp(flp, forkp->name() + "__sync", getCreateForkSyncDTypep(), insertBeforep);
unsigned joinCount = 0; // Needed for join counter
// Add a <fork sync>.done() to each begin
for (AstNode* beginp = forkp->stmtsp(); beginp; beginp = beginp->nextp()) {
for (AstNode* beginp = forkp->forksp(); beginp; beginp = beginp->nextp()) {
addForkDone(VN_AS(beginp, Begin), forkVscp);
joinCount++;
}
@@ -1025,10 +1025,7 @@ class TimingControlVisitor final : public VNVisitor {
// Put it in a fork so it doesn't block
// Could already be the only thing directly under a fork, reuse that if possible
AstFork* forkp = !nodep->nextp() ? VN_CAST(nodep->firstAbovep(), Fork) : nullptr;
if (!forkp) {
forkp = new AstFork{flp, "", nullptr};
forkp->joinType(VJoinType::JOIN_NONE);
}
if (!forkp) forkp = new AstFork{flp, VJoinType::JOIN_NONE};
if (!m_underProcedure) {
// If it's in a function, it won't be handled by V3Delayed
// Put it behind an additional named event that gets triggered in the NBA region
@@ -1043,7 +1040,7 @@ class TimingControlVisitor final : public VNVisitor {
controlp->replaceWith(forkp);
AstBegin* beginp = VN_CAST(controlp, Begin);
if (!beginp) beginp = new AstBegin{nodep->fileline(), "", controlp, false};
forkp->addStmtsp(beginp);
forkp->addForksp(beginp);
controlp = forkp;
}
UASSERT_OBJ(nodep, controlp, "Assignment should have timing control");
@@ -1115,11 +1112,11 @@ class TimingControlVisitor final : public VNVisitor {
AstNode* const controlp = nodep->timingControlp()->unlinkFrBack();
AstAssign* const assignp = new AstAssign{nodep->fileline(), lhs1p, rhs1p, controlp};
// Put the assignment in a fork..join_none.
AstBegin* const beginp = new AstBegin{flp, "", assignp, false};
AstFork* const forkp = new AstFork{flp, "", beginp};
forkp->joinType(VJoinType::JOIN_NONE);
AstFork* const forkp = new AstFork{flp, VJoinType::JOIN_NONE};
nodep->replaceWith(forkp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
AstBegin* const beginp = new AstBegin{flp, "", assignp, false};
forkp->addForksp(beginp);
visit(forkp); // Visit now as we need to do some post-processing
// IEEE 1800-2023 10.3.3 - if the RHS value differs from the currently scheduled value to
// be assigned, the currently scheduled assignment is descheduled. To keep track if an
@@ -1239,21 +1236,23 @@ class TimingControlVisitor final : public VNVisitor {
void visit(AstFork* nodep) override {
if (nodep->user1SetOnce()) return;
v3Global.setUsesTiming();
// Create a unique name for this fork
nodep->name("__Vfork_" + cvtToStr(++m_forkCnt));
unsigned idx = 0; // Index for naming begins
AstNode* stmtp = nodep->stmtsp();
// Put each statement in a begin
while (stmtp) {
UASSERT_OBJ(VN_IS(stmtp, Begin), nodep,
"All statements under forks must be begins at this point");
AstBegin* const beginp = VN_AS(stmtp, Begin);
stmtp = beginp->nextp();
iterate(beginp);
// Even if we do not find any awaits, we cannot simply inline the process here, as new
// awaits could be added later.
nodep->name("__Vfork_" + std::to_string(++m_forkCnt));
// TODO: Should process nodep->stmtsp() in case an earlier pass
// inserted something there, but as of today we don't need to.
// Process and name each fork
size_t idx = 0; // Index for naming begins
for (AstBegin *itemp = nodep->forksp(), *nextp; itemp; itemp = nextp) {
nextp = VN_AS(itemp->nextp(), Begin);
iterate(itemp);
// Note: Even if we do not find any awaits, we cannot simply inline
// the process here, as new awaits could be added later.
// Name the begin (later the name will be used for a new function)
beginp->name(nodep->name() + "__" + cvtToStr(idx++));
itemp->name(nodep->name() + "__" + std::to_string(idx++));
}
if (!nodep->joinType().joinNone()) makeForkJoin(nodep);
}