Support clocking blocks (#3674)

This commit is contained in:
Krzysztof Bieganski
2022-12-23 07:34:49 -05:00
committed by GitHub
parent d64971ba35
commit bb44d4e4f2
58 changed files with 1817 additions and 218 deletions
+25 -4
View File
@@ -243,16 +243,21 @@ public:
// METHODS
AstScope* scopep() { return m_scopep; }
// Make a new AstActive sensitive to the given special sensitivity class and return it
template <typename SenItemKind>
AstActive* makeSpecialActive(FileLine* const fl) {
AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}};
// Make a new AstActive sensitive to the given sentree and return it
AstActive* makeActive(FileLine* const fl, AstSenTree* const senTreep) {
auto* const activep = new AstActive{fl, "", senTreep};
activep->sensesStorep(activep->sensesp());
addActive(activep);
return activep;
}
// Make a new AstActive sensitive to the given special sensitivity class and return it
template <typename SenItemKind>
AstActive* makeSpecialActive(FileLine* const fl) {
AstSenTree* const senTreep = new AstSenTree{fl, new AstSenItem{fl, SenItemKind{}}};
return makeActive(fl, senTreep);
}
// Return an AstActive sensitive to the given special sensitivity class (possibly pre-created)
template <typename SenItemKind>
AstActive* getSpecialActive(FileLine* fl) {
@@ -542,6 +547,22 @@ private:
AstActive* const activep = m_namer.makeSpecialActive<AstSenItem::Combo>(nodep->fileline());
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysObserved* nodep) override {
UASSERT_OBJ(nodep->sensesp(), nodep, "Should have a sentree");
AstSenTree* const sensesp = nodep->sensesp();
sensesp->unlinkFrBack();
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sensesp);
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysReactive* nodep) override {
UASSERT_OBJ(nodep->sensesp(), nodep, "Should have a sentree");
AstSenTree* const sensesp = nodep->sensesp();
sensesp->unlinkFrBack();
// Make a new active for it, needs to be the only item under the active for V3Sched
AstActive* const activep = m_namer.makeActive(nodep->fileline(), sensesp);
activep->addStmtsp(nodep->unlinkFrBack());
}
void visit(AstAlwaysPublic* nodep) override {
visitAlways(nodep, nodep->sensesp(), VAlwaysKwd::ALWAYS);
}
+222 -15
View File
@@ -16,6 +16,7 @@
// Pre steps:
// Attach clocks to each assertion
// Substitute property references by property body (IEEE Std 1800-2012, section 16.12.1).
// Transform clocking blocks into imperative logic
//*************************************************************************
#include "config_build.h"
@@ -24,8 +25,10 @@
#include "V3AssertPre.h"
#include "V3Ast.h"
#include "V3Const.h"
#include "V3Global.h"
#include "V3Task.h"
#include "V3UniqueNames.h"
VL_DEFINE_DEBUG_FUNCTIONS;
@@ -37,16 +40,26 @@ class AssertPreVisitor final : public VNVisitor {
// Eventually inlines calls to sequences, properties, etc.
// We're not parsing the tree, or anything more complicated.
private:
// NODE STATE/TYPES
// NODE STATE
const VNUser1InUse m_inuser1;
// STATE
// Current context:
AstNetlist* const m_netlistp = nullptr; // Current netlist
AstNodeModule* m_modp = nullptr; // Current module
AstClocking* m_clockingp = nullptr; // Current clocking block
// Reset each module:
AstSenItem* m_seniDefaultp = nullptr; // Default sensitivity (from AstDefClock)
AstClocking* m_defaultClockingp = nullptr; // Default clocking for the current module
// Reset each assertion:
AstSenItem* m_senip = nullptr; // Last sensitivity
// Reset each always:
AstSenItem* m_seniAlwaysp = nullptr; // Last sensitivity in always
// Reset each assertion:
AstNodeExpr* m_disablep = nullptr; // Last disable
// Other:
V3UniqueNames m_cycleDlyNames{"__VcycleDly"}; // Cycle delay counter name generator
bool m_inAssign = false; // True if in an AssignNode
bool m_inAssignDlyLhs = false; // True if in AssignDly's LHS
bool m_inSynchDrive = false; // True if in synchronous drive
// METHODS
@@ -55,7 +68,7 @@ private:
// Return nullptr for always
AstSenTree* newp = nullptr;
AstSenItem* senip = m_senip;
if (!senip) senip = m_seniDefaultp;
if (!senip && m_defaultClockingp) senip = m_defaultClockingp->sensesp();
if (!senip) senip = m_seniAlwaysp;
if (!senip) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Unclocked assertion");
@@ -139,17 +152,199 @@ private:
// VISITORS
//========== Statements
void visit(AstClocking* nodep) override {
void visit(AstClocking* const nodep) override {
VL_RESTORER(m_clockingp);
m_clockingp = nodep;
UINFO(8, " CLOCKING" << nodep << endl);
// Store the new default clock, reset on new module
m_seniDefaultp = nodep->sensesp();
// Trash it, keeping children
if (nodep->bodysp()) {
nodep->replaceWith(nodep->bodysp()->unlinkFrBack());
} else {
nodep->unlinkFrBack();
iterateChildren(nodep);
}
void visit(AstClockingItem* const nodep) override {
FileLine* const flp = nodep->fileline();
V3Const::constifyEdit(nodep->skewp());
if (!VN_IS(nodep->skewp(), Const)) {
nodep->skewp()->v3error("Skew must be constant (IEEE 1800-2017 14.4)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
AstConst* const skewp = VN_AS(nodep->skewp(), Const);
if (skewp->num().isNegative()) skewp->v3error("Skew cannot be negative");
AstNodeExpr* const exprp = nodep->exprp();
// Get a ref to the sampled/driven variable
AstVar* const varp = nodep->varp()->unlinkFrBack();
m_clockingp->addVarsp(varp);
varp->user1p(nodep);
if (nodep->direction() == VDirection::OUTPUT) {
AstVarRef* const skewedRefp = new AstVarRef{flp, varp, VAccess::READ};
skewedRefp->user1(true);
AstAssign* const assignp = new AstAssign{flp, exprp->cloneTree(false), skewedRefp};
if (skewp->isZero()) {
// Drive the var in Re-NBA (IEEE 1800-2017 14.16)
m_clockingp->addNextHere(new AstAlwaysReactive{
flp, new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, assignp});
} else if (skewp->fileline()->timingOn()) {
// Create a fork so that this AlwaysObserved can be retriggered before the
// assignment happens. Also then it can be combo, avoiding the need for creating
// new triggers.
AstFork* const forkp = new AstFork{flp, "", assignp};
forkp->joinType(VJoinType::JOIN_NONE);
// Use Observed for this to make sure we do not miss the event
m_clockingp->addNextHere(new AstAlwaysObserved{
flp, new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, forkp});
if (v3Global.opt.timing().isSetTrue()) {
assignp->timingControlp(new AstDelay{flp, skewp->unlinkFrBack(), false});
} else if (v3Global.opt.timing().isSetFalse()) {
nodep->v3warn(E_NOTIMING,
"Clocking output skew greater than #0 requires --timing");
} else {
nodep->v3warn(E_NEEDTIMINGOPT,
"Use --timing or --no-timing to specify how "
"clocking output skew greater than #0 should be handled");
}
}
} else if (nodep->direction() == VDirection::INPUT) {
// Ref to the clockvar
AstVarRef* const refp = new AstVarRef{flp, varp, VAccess::WRITE};
refp->user1(true);
if (skewp->num().is1Step()) {
// Assign the sampled expression to the clockvar (IEEE 1800-2017 14.13)
AstSampled* const sampledp = new AstSampled{flp, exprp->cloneTree(false)};
sampledp->dtypeFrom(exprp);
m_clockingp->addNextHere(new AstAssignW{flp, refp, sampledp});
} else if (skewp->isZero()) {
// #0 means the var has to be sampled in Observed (IEEE 1800-2017 14.13)
AstAssign* const assignp = new AstAssign{flp, refp, exprp->cloneTree(false)};
m_clockingp->addNextHere(new AstAlwaysObserved{
flp, new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)}, assignp});
} else {
// Create a queue where we'll store sampled values with timestamps
AstSampleQueueDType* const queueDtp
= new AstSampleQueueDType{flp, exprp->dtypep()};
m_netlistp->typeTablep()->addTypesp(queueDtp);
AstVar* const queueVarp = new AstVar{
flp, VVarType::MODULETEMP,
"__Vqueue__" + m_clockingp->name() + "__DOT__" + varp->name(), queueDtp};
m_clockingp->addNextHere(queueVarp);
// Create a process like this:
// always queue.push(<sampled var>);
AstCMethodHard* const pushp = new AstCMethodHard{
flp, new AstVarRef{flp, queueVarp, VAccess::WRITE}, "push",
new AstTime(nodep->fileline(), m_modp->timeunit())};
pushp->addPinsp(exprp->cloneTree(false));
pushp->dtypeSetVoid();
m_clockingp->addNextHere(
new AstAlways{flp, VAlwaysKwd::ALWAYS, nullptr, pushp->makeStmt()});
// Create a process like this:
// always @<clocking event> queue.pop(<skew>, /*out*/<skewed var>});
AstCMethodHard* const popp = new AstCMethodHard{
flp, new AstVarRef{flp, queueVarp, VAccess::READWRITE}, "pop",
new AstTime(nodep->fileline(), m_modp->timeunit())};
popp->addPinsp(skewp->unlinkFrBack());
popp->addPinsp(refp);
popp->dtypeSetVoid();
m_clockingp->addNextHere(
new AstAlways{flp, VAlwaysKwd::ALWAYS,
new AstSenTree{flp, m_clockingp->sensesp()->cloneTree(false)},
popp->makeStmt()});
}
} else {
nodep->v3fatal("Invalid direction");
}
pushDeletep(nodep->unlinkFrBack());
}
void visit(AstDelay* nodep) override {
// Only cycle delays are relevant in this stage; also only process once
if (!nodep->isCycleDelay()) {
if (m_inSynchDrive) {
nodep->v3error("Only cycle delays can be used in synchronous drives"
" (IEEE 1800-2017 14.16)");
}
return;
}
if (m_inAssign && !m_inSynchDrive) {
nodep->v3error("Cycle delays not allowed as intra-assignment delays"
" (IEEE 1800-2017 14.11)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
if (nodep->stmtsp()) nodep->addNextHere(nodep->stmtsp()->unlinkFrBackWithNext());
FileLine* const flp = nodep->fileline();
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
AstConst* const constp = VN_CAST(valuep, Const);
if (constp->isZero()) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: ##0 delays");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
if (!m_defaultClockingp) {
nodep->v3error("Usage of cycle delays requires default clocking"
" (IEEE 1800-2017 14.11)");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
AstEventControl* const controlp = new AstEventControl{
nodep->fileline(),
new AstSenTree{flp, m_defaultClockingp->sensesp()->cloneTree(false)}, nullptr};
const std::string delayName = m_cycleDlyNames.get(nodep);
AstVar* const cntVarp = new AstVar{flp, VVarType::BLOCKTEMP, delayName + "__counter",
nodep->findBasicDType(VBasicDTypeKwd::UINT32)};
AstBegin* const beginp = new AstBegin{flp, delayName + "__block", cntVarp, false, true};
beginp->addStmtsp(new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE}, valuep});
beginp->addStmtsp(new AstWhile{
nodep->fileline(),
new AstGt{flp, new AstVarRef{flp, cntVarp, VAccess::READ}, new AstConst{flp, 0}},
controlp,
new AstAssign{flp, new AstVarRef{flp, cntVarp, VAccess::WRITE},
new AstSub{flp, new AstVarRef{flp, cntVarp, VAccess::READ},
new AstConst{flp, 1}}}});
nodep->replaceWith(beginp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
void visit(AstSenTree* nodep) override {
if (m_inSynchDrive) {
nodep->v3error("Event controls cannot be used in "
"synchronous drives (IEEE 1800-2017 14.16)");
}
}
void visit(AstNodeVarRef* nodep) override {
if (AstClockingItem* const itemp = VN_CAST(nodep->varp()->user1p(), ClockingItem)) {
if (nodep->access().isReadOrRW() && !nodep->user1()
&& itemp->direction() == VDirection::OUTPUT) {
nodep->v3error("Cannot read from output clockvar (IEEE 1800-2017 14.3)");
}
if (nodep->access().isWriteOrRW()) {
if (itemp->direction() == VDirection::OUTPUT) {
if (!m_inAssignDlyLhs) {
nodep->v3error("Only non-blocking assignments can write "
"to clockvars (IEEE 1800-2017 14.16)");
}
if (m_inAssign) m_inSynchDrive = true;
} else if (!nodep->user1() && itemp->direction() == VDirection::INPUT) {
nodep->v3error("Cannot write to input clockvar (IEEE 1800-2017 14.3)");
}
}
}
}
void visit(AstNodeAssign* nodep) override {
if (nodep->user1()) return;
VL_RESTORER(m_inAssign);
VL_RESTORER(m_inSynchDrive);
m_inAssign = true;
m_inSynchDrive = false;
{
VL_RESTORER(m_inAssignDlyLhs);
m_inAssignDlyLhs = VN_IS(nodep, AssignDly);
iterate(nodep->lhsp());
}
iterate(nodep->rhsp());
if (nodep->timingControlp()) {
iterate(nodep->timingControlp());
} else if (m_inSynchDrive) {
AstAssign* const assignp = new AstAssign{
nodep->fileline(), nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()};
assignp->user1(true);
nodep->replaceWith(assignp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
}
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstAlways* nodep) override {
iterateAndNextNull(nodep->sensesp());
@@ -255,9 +450,20 @@ private:
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_defaultClockingp);
VL_RESTORER(m_modp);
m_defaultClockingp = nullptr;
nodep->foreach([&](AstClocking* const clockingp) {
if (clockingp->isDefault()) {
if (m_defaultClockingp) {
clockingp->v3error("Only one default clocking block allowed per module"
" (IEEE 1800-2017 14.12)");
}
m_defaultClockingp = clockingp;
}
});
m_modp = nodep;
iterateChildren(nodep);
// Reset defaults
m_seniDefaultp = nullptr;
}
void visit(AstProperty* nodep) override {
// The body will be visited when will be substituted in place of property reference
@@ -268,7 +474,8 @@ private:
public:
// CONSTRUCTORS
explicit AssertPreVisitor(AstNetlist* nodep) {
explicit AssertPreVisitor(AstNetlist* nodep)
: m_netlistp{nodep} {
clearAssertInfo();
// Process
iterate(nodep);
+44
View File
@@ -1122,6 +1122,50 @@ public:
return false;
}
};
class AstSampleQueueDType final : public AstNodeDType {
// @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width
AstNodeDType* m_refDTypep = nullptr; // Elements of this type (after widthing)
public:
AstSampleQueueDType(FileLine* fl, AstNodeDType* dtp)
: ASTGEN_SUPER_SampleQueueDType(fl) {
refDTypep(dtp);
dtypep(dtp);
}
ASTGEN_MEMBERS_AstSampleQueueDType;
const char* broken() const override {
BROKEN_RTN(!((m_refDTypep && !childDTypep() && m_refDTypep->brokeExists())
|| (!m_refDTypep && childDTypep())));
return nullptr;
}
void cloneRelink() override {
if (m_refDTypep && m_refDTypep->clonep()) m_refDTypep = m_refDTypep->clonep();
}
bool same(const AstNode* samep) const override {
const AstNodeArrayDType* const asamep = static_cast<const AstNodeArrayDType*>(samep);
if (!asamep->subDTypep()) return false;
return (subDTypep() == asamep->subDTypep());
}
bool similarDType(const AstNodeDType* samep) const override {
const AstSampleQueueDType* const asamep = static_cast<const AstSampleQueueDType*>(samep);
return type() == samep->type() && asamep->subDTypep()
&& subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp());
}
void dumpSmall(std::ostream& str) const override;
AstNodeDType* getChildDTypep() const override { return childDTypep(); }
// op1 = Range of variable
AstNodeDType* subDTypep() const override { return m_refDTypep ? m_refDTypep : childDTypep(); }
void refDTypep(AstNodeDType* nodep) { m_refDTypep = nodep; }
AstNodeDType* virtRefDTypep() const override { return m_refDTypep; }
void virtRefDTypep(AstNodeDType* nodep) override { refDTypep(nodep); }
// METHODS
AstBasicDType* basicp() const override { return subDTypep()->basicp(); }
AstNodeDType* skipRefp() const override { return (AstNodeDType*)this; }
AstNodeDType* skipRefToConstp() const override { return (AstNodeDType*)this; }
AstNodeDType* skipRefToEnump() const override { return (AstNodeDType*)this; }
int widthAlignBytes() const override { return sizeof(std::map<std::string, std::string>); }
int widthTotalBytes() const override { return sizeof(std::map<std::string, std::string>); }
bool isCompound() const override { return true; }
};
class AstUnsizedArrayDType final : public AstNodeDType {
// Unsized/open-range Array data type, ie "some_dtype var_name []"
// @astgen op1 := childDTypep : Optional[AstNodeDType] // moved to refDTypep() in V3Width
+7
View File
@@ -936,6 +936,13 @@ public:
dtypeSetBit(); // Events 1 bit, objects 64 bits, so autoExtend=1 and use bit here
initWithNumber();
}
class OneStep {};
AstConst(FileLine* fl, OneStep)
: ASTGEN_SUPER_Const(fl)
, m_num(V3Number::OneStep{}, this) {
dtypeSetLogicSized(64, VSigning::UNSIGNED);
initWithNumber();
}
ASTGEN_MEMBERS_AstConst;
string name() const override { return num().ascii(); } // * = Value
const V3Number& num() const VL_MT_SAFE { return m_num; } // * = Value
+69 -9
View File
@@ -814,17 +814,50 @@ public:
AstClass* classp() const; // Class being extended (after link)
};
class AstClocking final : public AstNode {
// Set default clock region
// Parents: MODULE
// @astgen op1 := sensesp : List[AstSenItem]
// @astgen op2 := bodysp : List[AstNode]
// Children: SENITEM, CLOCKING ITEMs, VARs
// @astgen op1 := sensesp : AstSenItem
// @astgen op2 := itemsp : List[AstClockingItem]
// @astgen op3 := varsp : List[AstVar]
// @astgen op4 := eventp : Optional[AstVar]
std::string m_name; // Clocking block name
const bool m_isDefault = false; // True if default clocking
public:
AstClocking(FileLine* fl, AstSenItem* sensesp, AstNode* bodysp)
: ASTGEN_SUPER_Clocking(fl) {
this->addSensesp(sensesp);
this->addBodysp(bodysp);
AstClocking(FileLine* fl, const std::string& name, AstSenItem* sensesp,
AstClockingItem* itemsp, bool isDefault)
: ASTGEN_SUPER_Clocking(fl)
, m_isDefault{isDefault} {
m_name = name;
this->sensesp(sensesp);
addItemsp(itemsp);
}
ASTGEN_MEMBERS_AstClocking;
std::string name() const override { return m_name; }
bool isDefault() const { return m_isDefault; }
};
class AstClockingItem final : public AstNode {
// Parents: CLOCKING
// Children: EXPRs, ASSIGNs, VARs
// @astgen op1 := skewp : Optional[AstNodeExpr]
// @astgen op2 := exprp : Optional[AstNodeExpr]
// @astgen op3 := assignp : Optional[AstAssign]
// @astgen op4 := varp : Optional[AstVar]
VDirection m_direction;
public:
AstClockingItem(FileLine* fl, VDirection direction, AstNodeExpr* skewp, AstNode* clockingDeclp)
: ASTGEN_SUPER_ClockingItem(fl) {
m_direction = direction;
this->skewp(skewp);
if (AstAssign* const assignp = VN_CAST(clockingDeclp, Assign)) {
this->assignp(assignp);
} else {
exprp(VN_AS(clockingDeclp, NodeExpr));
}
}
ASTGEN_MEMBERS_AstClockingItem;
VDirection direction() const { return m_direction; }
};
class AstConstPool final : public AstNode {
// Container for const static data
@@ -2182,6 +2215,17 @@ public:
void dump(std::ostream& str) const override;
VAlwaysKwd keyword() const { return m_keyword; }
};
class AstAlwaysObserved final : public AstNodeProcedure {
// Like always but Observed scheduling region
// @astgen op1 := sensesp : Optional[AstSenTree] // Sensitivity list, removed in V3Active
public:
AstAlwaysObserved(FileLine* fl, AstSenTree* sensesp, AstNode* bodysp)
: ASTGEN_SUPER_AlwaysObserved(fl, bodysp) {
this->sensesp(sensesp);
}
ASTGEN_MEMBERS_AstAlwaysObserved;
};
class AstAlwaysPost final : public AstNodeProcedure {
// Like always but post assignments for memory assignment IFs
// @astgen op1 := sensesp : Optional[AstSenTree] // Sensitivity list iff clocked
@@ -2200,6 +2244,17 @@ public:
: ASTGEN_SUPER_AlwaysPostponed(fl, stmtsp) {}
ASTGEN_MEMBERS_AstAlwaysPostponed;
};
class AstAlwaysReactive final : public AstNodeProcedure {
// Like always but Reactive scheduling region
// @astgen op1 := sensesp : Optional[AstSenTree] // Sensitivity list, removed in V3Active
public:
AstAlwaysReactive(FileLine* fl, AstSenTree* sensesp, AstNode* bodysp)
: ASTGEN_SUPER_AlwaysReactive(fl, bodysp) {
this->sensesp(sensesp);
}
ASTGEN_MEMBERS_AstAlwaysReactive;
};
class AstFinal final : public AstNodeProcedure {
public:
AstFinal(FileLine* fl, AstNode* stmtsp)
@@ -2496,13 +2551,18 @@ class AstDelay final : public AstNodeStmt {
// Delay statement
// @astgen op1 := lhsp : AstNodeExpr // Delay value
// @astgen op2 := stmtsp : List[AstNode] // Statements under delay
const bool m_isCycle; // True if it is a cycle delay
public:
AstDelay(FileLine* fl, AstNodeExpr* lhsp)
: ASTGEN_SUPER_Delay(fl) {
AstDelay(FileLine* fl, AstNodeExpr* lhsp, bool isCycle)
: ASTGEN_SUPER_Delay(fl)
, m_isCycle{isCycle} {
this->lhsp(lhsp);
}
ASTGEN_MEMBERS_AstDelay;
void dump(std::ostream& str) const override;
bool isTimingControl() const override { return true; }
bool isCycleDelay() const { return m_isCycle; }
bool same(const AstNode* /*samep*/) const override { return true; }
};
class AstDisable final : public AstNodeStmt {
+12
View File
@@ -724,6 +724,9 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound) const {
// + 1 below as VlQueue uses 0 to mean unlimited, 1 to mean size() max is 1
if (adtypep->boundp()) info.m_type += ", " + cvtToStr(adtypep->boundConst() + 1);
info.m_type += ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, SampleQueueDType)) {
const CTypeRecursed sub = adtypep->subDTypep()->cTypeRecurse(true);
info.m_type = "VlSampleQueue<" + sub.m_type + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) {
info.m_type = "VlClassRef<" + EmitCBaseVisitor::prefixNameProtect(adtypep) + ">";
} else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) {
@@ -2002,6 +2005,10 @@ bool AstWildcardArrayDType::similarDType(const AstNodeDType* samep) const {
return type() == samep->type() && asamep->subDTypep()
&& subDTypep()->skipRefp()->similarDType(asamep->subDTypep()->skipRefp());
}
void AstSampleQueueDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[*]";
}
void AstUnsizedArrayDType::dumpSmall(std::ostream& str) const {
this->AstNodeDType::dumpSmall(str);
str << "[]";
@@ -2323,3 +2330,8 @@ AstAlways* AstAssignW::convertToAlways() {
replaceWith(newp); // User expected to then deleteTree();
return newp;
}
void AstDelay::dump(std::ostream& str) const {
this->AstNodeStmt::dump(str);
if (isCycleDelay()) str << " [CYCLE]";
}
+17 -11
View File
@@ -70,7 +70,7 @@ private:
string m_namedScope; // Name of begin blocks above us
string m_unnamedScope; // Name of begin blocks, including unnamed blocks
int m_ifDepth = 0; // Current if depth
bool m_underFork = false; // True if the current statement is directly under a fork
bool m_keepBegins = false; // True if begins should not be inlined
// METHODS
@@ -123,11 +123,18 @@ private:
// VISITORS
void visit(AstFork* nodep) override {
VL_RESTORER(m_underFork);
m_underFork = true;
// Keep this begin to group its statements together
VL_RESTORER(m_keepBegins);
m_keepBegins = true;
dotNames(nodep, "__FORK__");
nodep->name("");
}
void visit(AstNodeAssign* nodep) override {
// Keep begin under assignment (in nodep->timingControlp())
VL_RESTORER(m_keepBegins);
m_keepBegins = true;
iterateChildren(nodep);
}
void visit(AstNodeModule* nodep) override {
VL_RESTORER(m_modp);
{
@@ -178,15 +185,14 @@ private:
VL_RESTORER(m_unnamedScope);
{
{
VL_RESTORER(m_underFork);
m_underFork = false;
VL_RESTORER(m_keepBegins);
m_keepBegins = false;
dotNames(nodep, "__BEGIN__");
}
UASSERT_OBJ(!nodep->genforp(), nodep, "GENFORs should have been expanded earlier");
// Cleanup
if (m_underFork) {
// If we're under a fork, keep this begin to group its statements together
if (m_keepBegins) {
nodep->name("");
return;
}
@@ -263,8 +269,8 @@ private:
}
// VISITORS - LINT CHECK
void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered
VL_RESTORER(m_underFork);
m_underFork = false;
VL_RESTORER(m_keepBegins);
m_keepBegins = false;
// Check IFDEPTH warning - could be in other transform files if desire
VL_RESTORER(m_ifDepth);
if (m_ifDepth == -1 || v3Global.opt.ifDepth() < 1) { // Turned off
@@ -279,8 +285,8 @@ private:
iterateChildren(nodep);
}
void visit(AstNode* nodep) override {
VL_RESTORER(m_underFork);
m_underFork = false;
VL_RESTORER(m_keepBegins);
m_keepBegins = false;
iterateChildren(nodep);
}
+5
View File
@@ -302,6 +302,11 @@ private:
}
//-----
void visit(AstClockingItem* nodep) override {
// Prevent V3Dead from deleting clockvars that are seemingly dead before V3AssertPre. Later
// the vars will be moved to the containing module so if they are actually dead they will
// still get deleted.
}
void visit(AstNode* nodep) override {
if (nodep->isOutputter()) m_sideEffect = true;
iterateChildren(nodep);
+1
View File
@@ -97,6 +97,7 @@ public:
return v3Global.opt.compLimitMembers() != 0 // Enabled
&& !varp->isStatic() // Not a static variable
&& !varp->isSc() // Aggregates can't be anon
&& !VN_IS(varp->dtypep()->skipRefp(), SampleQueueDType) // Aggregates can't be anon
&& (varp->basicp() && !varp->basicp()->isOpaque()); // Aggregates can't be anon
}
+2
View File
@@ -660,6 +660,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, const string& varNameP
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
return emitVarResetRecurse(varp, varNameProtected, adtypep->subDTypep(), depth + 1,
suffix + ".atDefault()" + cvtarray);
} else if (VN_IS(dtypep, SampleQueueDType)) {
return "";
} else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) {
UASSERT_OBJ(adtypep->hi() >= adtypep->lo(), varp,
"Should have swapped msb & lsb earlier.");
+1
View File
@@ -510,6 +510,7 @@ private:
void visit(AstCFunc* nodep) override {
iterateNewStmt(nodep, "User C Function", "User C Function");
}
void visit(AstClocking* nodep) override { iterateNewStmt(nodep, nullptr, nullptr); }
void visit(AstSenItem* nodep) override {
m_inSenItem = true;
if (m_logicVertexp) { // Already under logic; presumably a SenGate
+81
View File
@@ -740,6 +740,7 @@ class LinkDotFindVisitor final : public VNVisitor {
// STATE
LinkDotState* const m_statep; // State to pass between visitors, including symbol table
AstNodeModule* m_classOrPackagep = nullptr; // Current package
AstClocking* m_clockingp = nullptr; // Current clocking block
VSymEnt* m_modSymp = nullptr; // Symbol Entry for current module
VSymEnt* m_curSymp = nullptr; // Symbol Entry for current table, where to lookup/insert
string m_scope; // Scope text
@@ -1123,6 +1124,49 @@ class LinkDotFindVisitor final : public VNVisitor {
m_ftaskp = nullptr;
}
}
void visit(AstClocking* nodep) override {
VL_RESTORER(m_clockingp);
m_clockingp = nodep;
iterate(nodep->sensesp());
iterateAndNextNull(nodep->itemsp());
// If the block has no name, one cannot reference the clockvars
if (nodep->name().empty()) return;
VL_RESTORER(m_curSymp);
m_curSymp = m_statep->insertBlock(m_curSymp, nodep->name(), nodep, m_classOrPackagep);
m_curSymp->fallbackp(nullptr);
iterateAndNextNull(nodep->itemsp());
}
void visit(AstClockingItem* nodep) override {
if (nodep->varp()) {
if (m_curSymp->nodep() == m_clockingp) iterate(nodep->varp());
return;
}
std::string varname;
AstNodeDType* dtypep;
if (AstAssign* const assignp = nodep->assignp()) {
AstNodeExpr* const rhsp = assignp->rhsp()->unlinkFrBack();
dtypep = new AstRefDType{nodep->fileline(), AstRefDType::FlagTypeOfExpr{},
rhsp->cloneTree(false)};
nodep->exprp(rhsp);
varname = assignp->lhsp()->name();
VL_DO_DANGLING(assignp->unlinkFrBack()->deleteTree(), assignp);
} else {
AstNodeExpr* const refp = nodep->exprp();
const VSymEnt* foundp = m_curSymp->findIdFallback(refp->name());
if (!foundp || !foundp->nodep()) {
refp->v3error("Corresponding variable " << refp->prettyNameQ()
<< " does not exist");
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
return;
}
varname = refp->name();
dtypep = VN_AS(foundp->nodep(), Var)->childDTypep()->cloneTree(false);
}
AstVar* const newvarp = new AstVar{nodep->fileline(), VVarType::MODULETEMP, varname,
VFlagChildDType{}, dtypep};
nodep->varp(newvarp);
iterate(nodep->exprp());
}
void visit(AstVar* nodep) override {
// Var: Remember its name for later resolution
UASSERT_OBJ(m_curSymp && m_modSymp, nodep, "Var not under module?");
@@ -1150,6 +1194,10 @@ class LinkDotFindVisitor final : public VNVisitor {
<< cvtToHex(foundp->parentp()) << endl);
if (foundp->parentp() == m_curSymp // Only when on same level
&& !foundp->imported()) { // and not from package
if (VN_IS(m_curSymp->nodep(), Clocking)) {
nodep->v3error("Multiple clockvars with the same name not allowed");
return;
}
const bool nansiBad
= ((findvarp->isDeclTyped() && nodep->isDeclTyped())
|| (findvarp->isIO() && nodep->isIO())); // e.g. !(output && output)
@@ -1946,6 +1994,7 @@ private:
AstNodeModule* m_modp = nullptr; // Current module
AstNodeFTask* m_ftaskp = nullptr; // Current function/task
int m_modportNum = 0; // Uniqueify modport numbers
bool m_inSens = false; // True if in senitem
struct DotStates {
DotPosition m_dotPos; // Scope part of dotted resolution
@@ -2062,6 +2111,24 @@ private:
refp->user5p(nodep);
}
}
VSymEnt* getCreateClockingEventSymEnt(AstClocking* clockingp) {
if (!clockingp->eventp()) {
AstVar* const eventp = new AstVar{
clockingp->fileline(), VVarType::MODULETEMP, clockingp->name(), VFlagChildDType{},
new AstBasicDType{clockingp->fileline(), VBasicDTypeKwd::EVENT}};
clockingp->eventp(eventp);
// Trigger the clocking event in Observed (IEEE 1800-2017 14.13)
clockingp->addNextHere(new AstAlwaysObserved{
clockingp->fileline(),
new AstSenTree{clockingp->fileline(), clockingp->sensesp()->cloneTree(false)},
new AstFireEvent{clockingp->fileline(),
new AstVarRef{clockingp->fileline(), eventp, VAccess::WRITE},
false}});
v3Global.setHasEvents();
eventp->user1p(new VSymEnt{m_statep->symsp(), eventp});
}
return reinterpret_cast<VSymEnt*>(clockingp->eventp()->user1p());
}
bool isParamedClassRef(const AstNode* nodep) {
// Is this a parametrized reference to a class, or a reference to class parameter
@@ -2311,6 +2378,11 @@ private:
m_ds.m_dotp = lastStates.m_dotp;
}
}
void visit(AstSenItem* nodep) override {
VL_RESTORER(m_inSens);
m_inSens = true;
iterateChildren(nodep);
}
void visit(AstParseRef* nodep) override {
if (nodep->user3SetOnce()) return;
UINFO(9, " linkPARSEREF " << m_ds.ascii() << " n=" << nodep << endl);
@@ -2417,6 +2489,12 @@ private:
}
// What fell out?
bool ok = false;
// Special case: waiting on clocking event
if (m_inSens && foundp && m_ds.m_dotPos != DP_SCOPE) {
if (AstClocking* const clockingp = VN_CAST(foundp->nodep(), Clocking)) {
foundp = getCreateClockingEventSymEnt(clockingp);
}
}
if (!foundp) {
} else if (VN_IS(foundp->nodep(), Cell) || VN_IS(foundp->nodep(), Begin)
|| VN_IS(foundp->nodep(), Netlist) // for $root
@@ -2582,6 +2660,9 @@ private:
m_ds.m_dotPos = DP_MEMBER;
m_ds.m_dotText = "";
}
} else if (VN_IS(foundp->nodep(), Clocking)) {
m_ds.m_dotSymp = foundp;
ok = m_ds.m_dotPos == DP_SCOPE;
}
//
if (!ok) {
+62
View File
@@ -60,6 +60,8 @@ private:
AstNodeModule* m_modp = nullptr; // Current module
AstNodeFTask* m_ftaskp = nullptr; // Current task
AstNodeDType* m_dtypep = nullptr; // Current data type
AstNodeExpr* m_defaultInSkewp = nullptr; // Current default input skew
AstNodeExpr* m_defaultOutSkewp = nullptr; // Current default output skew
int m_genblkAbove = 0; // Begin block number of if/case/for above
int m_genblkNum = 0; // Begin block number, 0=none seen
VLifetime m_lifetime = VLifetime::STATIC; // Propagating lifetime
@@ -632,6 +634,66 @@ private:
nodep->classOrPackagep(m_stdPackagep);
}
}
void visit(AstClocking* nodep) override {
VL_RESTORER(m_defaultInSkewp);
VL_RESTORER(m_defaultOutSkewp);
// Find default input and output skews
AstClockingItem* nextItemp = nodep->itemsp();
for (AstClockingItem* itemp = nextItemp; itemp; itemp = nextItemp) {
nextItemp = VN_AS(itemp->nextp(), ClockingItem);
if (itemp->exprp() || itemp->assignp()) continue;
if (itemp->skewp()) {
if (itemp->direction() == VDirection::INPUT) {
// Disallow default redefinition; note some simulators allow this
if (m_defaultInSkewp) {
itemp->skewp()->v3error("Multiple default input skews not allowed");
}
m_defaultInSkewp = itemp->skewp();
} else if (itemp->direction() == VDirection::OUTPUT) {
if (AstConst* const constp = VN_CAST(itemp->skewp(), Const)) {
if (constp->num().is1Step()) {
itemp->skewp()->v3error("1step not allowed as output skew");
}
}
// Disallow default redefinition; note some simulators allow this
if (m_defaultOutSkewp) {
itemp->skewp()->v3error("Multiple default output skews not allowed");
}
m_defaultOutSkewp = itemp->skewp();
} else {
itemp->v3fatalSrc("Incorrect direction");
}
}
pushDeletep(itemp->unlinkFrBack());
}
iterateChildren(nodep);
}
void visit(AstClockingItem* nodep) override {
if (nodep->direction() == VDirection::OUTPUT) {
if (!nodep->skewp()) {
if (m_defaultOutSkewp) {
nodep->skewp(m_defaultOutSkewp->cloneTree(false));
} else {
// Default is 0 (IEEE 1800-2017 14.3)
nodep->skewp(new AstConst{nodep->fileline(), 0});
}
} else if (AstConst* const constp = VN_CAST(nodep->skewp(), Const)) {
if (constp->num().is1Step()) {
nodep->skewp()->v3error("1step not allowed as output skew");
}
}
} else if (nodep->direction() == VDirection::INPUT) {
if (!nodep->skewp()) {
if (m_defaultInSkewp) {
nodep->skewp(m_defaultInSkewp->cloneTree(false));
} else {
// Default is 1step (IEEE 1800-2017 14.3)
nodep->skewp(new AstConst{nodep->fileline(), AstConst::OneStep{}});
}
}
}
iterateChildren(nodep);
}
void visit(AstNode* nodep) override {
// Default: Just iterate
+4
View File
@@ -494,6 +494,10 @@ V3Number& V3Number::setMask(int nbits) {
string V3Number::ascii(bool prefixed, bool cleanVerilog) const {
std::ostringstream out;
if (is1Step()) {
out << "1step";
return out.str();
}
if (isDouble()) {
out.precision(17);
if (VL_UNCOVERABLE(width() != 64)) {
+12
View File
@@ -97,6 +97,7 @@ private:
public:
bool m_sized : 1; // True if the user specified the width, else we track it.
bool m_signed : 1; // True if signed value
bool m_is1Step : 1; // True if 1step
bool m_isNull : 1; // True if "null" versus normal 0
bool m_fromString : 1; // True if from string literal
bool m_autoExtend : 1; // True if SystemVerilog extend-to-any-width
@@ -107,6 +108,7 @@ public:
: m_type{V3NumberDataType::UNINITIALIZED}
, m_sized{false}
, m_signed{false}
, m_is1Step{false}
, m_isNull{false}
, m_fromString{false}
, m_autoExtend{false} {}
@@ -118,6 +120,7 @@ public:
, m_type{other.m_type}
, m_sized{other.m_sized}
, m_signed{other.m_signed}
, m_is1Step{other.m_is1Step}
, m_isNull{other.m_isNull}
, m_fromString{other.m_fromString}
, m_autoExtend{other.m_autoExtend} {
@@ -145,6 +148,7 @@ public:
m_type = other.m_type;
m_sized = other.m_sized;
m_signed = other.m_signed;
m_is1Step = other.m_is1Step;
m_isNull = other.m_isNull;
m_fromString = other.m_fromString;
m_autoExtend = other.m_autoExtend;
@@ -156,6 +160,7 @@ public:
, m_type{other.m_type}
, m_sized{other.m_sized}
, m_signed{other.m_signed}
, m_is1Step{other.m_is1Step}
, m_isNull{other.m_isNull}
, m_fromString{other.m_fromString}
, m_autoExtend{other.m_autoExtend} {
@@ -184,6 +189,7 @@ public:
m_type = other.m_type;
m_sized = other.m_sized;
m_signed = other.m_signed;
m_is1Step = other.m_is1Step;
m_isNull = other.m_isNull;
m_fromString = other.m_fromString;
m_autoExtend = other.m_autoExtend;
@@ -489,6 +495,11 @@ public:
setString(value);
m_data.m_fromString = true;
}
class OneStep {};
V3Number(OneStep, AstNode* nodep) {
init(nodep, 64);
m_data.m_is1Step = true;
}
class Null {};
V3Number(Null, AstNode* nodep) {
init(nodep);
@@ -601,6 +612,7 @@ public:
|| m_data.type() == V3NumberDataType::DOUBLE;
}
bool isNegative() const VL_MT_SAFE { return !isString() && bitIs1(width() - 1); }
bool is1Step() const VL_MT_SAFE { return m_data.m_is1Step; }
bool isNull() const VL_MT_SAFE { return m_data.m_isNull; }
bool isFourState() const VL_MT_SAFE;
bool hasZ() const {
+6
View File
@@ -385,6 +385,12 @@ class OrderBuildVisitor final : public VNVisitor {
iterateLogic(nodep);
m_inPost = false;
}
void visit(AstAlwaysObserved* nodep) override { //
iterateLogic(nodep);
}
void visit(AstAlwaysReactive* nodep) override { //
iterateLogic(nodep);
}
void visit(AstFinal* nodep) override { // LCOV_EXCL_START
nodep->v3fatalSrc("AstFinal should not need ordering");
} // LCOV_EXCL_STOP
+188 -86
View File
@@ -192,7 +192,13 @@ LogicClasses gatherLogicClasses(AstNetlist* netlistp) {
}
} else {
UASSERT_OBJ(senTreep->hasClocked(), activep, "What else could it be?");
result.m_clocked.emplace_back(scopep, activep);
if (VN_IS(activep->stmtsp(), AlwaysObserved)) {
result.m_observed.emplace_back(scopep, activep);
} else if (VN_IS(activep->stmtsp(), AlwaysReactive)) {
result.m_reactive.emplace_back(scopep, activep);
} else {
result.m_clocked.emplace_back(scopep, activep);
}
}
});
@@ -332,6 +338,20 @@ struct TriggerKit {
}
};
//============================================================================
// EvalKit groups items that have to be passed to createEval() for a given eval region
struct EvalKit {
// The TRIGGERVEC AstVarScope representing the region's trigger flags
AstVarScope* const m_vscp = nullptr;
// The AstCFunc that computes the region's active triggers
AstCFunc* const m_triggerComputep = nullptr;
// The AstCFunc that dumps the region's active triggers
AstCFunc* const m_dumpp = nullptr;
// The AstCFunc that evaluates the region's logic
AstCFunc* const m_funcp = nullptr;
};
// Create an AstSenTree that is sensitive to the given trigger index. Must not exist yet!
AstSenTree* createTriggerSenTree(AstNetlist* netlistp, AstVarScope* const vscp, uint32_t index) {
AstTopScope* const topScopep = netlistp->topScopep();
@@ -765,15 +785,45 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp,
}
//============================================================================
// Bold together parts to create the top level _eval function
// Helpers for 'createEval'
AstStmtExpr* createTriggerClearCall(FileLine* const flp, AstVarScope* const vscp) { // Trigger
AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "clear"};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstStmtExpr* createTriggerSetCall(FileLine* const flp, AstVarScope* const toVscp,
AstVarScope* const fromVscp) {
AstVarRef* const lhsp = new AstVarRef{flp, toVscp, VAccess::WRITE};
AstVarRef* const argp = new AstVarRef{flp, fromVscp, VAccess::READ};
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "set", argp};
callp->dtypeSetVoid();
return callp->makeStmt();
}
AstStmtExpr* createTriggerAndNotCall(FileLine* const flp, AstVarScope* const lhsVscp,
AstVarScope* const aVscp, AstVarScope* const bVscp) {
AstVarRef* const lhsp = new AstVarRef{flp, lhsVscp, VAccess::WRITE};
AstVarRef* const opap = new AstVarRef{flp, aVscp, VAccess::READ};
AstVarRef* const opbp = new AstVarRef{flp, bVscp, VAccess::READ};
opap->addNext(opbp);
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "andNot", opap};
callp->dtypeSetVoid();
return callp->makeStmt();
}
//============================================================================
// Bolt together parts to create the top level _eval function
void createEval(AstNetlist* netlistp, //
AstNode* icoLoop, //
const TriggerKit& actTrig, //
const EvalKit& actKit, //
AstVarScope* preTrigsp, //
AstVarScope* nbaTrigsp, //
AstCFunc* actFuncp, //
AstCFunc* nbaFuncp, //
const EvalKit& nbaKit, //
const EvalKit& obsKit, //
const EvalKit& reactKit, //
AstCFunc* postponedFuncp, //
TimingKit& timingKit //
) {
@@ -785,31 +835,16 @@ void createEval(AstNetlist* netlistp, //
// Start with the ico loop, if any
if (icoLoop) funcp->addStmtsp(icoLoop);
// Create the NBA trigger dumping function, which is the same as act trigger
// dumping function, but referencing the nba trigger vector.
AstCFunc* const nbaDumpp = actTrig.m_dumpp->cloneTree(false);
actTrig.m_dumpp->addNextHere(nbaDumpp);
nbaDumpp->name("_dump_triggers__nba");
nbaDumpp->foreach([&](AstVarRef* refp) {
UASSERT_OBJ(refp->access().isReadOnly(), refp, "Should only read state");
if (refp->varScopep() == actTrig.m_vscp) {
refp->replaceWith(new AstVarRef{refp->fileline(), nbaTrigsp, VAccess::READ});
}
});
nbaDumpp->foreach([&](AstText* textp) { //
textp->text(VString::replaceWord(textp->text(), "act", "nba"));
});
// Create the active eval loop
AstNodeStmt* const activeEvalLoopp
= makeEvalLoop(
netlistp, "act", "Active", actTrig.m_vscp, actTrig.m_dumpp,
netlistp, "act", "Active", actKit.m_vscp, actKit.m_dumpp,
[&]() { // Trigger
AstNodeStmt* resultp = nullptr;
// Compute the current triggers
{
AstCCall* const trigsp = new AstCCall{flp, actTrig.m_funcp};
AstCCall* const trigsp = new AstCCall{flp, actKit.m_triggerComputep};
trigsp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, trigsp->makeStmt());
}
@@ -822,38 +857,21 @@ void createEval(AstNetlist* netlistp, //
return resultp;
},
[&]() { // Body
AstNodeStmt* resultp = nullptr;
// Compute the pre triggers
{
AstVarRef* const lhsp = new AstVarRef{flp, preTrigsp, VAccess::WRITE};
AstVarRef* const opap = new AstVarRef{flp, actTrig.m_vscp, VAccess::READ};
AstVarRef* const opbp = new AstVarRef{flp, nbaTrigsp, VAccess::READ};
opap->addNext(opbp);
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "andNot", opap};
callp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, callp->makeStmt());
}
AstNodeStmt* resultp
= createTriggerAndNotCall(flp, preTrigsp, actKit.m_vscp, nbaKit.m_vscp);
// Latch the active trigger flags under the NBA trigger flags
{
AstVarRef* const lhsp = new AstVarRef{flp, nbaTrigsp, VAccess::WRITE};
AstVarRef* const argp = new AstVarRef{flp, actTrig.m_vscp, VAccess::READ};
AstCMethodHard* const callp = new AstCMethodHard{flp, lhsp, "set", argp};
callp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, callp->makeStmt());
}
resultp = AstNode::addNext(
resultp, createTriggerSetCall(flp, nbaKit.m_vscp, actKit.m_vscp));
// Resume triggered timing schedulers
if (AstCCall* const resumep = timingKit.createResume(netlistp)) {
resultp = AstNode::addNext(resultp, resumep->makeStmt());
}
// Invoke body function
{
AstCCall* const callp = new AstCCall{flp, actFuncp};
AstCCall* const callp = new AstCCall{flp, actKit.m_funcp};
callp->dtypeSetVoid();
return AstNode::addNext(resultp, callp->makeStmt());
resultp = AstNode::addNext(resultp, callp->makeStmt());
}
return resultp;
@@ -861,32 +879,75 @@ void createEval(AstNetlist* netlistp, //
.second;
// Create the NBA eval loop. This uses the Active eval loop in the trigger section.
AstNodeStmt* const nbaEvalLoopp
AstNodeStmt* topEvalLoopp
= makeEvalLoop(
netlistp, "nba", "NBA", nbaTrigsp, nbaDumpp,
netlistp, "nba", "NBA", nbaKit.m_vscp, nbaKit.m_dumpp,
[&]() { // Trigger
AstNodeStmt* resultp = nullptr;
// Reset NBA triggers
{
AstVarRef* const refp = new AstVarRef{flp, nbaTrigsp, VAccess::WRITE};
AstCMethodHard* const callp = new AstCMethodHard{flp, refp, "clear"};
callp->dtypeSetVoid();
resultp = AstNode::addNext(resultp, callp->makeStmt());
}
AstNodeStmt* resultp = createTriggerClearCall(flp, nbaKit.m_vscp);
// Run the Active eval loop
return AstNode::addNext(resultp, activeEvalLoopp);
resultp = AstNode::addNext(resultp, activeEvalLoopp);
return resultp;
},
[&]() { // Body
AstCCall* const callp = new AstCCall{flp, nbaFuncp};
AstCCall* const callp = new AstCCall{flp, nbaKit.m_funcp};
callp->dtypeSetVoid();
return callp->makeStmt();
AstNodeStmt* resultp = callp->makeStmt();
// Latch the NBA trigger flags under the following region's trigger flags
AstVarScope* const nextVscp = obsKit.m_vscp ? obsKit.m_vscp : reactKit.m_vscp;
if (nextVscp) {
resultp = AstNode::addNext(
resultp, createTriggerSetCall(flp, nextVscp, nbaKit.m_vscp));
}
return resultp;
})
.second;
// Add the NBA eval loop
funcp->addStmtsp(nbaEvalLoopp);
if (obsKit.m_funcp) {
// Create the Observed eval loop. This uses the NBA eval loop in the trigger section.
topEvalLoopp
= makeEvalLoop(
netlistp, "obs", "Observed", obsKit.m_vscp, obsKit.m_dumpp,
[&]() { // Trigger
// Reset Observed triggers
AstNodeStmt* resultp = createTriggerClearCall(flp, obsKit.m_vscp);
// Run the NBA eval loop
resultp = AstNode::addNext(resultp, topEvalLoopp);
return resultp;
},
[&]() { // Body
AstCCall* const callp = new AstCCall{flp, obsKit.m_funcp};
callp->dtypeSetVoid();
AstNodeStmt* resultp = callp->makeStmt();
// Latch the Observed trigger flags under the Reactive trigger flags
if (reactKit.m_vscp) {
resultp = AstNode::addNext(
resultp, createTriggerSetCall(flp, reactKit.m_vscp, obsKit.m_vscp));
}
return resultp;
})
.second;
}
if (reactKit.m_funcp) {
// Create the Reactive eval loop. This uses the previous eval loop in the trigger section.
topEvalLoopp = makeEvalLoop(
netlistp, "react", "Reactive", reactKit.m_vscp, reactKit.m_dumpp,
[&]() { // Trigger
// Reset Reactive triggers
AstNodeStmt* resultp = createTriggerClearCall(flp, reactKit.m_vscp);
// Run the previous eval loop
resultp = AstNode::addNext(resultp, topEvalLoopp);
return resultp;
},
[&]() { // Body
auto* const callp = new AstCCall{flp, reactKit.m_funcp};
callp->dtypeSetVoid();
return callp->makeStmt();
})
.second;
}
funcp->addStmtsp(topEvalLoopp);
// Add the Postponed eval call
if (postponedFuncp) {
@@ -989,6 +1050,8 @@ void schedule(AstNetlist* netlistp) {
const auto& senTreeps = getSenTreesUsedBy({&logicRegions.m_pre, //
&logicRegions.m_act, //
&logicRegions.m_nba, //
&logicClasses.m_observed, //
&logicClasses.m_reactive, //
&timingKit.m_lbs});
const TriggerKit& actTrig
= createTriggers(netlistp, initp, senExprBuilder, senTreeps, "act", extraTriggers);
@@ -1002,7 +1065,6 @@ void schedule(AstNetlist* netlistp) {
AstVarScope* const actTrigVscp = actTrig.m_vscp;
AstVarScope* const preTrigVscp = scopeTopp->createTempLike("__VpreTriggered", actTrigVscp);
AstVarScope* const nbaTrigVscp = scopeTopp->createTempLike("__VnbaTriggered", actTrigVscp);
const auto cloneMapWithNewTriggerReferences
= [=](std::unordered_map<const AstSenTree*, AstSenTree*> map, AstVarScope* vscp) {
@@ -1025,7 +1087,6 @@ void schedule(AstNetlist* netlistp) {
const auto& actTrigMap = actTrig.m_map;
const auto preTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, preTrigVscp);
const auto nbaTrigMap = cloneMapWithNewTriggerReferences(actTrigMap, nbaTrigVscp);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-triggers");
// Note: Experiments so far show that running the Act (or Ico) regions on
@@ -1061,37 +1122,78 @@ void schedule(AstNetlist* netlistp) {
splitCheck(actFuncp);
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-act");
// Step 10: Create the 'nba' region evaluation function
const EvalKit& actKit = {actTrig.m_vscp, actTrig.m_funcp, actTrig.m_dumpp, actFuncp};
// Remap sensitivities of the input logic to the triggers
remapSensitivities(logicRegions.m_nba, nbaTrigMap);
remapSensitivities(logicReplicas.m_nba, nbaTrigMap);
const auto& nbaTimingDomains = timingKit.remapDomains(nbaTrigMap);
// Orders a region's logic and creates the region eval function
const auto order = [&](const std::string& name,
const std::vector<V3Sched::LogicByScope*>& logic) -> EvalKit {
AstVarScope* const trigVscp
= scopeTopp->createTempLike("__V" + name + "Triggered", actTrigVscp);
const auto trigMap = cloneMapWithNewTriggerReferences(actTrigMap, trigVscp);
// Remap sensitivities of the input logic to the triggers
for (LogicByScope* lbs : logic) remapSensitivities(*lbs, trigMap);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSenNba;
invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap);
// Create the inverse map from trigger ref AstSenTree to original AstSenTree
std::unordered_map<const AstSenItem*, const AstSenTree*> trigToSen;
invertAndMergeSenTreeMap(trigToSen, trigMap);
AstSenTree* const dpiExportTriggeredNba
= createTriggerSenTree(netlistp, nbaTrigVscp, dpiExportTriggerIndex);
AstSenTree* const dpiExportTriggered
= createTriggerSenTree(netlistp, trigVscp, dpiExportTriggerIndex);
AstCFunc* const nbaFuncp = V3Order::order(
netlistp, {&logicRegions.m_nba, &logicReplicas.m_nba}, trigToSenNba, "nba",
v3Global.opt.mtasks(), false, [&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
auto it = nbaTimingDomains.find(vscp);
if (it != nbaTimingDomains.end()) out = it->second;
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggeredNba);
const auto& timingDomains = timingKit.remapDomains(trigMap);
AstCFunc* const funcp = V3Order::order(
netlistp, logic, trigToSen, name, name == "nba" && v3Global.opt.mtasks(), false,
[&](const AstVarScope* vscp, std::vector<AstSenTree*>& out) {
auto it = timingDomains.find(vscp);
if (it != timingDomains.end()) out = it->second;
if (vscp->varp()->isWrittenByDpi()) out.push_back(dpiExportTriggered);
});
// Create the trigger dumping function, which is the same as act trigger
// dumping function, but referencing this region's trigger vector.
AstCFunc* const dumpp = actTrig.m_dumpp->cloneTree(false);
actTrig.m_dumpp->addNextHere(dumpp);
dumpp->name("_dump_triggers__" + name);
dumpp->foreach([&](AstVarRef* refp) {
UASSERT_OBJ(refp->access().isReadOnly(), refp, "Should only read state");
if (refp->varScopep() == actTrig.m_vscp) {
refp->replaceWith(new AstVarRef{refp->fileline(), trigVscp, VAccess::READ});
}
});
splitCheck(nbaFuncp);
netlistp->evalNbap(nbaFuncp); // Remember for V3LifePost
dumpp->foreach([&](AstText* textp) { //
textp->text(VString::replaceWord(textp->text(), "act", name));
});
return {trigVscp, nullptr, dumpp, funcp};
};
// Step 10: Create the 'nba' region evaluation function
const EvalKit& nbaKit = order("nba", {&logicRegions.m_nba, &logicReplicas.m_nba});
splitCheck(nbaKit.m_funcp);
netlistp->evalNbap(nbaKit.m_funcp); // Remember for V3LifePost
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-nba");
// Step 11: Create the 'postponed' region evaluation function
// Orders a region's logic and creates the region eval function (only if there is any logic in
// the region)
const auto orderIfNonEmpty = [&](const std::string& name, LogicByScope& lbs) -> EvalKit {
if (lbs.empty()) return {};
const auto& kit = order(name, {&lbs});
if (v3Global.opt.stats()) V3Stats::statsStage("sched-create-" + name);
return kit;
};
// Step 11: Create the 'obs' region evaluation function
const EvalKit& obsKit = orderIfNonEmpty("obs", logicClasses.m_observed);
// Step 12: Create the 're' region evaluation function
const EvalKit& reactKit = orderIfNonEmpty("react", logicClasses.m_reactive);
// Step 13: Create the 'postponed' region evaluation function
auto* const postponedFuncp = createPostponed(netlistp, logicClasses);
// Step 12: Bolt it all together to create the '_eval' function
createEval(netlistp, icoLoopp, actTrig, preTrigVscp, nbaTrigVscp, actFuncp, nbaFuncp,
postponedFuncp, timingKit);
// Step 14: Bolt it all together to create the '_eval' function
createEval(netlistp, icoLoopp, actKit, preTrigVscp, nbaKit, obsKit, reactKit, postponedFuncp,
timingKit);
transformForks(netlistp);
+2
View File
@@ -82,6 +82,8 @@ struct LogicClasses final {
LogicByScope m_clocked; // Clocked (or sequential) logic (logic with explicit sensitivities)
LogicByScope m_hybrid; // Hybrid logic (combinational logic with some explicit sensitivities)
LogicByScope m_postponed; // Postponed logic ($strobe)
LogicByScope m_observed; // Observed logic (contains AstAlwaysObserved)
LogicByScope m_reactive; // Reactive logic (contains AstAlwaysReactive)
LogicClasses() = default;
VL_UNCOPYABLE(LogicClasses);
+13 -1
View File
@@ -58,6 +58,7 @@ private:
// STATE, for passing down one level of hierarchy (may need save/restore)
AstCell* m_aboveCellp = nullptr; // Cell that instantiates this module
AstScope* m_aboveScopep = nullptr; // Scope that instantiates this scope
AstClocking* m_clockingp = nullptr; // Current clocking block
std::unordered_map<AstNodeModule*, AstScope*> m_packageScopes; // Scopes for each package
VarScopeMap m_varScopes; // Varscopes created for each scope and var
@@ -247,6 +248,15 @@ private:
// We iterate under the *clone*
iterateChildren(clonep);
}
void visit(AstClocking* nodep) override {
VL_RESTORER(m_clockingp);
m_clockingp = nodep;
UINFO(4, " CLOCKING " << nodep << endl);
iterateChildren(nodep);
if (nodep->varsp()) m_scopep->modp()->addStmtsp(nodep->varsp()->unlinkFrBackWithNext());
if (nodep->eventp()) m_scopep->modp()->addStmtsp(nodep->eventp()->unlinkFrBack());
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
void visit(AstNodeFTask* nodep) override {
// Add to list of blocks under this scope
UINFO(4, " FTASK " << nodep << endl);
@@ -265,7 +275,9 @@ private:
}
void visit(AstVar* nodep) override {
// Make new scope variable
if (!nodep->user1p()) {
if (m_clockingp) {
nodep->name(VString::dot(m_clockingp->name(), "__DOT__", nodep->name()));
} else if (!nodep->user1p()) {
AstScope* scopep = m_scopep;
if (AstIfaceRefDType* const ifacerefp = VN_CAST(nodep->dtypep(), IfaceRefDType)) {
// Attach every non-virtual interface variable its inner scope
+9 -2
View File
@@ -155,8 +155,8 @@ private:
}
// Transform an assignment with an intra timing control into a timing control with the
// assignment under it
AstNodeStmt* factorOutTimingControl(AstNodeAssign* nodep) const {
AstNodeStmt* stmtp = nodep;
AstNode* factorOutTimingControl(AstNodeAssign* nodep) const {
AstNode* stmtp = nodep;
AstDelay* delayp = getLhsNetDelay(nodep);
FileLine* const flp = nodep->fileline();
AstNode* const controlp = nodep->timingControlp();
@@ -182,6 +182,11 @@ private:
stmtp->replaceWith(eventControlp);
eventControlp->addStmtsp(stmtp);
stmtp = eventControlp;
} else if (auto* const beginp = VN_CAST(controlp, Begin)) {
// Begin from V3AssertPre
stmtp->replaceWith(beginp);
beginp->addStmtsp(stmtp);
stmtp = beginp;
}
return stmtp == nodep ? nullptr : stmtp;
}
@@ -495,6 +500,8 @@ private:
m_procp->user2(true);
}
void visit(AstDelay* nodep) override {
UASSERT_OBJ(!nodep->isCycleDelay(), nodep,
"Cycle delays should have been handled in V3AssertPre");
FileLine* const flp = nodep->fileline();
AstNodeExpr* valuep = V3Const::constifyEdit(nodep->lhsp()->unlinkFrBack());
auto* const constp = VN_CAST(valuep, Const);
+6
View File
@@ -5374,6 +5374,12 @@ private:
}
}
}
void visit(AstClockingItem* nodep) override {
nodep->exprp()->foreach([nodep](AstVarRef* const refp) {
refp->access(nodep->direction().isWritable() ? VAccess::WRITE : VAccess::READ);
});
userIterateChildren(nodep, WidthVP{SELF, PRELIM}.p());
}
void visit(AstWait* nodep) override {
if (VN_IS(m_ftaskp, Func)) {
nodep->v3error("Wait statements are not legal in functions. Suggest use a task "
+2 -2
View File
@@ -637,6 +637,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
<VA5,SAX>{
/* Generic unsupported warnings */
"1step" { FL; return ya1STEP; }
"above" { ERROR_RSVD_WORD("AMS"); }
"abs" { ERROR_RSVD_WORD("AMS"); }
"absdelay" { ERROR_RSVD_WORD("AMS"); }
@@ -929,8 +930,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5}
return yaTIMENUM;
}
1step {
FL; yylval.cdouble = 0; // Unsupported
return yaTIMENUM;
return ya1STEP;
}
}
+106 -84
View File
@@ -243,6 +243,19 @@ public:
fileline->v3error("Unsupported DPI type '" << str << "': Use 'DPI-C'");
}
}
// Given a list of clocking declarations, put them in clocking items
AstClockingItem* makeClockingItemList(FileLine* flp, const VDirection direction,
AstNodeExpr* skewp, AstNode* const clockingDeclps) {
AstClockingItem* itemsp = nullptr;
for (AstNode *nodep = clockingDeclps, *nextp; nodep; nodep = nextp) {
nextp = nodep->nextp();
if (nextp) nextp->unlinkFrBackWithNext();
if (itemsp && skewp) skewp = skewp->cloneTree(false);
AstClockingItem* itemp = new AstClockingItem{flp, direction, skewp, nodep};
itemsp = itemsp ? itemsp->addNext(itemp) : itemp;
}
return itemsp;
}
};
const VBasicDTypeKwd LOGIC = VBasicDTypeKwd::LOGIC; // Shorthand "LOGIC"
@@ -494,6 +507,7 @@ BISONPRE_VERSION(3.7,%define api.header.include {"V3ParseBison.h"})
// for example yP_ for punctuation based operators.
// Double underscores "yX__Y" means token X followed by Y,
// and "yX__ETC" means X folled by everything but Y(s).
%token<fl> ya1STEP "1step"
//UNSUP %token<fl> yACCEPT_ON "accept_on"
%token<fl> yALIAS "alias"
%token<fl> yALWAYS "always"
@@ -2828,13 +2842,13 @@ delay_controlE<delayp>:
delay_control<delayp>: //== IEEE: delay_control
'#' delay_value
{ $$ = new AstDelay{$<fl>1, $2}; }
{ $$ = new AstDelay{$<fl>1, $2, false}; }
| '#' '(' minTypMax ')'
{ $$ = new AstDelay{$<fl>1, $3}; }
{ $$ = new AstDelay{$<fl>1, $3, false}; }
| '#' '(' minTypMax ',' minTypMax ')'
{ $$ = new AstDelay{$<fl>1, $3}; RISEFALLDLYUNSUP($3); DEL($5); }
{ $$ = new AstDelay{$<fl>1, $3, false}; RISEFALLDLYUNSUP($3); DEL($5); }
| '#' '(' minTypMax ',' minTypMax ',' minTypMax ')'
{ $$ = new AstDelay{$<fl>1, $3}; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); }
{ $$ = new AstDelay{$<fl>1, $3, false}; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); }
;
delay_value<nodeExprp>: // ==IEEE:delay_value
@@ -3320,6 +3334,10 @@ statement_item<nodep>: // IEEE: statement_item
// // IEEE: nonblocking_assignment
| fexprLvalue yP_LTE delay_or_event_controlE expr ';'
{ $$ = new AstAssignDly{$2, $1, $4, $3}; }
// // IEEE: clocking_drive ';'
| fexprLvalue yP_LTE cycle_delay expr ';'
{ $$ = new AstAssignDly{$2, $1, $4, $3}; }
//UNSUP cycle_delay fexprLvalue yP_LTE ';' { UNSUP }
| yASSIGN idClassSel '=' delay_or_event_controlE expr ';'
{ $$ = new AstAssign{$1, $2, $5, $4}; }
| yDEASSIGN variable_lvalue ';'
@@ -3440,8 +3458,14 @@ statement_item<nodep>: // IEEE: statement_item
if ($2 && $2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext();
$$ = new AstEventControl{FILELINE_OR_CRE($1), $1, $2};
addNextNull($$, nextp); }
//UNSUP cycle_delay stmtBlock { UNSUP }
//
| cycle_delay stmtBlock
{ AstNode* nextp = nullptr;
if ($2) {
if ($2->nextp()) nextp = $2->nextp()->unlinkFrBackWithNext();
$1->addStmtsp($2);
}
$$ = $1;
addNextNull($$, nextp); }
| seq_block { $$ = $1; }
//
// // IEEE: wait_statement
@@ -3452,13 +3476,6 @@ statement_item<nodep>: // IEEE: statement_item
// // IEEE: procedural_assertion_statement
| procedural_assertion_statement { $$ = $1; }
//
// // IEEE: clocking_drive ';'
// // Pattern w/o cycle_delay handled by nonblocking_assign above
// // clockvar_expression made to fexprLvalue to prevent reduce conflict
// // Note LTE in this context is highest precedence, so first on left wins
//UNSUP cycle_delay fexprLvalue yP_LTE ';' { UNSUP }
//UNSUP fexprLvalue yP_LTE cycle_delay expr ';' { UNSUP }
//
//UNSUP randsequence_statement { $$ = $1; }
//
// // IEEE: randcase_statement
@@ -5403,87 +5420,92 @@ endLabelE<strp>:
//************************************************
// Clocking
clocking_declaration<nodep>: // IEEE: clocking_declaration (INCOMPLETE)
//UNSUP: vvv remove this -- vastly simplified grammar:
yDEFAULT yCLOCKING '@' '(' senitemEdge ')' ';' yENDCLOCKING
{ $$ = new AstClocking{$2, $5, nullptr}; }
//UNSUP: ^^^ remove this -- vastly simplified grammar:
//UNSUP clockingFront clocking_event ';'
//UNSUP clocking_itemListE yENDCLOCKING endLabelE { SYMP->popScope($$); }
clocking_declaration<nodep>: // IEEE: clocking_declaration
yCLOCKING idAny clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE
{ $$ = new AstClocking{$<fl>2, *$2, $3, $5, false}; }
| yDEFAULT yCLOCKING clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE
{ $$ = new AstClocking{$<fl>2, "", $3, $5, true}; }
| yDEFAULT yCLOCKING idAny clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE
{ $$ = new AstClocking{$<fl>3, *$3, $4, $6, true}; }
| yGLOBAL__CLOCKING yCLOCKING clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE
{ $$ = nullptr;
BBUNSUP($<fl>2, "Unsupported: global clocking"); }
| yGLOBAL__CLOCKING yCLOCKING idAny clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE
{ $$ = nullptr;
BBUNSUP($<fl>3, "Unsupported: global clocking"); }
;
//UNSUPclockingFront: // IEEE: part of class_declaration
//UNSUP yCLOCKING { PARSEP->symPushNewAnon(VAstType::CLOCKING); }
//UNSUP | yCLOCKING idAny/*clocking_identifier*/ { SYMP->pushNew($$); }
//UNSUP | yDEFAULT yCLOCKING { PARSEP->symPushNewAnon(VAstType::CLOCKING); }
//UNSUP | yDEFAULT yCLOCKING idAny/*clocking_identifier*/ { SYMP->pushNew($$); }
//UNSUP | yGLOBAL__CLOCKING yCLOCKING { PARSEP->symPushNewAnon(VAstType::CLOCKING); }
//UNSUP | yGLOBAL__CLOCKING yCLOCKING idAny/*clocking_identifier*/ { SYMP->pushNew($$); }
//UNSUP ;
clocking_event<senItemp>: // IEEE: clocking_event
'@' id
{ $$ = new AstSenItem{$<fl>2, VEdgeType::ET_CHANGED, new AstParseRef{$<fl>2, VParseRefExp::PX_TEXT, *$2, nullptr, nullptr}}; }
| '@' '(' event_expression ')' { $$ = $3; }
;
//UNSUPclocking_event: // ==IEEE: clocking_event
//UNSUP '@' id { }
//UNSUP | '@' '(' event_expression ')' { }
//UNSUP ;
clocking_itemListE<clockingItemp>:
/* empty */ { $$ = nullptr; }
| clocking_itemList { $$ = $1; }
;
//UNSUPclocking_itemListE:
//UNSUP /* empty */ { $$ = nullptr; }
//UNSUP | clocking_itemList { $$ = $1; }
//UNSUP ;
clocking_itemList<clockingItemp>: // IEEE: [ clocking_item ]
clocking_item { $$ = $1; }
| clocking_itemList clocking_item { if ($1) $$ = addNextNull($1, $2); }
;
//UNSUPclocking_itemList: // IEEE: [ clocking_item ]
//UNSUP clocking_item { $$ = $1; }
//UNSUP | clocking_itemList clocking_item { $$ = addNextNull($1, $2); }
//UNSUP ;
clocking_item<clockingItemp>: // IEEE: clocking_item
yDEFAULT yINPUT clocking_skew ';' { $$ = new AstClockingItem{$<fl>1, VDirection::INPUT, $3, nullptr}; }
| yDEFAULT yOUTPUT clocking_skew ';' { $$ = new AstClockingItem{$<fl>1, VDirection::OUTPUT, $3, nullptr}; }
| yDEFAULT yINPUT clocking_skew yOUTPUT clocking_skew ';'
{ $$ = new AstClockingItem{$<fl>1, VDirection::INPUT, $3, nullptr};
$$->addNext(new AstClockingItem{$<fl>4, VDirection::OUTPUT, $5, nullptr}); }
| yINPUT clocking_skewE list_of_clocking_decl_assign ';'
{ $$ = GRAMMARP->makeClockingItemList($<fl>1, VDirection::INPUT, $2, $3); }
| yOUTPUT clocking_skewE list_of_clocking_decl_assign ';'
{ $$ = GRAMMARP->makeClockingItemList($<fl>1, VDirection::OUTPUT, $2, $3); }
| yINPUT clocking_skewE yOUTPUT clocking_skewE list_of_clocking_decl_assign ';'
{ $$ = nullptr;
BBUNSUP($5, "Unsupported: Mixed input/output clocking items"); }
| yINOUT list_of_clocking_decl_assign ';'
{ $$ = nullptr;
BBUNSUP($1, "Unsupported: 'inout' clocking items"); }
| assertion_item_declaration
{ $$ = nullptr;
BBUNSUP($1, "Unsupported: assertion items in clocking blocks"); }
;
//UNSUPclocking_item: // ==IEEE: clocking_item
//UNSUP yDEFAULT default_skew ';' { }
//UNSUP | clocking_direction list_of_clocking_decl_assign ';' { }
//UNSUP | assertion_item_declaration { }
//UNSUP ;
list_of_clocking_decl_assign<nodep>: // IEEE: list_of_clocking_decl_assign
clocking_decl_assign { $$ = $1; }
| list_of_clocking_decl_assign ',' clocking_decl_assign
{ $$ = addNextNull($1, $3); }
;
//UNSUPdefault_skew: // ==IEEE: default_skew
//UNSUP yINPUT clocking_skew { }
//UNSUP | yOUTPUT clocking_skew { }
//UNSUP | yINPUT clocking_skew yOUTPUT clocking_skew { }
//UNSUP ;
clocking_decl_assign<nodep>: // IEEE: clocking_decl_assign
idAny/*new-signal_identifier*/ exprEqE
{ AstParseRef* const refp = new AstParseRef{$<fl>1, VParseRefExp::PX_TEXT, *$1, nullptr, nullptr};
$$ = refp;
if ($2) $$ = new AstAssign{$<fl>2, refp, $2}; }
;
//UNSUPclocking_direction: // ==IEEE: clocking_direction
//UNSUP yINPUT clocking_skewE { }
//UNSUP | yOUTPUT clocking_skewE { }
//UNSUP | yINPUT clocking_skewE yOUTPUT clocking_skewE { }
//UNSUP | yINOUT { }
//UNSUP ;
clocking_skewE<nodeExprp>: // IEEE: [clocking_skew]
/* empty */ { $$ = nullptr; }
| clocking_skew { $$ = $1; }
;
//UNSUPlist_of_clocking_decl_assign: // ==IEEE: list_of_clocking_decl_assign
//UNSUP clocking_decl_assign { $$ = $1; }
//UNSUP | list_of_clocking_decl_assign ',' clocking_decl_assign { }
//UNSUP ;
clocking_skew<nodeExprp>: // IEEE: clocking_skew
delay_control { $$ = $1->lhsp()->unlinkFrBack(); $1->deleteTree(); }
| '#' ya1STEP { $$ = new AstConst{$<fl>1, AstConst::OneStep{}}; }
| yPOSEDGE delay_controlE { $$ = nullptr;
BBUNSUP($1, "Unsupported: clocking event edge override"); }
| yNEGEDGE delay_controlE { $$ = nullptr;
BBUNSUP($1, "Unsupported: clocking event edge override"); }
| yEDGE delay_controlE { $$ = nullptr;
BBUNSUP($1, "Unsupported: clocking event edge override"); }
;
//UNSUPclocking_decl_assign: // ==IEEE: clocking_decl_assign
//UNSUP idAny/*new-signal_identifier*/ exprEqE { $$ = $1; }
//UNSUP ;
//UNSUPclocking_skewE: // IEEE: [clocking_skew]
//UNSUP /* empty */ { $$ = nullptr; }
//UNSUP | clocking_skew { $$ = $1; }
//UNSUP ;
//UNSUPclocking_skew: // ==IEEE: clocking_skew
//UNSUP yPOSEDGE { }
//UNSUP | yPOSEDGE delay_control { }
//UNSUP | yNEGEDGE { }
//UNSUP | yNEGEDGE delay_control { }
//UNSUP | yEDGE { NEED_S09($<fl>1, "edge"); }
//UNSUP | yEDGE delay_control { NEED_S09($<fl>1, "edge"); }
//UNSUP | delay_control { $$ = $1; }
//UNSUP ;
//UNSUPcycle_delay: // ==IEEE: cycle_delay
//UNSUP yP_POUNDPOUND yaINTNUM { }
//UNSUP | yP_POUNDPOUND id { }
//UNSUP | yP_POUNDPOUND '(' expr ')' { }
//UNSUP ;
cycle_delay<delayp>: // IEEE: cycle_delay
yP_POUNDPOUND yaINTNUM { $$ = new AstDelay{$<fl>1, new AstConst{$<fl>2, *$2}, true}; }
| yP_POUNDPOUND id { $$ = new AstDelay{$<fl>1, new AstParseRef{$<fl>2, VParseRefExp::PX_TEXT, *$2, nullptr, nullptr}, true}; }
| yP_POUNDPOUND '(' expr ')' { $$ = new AstDelay{$<fl>1, $3, true}; }
;
//************************************************
// Asserts