diff --git a/include/verilated_types.h b/include/verilated_types.h index 3055b5e04..1d6afb16b 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1238,6 +1238,42 @@ static inline bool VL_CAST_DYNAMIC(VlClassRef in, VlClassRef& outr) { } } +//============================================================================= +// VlSampleQueue stores samples for input clockvars in clocking blocks. At a clocking event, +// samples from this queue should be written to the correct input clockvar. + +template +class VlSampleQueue final { + // TYPES + // Type representing a single value sample at a point in time + struct VlSample { + uint64_t m_timestamp; // Timestamp at which the value was sampled + T_Sampled m_value; // The sampled value + }; + + // MEMBERS + std::deque m_queue; // Queue of samples with timestamps + +public: + // METHODS + // Push a new sample with the given timestamp to the end of the queue + void push(uint64_t time, const T_Sampled& value) { m_queue.push_back({time, value}); } + // Get the latest sample with its timestamp less than or equal to the given skew + void pop(uint64_t time, uint64_t skew, T_Sampled& value) { + if (time < skew) return; + // Find the last element not greater than (time - skew). Do a binary search, as the queue + // should be ordered. + auto it = std::lower_bound(m_queue.rbegin(), m_queue.rend(), VlSample{time - skew, {}}, + [](const VlSample& sample, const VlSample& skewed) { + return sample.m_timestamp > skewed.m_timestamp; + }); + if (it != m_queue.rend()) { + value = it->m_value; + m_queue.erase(m_queue.begin(), it.base()); + } + } +}; + //====================================================================== #define VL_NEW(Class, ...) \ diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 7cfa896ae..6f60a4b21 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -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 - 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 + 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 AstActive* getSpecialActive(FileLine* fl) { @@ -542,6 +547,22 @@ private: AstActive* const activep = m_namer.makeSpecialActive(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); } diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index 8e84e2986..b52839a1a 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -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(); + 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 @ queue.pop(, /*out*/}); + 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); diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 3fd96e40f..c623681ad 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -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(samep); + if (!asamep->subDTypep()) return false; + return (subDTypep() == asamep->subDTypep()); + } + bool similarDType(const AstNodeDType* samep) const override { + const AstSampleQueueDType* const asamep = static_cast(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); } + int widthTotalBytes() const override { return sizeof(std::map); } + 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 diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index ff2d37089..aa4dfddd2 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -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 diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index e407dbf4a..28265a96c 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -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 { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3332db08d..21678f382 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -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]"; +} diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 9af5384a6..02c742996 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -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); } diff --git a/src/V3Dead.cpp b/src/V3Dead.cpp index ed0a1452f..4a602c205 100644 --- a/src/V3Dead.cpp +++ b/src/V3Dead.cpp @@ -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); diff --git a/src/V3EmitCBase.h b/src/V3EmitCBase.h index 8f7a97dd8..932bf7248 100644 --- a/src/V3EmitCBase.h +++ b/src/V3EmitCBase.h @@ -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 } diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 42b4675d5..24818d18d 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -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."); diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index fe8fae672..0f82d8730 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -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 diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index bf6a6c5ad..f4d3a6582 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -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(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) { diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index ea67c2aea..4ca4abf56 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -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 diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 49081321a..09c07f580 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -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)) { diff --git a/src/V3Number.h b/src/V3Number.h index ea4fd2cae..5daadf93d 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -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 { diff --git a/src/V3Order.cpp b/src/V3Order.cpp index 037b16230..1d0c0ac53 100644 --- a/src/V3Order.cpp +++ b/src/V3Order.cpp @@ -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 diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 885e5d4a0..658243e93 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -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 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& 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 trigToSenNba; - invertAndMergeSenTreeMap(trigToSenNba, nbaTrigMap); + // Create the inverse map from trigger ref AstSenTree to original AstSenTree + std::unordered_map 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& 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& 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); diff --git a/src/V3Sched.h b/src/V3Sched.h index 3132706e8..0c364cfc4 100644 --- a/src/V3Sched.h +++ b/src/V3Sched.h @@ -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); diff --git a/src/V3Scope.cpp b/src/V3Scope.cpp index ffc8127e7..d2177245c 100644 --- a/src/V3Scope.cpp +++ b/src/V3Scope.cpp @@ -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 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 diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index 195714a2b..00fead7d2 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -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); diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 86f8f8f03..94aca3878 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -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 " diff --git a/src/verilog.l b/src/verilog.l index b4b42635c..dedd4c6c9 100644 --- a/src/verilog.l +++ b/src/verilog.l @@ -637,6 +637,7 @@ vnum {vnum1}|{vnum2}|{vnum3}|{vnum4}|{vnum5} { /* 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; } } diff --git a/src/verilog.y b/src/verilog.y index d6757a093..114e3da12 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -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 ya1STEP "1step" //UNSUP %token yACCEPT_ON "accept_on" %token yALIAS "alias" %token yALWAYS "always" @@ -2828,13 +2842,13 @@ delay_controlE: delay_control: //== IEEE: delay_control '#' delay_value - { $$ = new AstDelay{$1, $2}; } + { $$ = new AstDelay{$1, $2, false}; } | '#' '(' minTypMax ')' - { $$ = new AstDelay{$1, $3}; } + { $$ = new AstDelay{$1, $3, false}; } | '#' '(' minTypMax ',' minTypMax ')' - { $$ = new AstDelay{$1, $3}; RISEFALLDLYUNSUP($3); DEL($5); } + { $$ = new AstDelay{$1, $3, false}; RISEFALLDLYUNSUP($3); DEL($5); } | '#' '(' minTypMax ',' minTypMax ',' minTypMax ')' - { $$ = new AstDelay{$1, $3}; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); } + { $$ = new AstDelay{$1, $3, false}; RISEFALLDLYUNSUP($3); DEL($5); DEL($7); } ; delay_value: // ==IEEE:delay_value @@ -3320,6 +3334,10 @@ statement_item: // 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: // 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: // 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: //************************************************ // Clocking -clocking_declaration: // 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: // IEEE: clocking_declaration + yCLOCKING idAny clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE + { $$ = new AstClocking{$2, *$2, $3, $5, false}; } + | yDEFAULT yCLOCKING clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE + { $$ = new AstClocking{$2, "", $3, $5, true}; } + | yDEFAULT yCLOCKING idAny clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE + { $$ = new AstClocking{$3, *$3, $4, $6, true}; } + | yGLOBAL__CLOCKING yCLOCKING clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE + { $$ = nullptr; + BBUNSUP($2, "Unsupported: global clocking"); } + | yGLOBAL__CLOCKING yCLOCKING idAny clocking_event ';' clocking_itemListE yENDCLOCKING endLabelE + { $$ = nullptr; + BBUNSUP($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: // IEEE: clocking_event + '@' id + { $$ = new AstSenItem{$2, VEdgeType::ET_CHANGED, new AstParseRef{$2, VParseRefExp::PX_TEXT, *$2, nullptr, nullptr}}; } + | '@' '(' event_expression ')' { $$ = $3; } + ; -//UNSUPclocking_event: // ==IEEE: clocking_event -//UNSUP '@' id { } -//UNSUP | '@' '(' event_expression ')' { } -//UNSUP ; +clocking_itemListE: + /* empty */ { $$ = nullptr; } + | clocking_itemList { $$ = $1; } + ; -//UNSUPclocking_itemListE: -//UNSUP /* empty */ { $$ = nullptr; } -//UNSUP | clocking_itemList { $$ = $1; } -//UNSUP ; +clocking_itemList: // 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: // IEEE: clocking_item + yDEFAULT yINPUT clocking_skew ';' { $$ = new AstClockingItem{$1, VDirection::INPUT, $3, nullptr}; } + | yDEFAULT yOUTPUT clocking_skew ';' { $$ = new AstClockingItem{$1, VDirection::OUTPUT, $3, nullptr}; } + | yDEFAULT yINPUT clocking_skew yOUTPUT clocking_skew ';' + { $$ = new AstClockingItem{$1, VDirection::INPUT, $3, nullptr}; + $$->addNext(new AstClockingItem{$4, VDirection::OUTPUT, $5, nullptr}); } + | yINPUT clocking_skewE list_of_clocking_decl_assign ';' + { $$ = GRAMMARP->makeClockingItemList($1, VDirection::INPUT, $2, $3); } + | yOUTPUT clocking_skewE list_of_clocking_decl_assign ';' + { $$ = GRAMMARP->makeClockingItemList($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: // 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: // IEEE: clocking_decl_assign + idAny/*new-signal_identifier*/ exprEqE + { AstParseRef* const refp = new AstParseRef{$1, VParseRefExp::PX_TEXT, *$1, nullptr, nullptr}; + $$ = refp; + if ($2) $$ = new AstAssign{$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: // 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: // IEEE: clocking_skew + delay_control { $$ = $1->lhsp()->unlinkFrBack(); $1->deleteTree(); } + | '#' ya1STEP { $$ = new AstConst{$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($1, "edge"); } -//UNSUP | yEDGE delay_control { NEED_S09($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: // IEEE: cycle_delay + yP_POUNDPOUND yaINTNUM { $$ = new AstDelay{$1, new AstConst{$2, *$2}, true}; } + | yP_POUNDPOUND id { $$ = new AstDelay{$1, new AstParseRef{$2, VParseRefExp::PX_TEXT, *$2, nullptr, nullptr}, true}; } + | yP_POUNDPOUND '(' expr ')' { $$ = new AstDelay{$1, $3, true}; } + ; //************************************************ // Asserts diff --git a/test_regress/t/t_clocking_bad1.out b/test_regress/t/t_clocking_bad1.out new file mode 100644 index 000000000..214ae7a0d --- /dev/null +++ b/test_regress/t/t_clocking_bad1.out @@ -0,0 +1,5 @@ +%Error: t/t_clocking_bad1.v:16:12: Only one default clocking block allowed per module (IEEE 1800-2017 14.12) + : ... In instance t + 16 | default clocking @(posedge clk); + | ^~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_bad1.pl b/test_regress/t/t_clocking_bad1.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_clocking_bad1.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_bad1.v b/test_regress/t/t_clocking_bad1.v new file mode 100644 index 000000000..306b502f6 --- /dev/null +++ b/test_regress/t/t_clocking_bad1.v @@ -0,0 +1,18 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + default clocking @(posedge clk); + endclocking + + default clocking @(posedge clk); + endclocking +endmodule diff --git a/test_regress/t/t_clocking_bad2.out b/test_regress/t/t_clocking_bad2.out new file mode 100644 index 000000000..5d32295d1 --- /dev/null +++ b/test_regress/t/t_clocking_bad2.out @@ -0,0 +1,16 @@ +%Error: t/t_clocking_bad2.v:15:32: 1step not allowed as output skew + 15 | default input #1 output #1step; + | ^ +%Error: t/t_clocking_bad2.v:16:23: Multiple default input skews not allowed + 16 | default input #2 output #2; + | ^ +%Error: t/t_clocking_bad2.v:16:33: Multiple default output skews not allowed + 16 | default input #2 output #2; + | ^ +%Error: t/t_clocking_bad2.v:17:15: 1step not allowed as output skew + 17 | output #1step out; + | ^ +%Error: t/t_clocking_bad2.v:18:8: Multiple clockvars with the same name not allowed + 18 | output out; + | ^~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_bad2.pl b/test_regress/t/t_clocking_bad2.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_clocking_bad2.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_bad2.v b/test_regress/t/t_clocking_bad2.v new file mode 100644 index 000000000..7d5581415 --- /dev/null +++ b/test_regress/t/t_clocking_bad2.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic in, out; + clocking cb @(posedge clk); + default input #1 output #1step; + default input #2 output #2; + output #1step out; + output out; + endclocking +endmodule diff --git a/test_regress/t/t_clocking_bad3.out b/test_regress/t/t_clocking_bad3.out new file mode 100644 index 000000000..8202621e3 --- /dev/null +++ b/test_regress/t/t_clocking_bad3.out @@ -0,0 +1,13 @@ +%Error: t/t_clocking_bad3.v:14:14: Corresponding variable 'in' does not exist + 14 | input in; + | ^~ +%Error: t/t_clocking_bad3.v:15:15: Corresponding variable 'out' does not exist + 15 | output out; + | ^~~ +%Error: t/t_clocking_bad3.v:18:13: Duplicate declaration of CLOCKING 'cb': 'cb' + 18 | clocking cb @(posedge clk); + | ^~ + t/t_clocking_bad3.v:13:13: ... Location of original declaration + 13 | clocking cb @(posedge clk); + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_bad3.pl b/test_regress/t/t_clocking_bad3.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_clocking_bad3.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_bad3.v b/test_regress/t/t_clocking_bad3.v new file mode 100644 index 000000000..a7949e4a0 --- /dev/null +++ b/test_regress/t/t_clocking_bad3.v @@ -0,0 +1,20 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + clocking cb @(posedge clk); + input in; + output out; + endclocking + + clocking cb @(posedge clk); + endclocking +endmodule diff --git a/test_regress/t/t_clocking_bad4.out b/test_regress/t/t_clocking_bad4.out new file mode 100644 index 000000000..b5702e561 --- /dev/null +++ b/test_regress/t/t_clocking_bad4.out @@ -0,0 +1,45 @@ +%Error: t/t_clocking_bad4.v:23:15: Skew must be constant (IEEE 1800-2017 14.4) + : ... In instance t + 23 | input #cyc in; + | ^~~ +%Error: t/t_clocking_bad4.v:24:16: Skew cannot be negative + : ... In instance t + 24 | input #(-1) out; + | ^ +%Error: t/t_clocking_bad4.v:31:11: Usage of cycle delays requires default clocking (IEEE 1800-2017 14.11) + : ... In instance t + 31 | always ##1; + | ^~ +%Error: t/t_clocking_bad4.v:32:15: Only non-blocking assignments can write to clockvars (IEEE 1800-2017 14.16) + : ... In instance t + 32 | always cb1.out = clk; + | ^~~ +%Error: t/t_clocking_bad4.v:33:15: Only non-blocking assignments can write to clockvars (IEEE 1800-2017 14.16) + : ... In instance t + 33 | assign cb1.out = clk; + | ^~~ +%Error: t/t_clocking_bad4.v:34:21: Only non-blocking assignments can write to clockvars (IEEE 1800-2017 14.16) + : ... In instance t + 34 | always write(cb1.out); + | ^~~ +%Error: t/t_clocking_bad4.v:35:22: Event controls cannot be used in synchronous drives (IEEE 1800-2017 14.16) + : ... In instance t + 35 | always cb1.out <= @(posedge clk) 1; + | ^ +%Error: t/t_clocking_bad4.v:36:22: Only cycle delays can be used in synchronous drives (IEEE 1800-2017 14.16) + : ... In instance t + 36 | always cb1.out <= #1 1; + | ^ +%Error: t/t_clocking_bad4.v:37:18: Cycle delays not allowed as intra-assignment delays (IEEE 1800-2017 14.11) + : ... In instance t + 37 | always out <= ##1 1; + | ^~ +%Error: t/t_clocking_bad4.v:40:12: Cannot write to input clockvar (IEEE 1800-2017 14.3) + : ... In instance t + 40 | cb1.in = 1; + | ^~ +%Error: t/t_clocking_bad4.v:41:21: Cannot read from output clockvar (IEEE 1800-2017 14.3) + : ... In instance t + 41 | $display(cb1.out); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_bad4.pl b/test_regress/t/t_clocking_bad4.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_clocking_bad4.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_bad4.v b/test_regress/t/t_clocking_bad4.v new file mode 100644 index 000000000..58707a1af --- /dev/null +++ b/test_regress/t/t_clocking_bad4.v @@ -0,0 +1,43 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic in, out; + clocking cb1 @(posedge clk); + input in; + output out; + endclocking + + int cyc = 0; + always @(posedge clk) cyc <= cyc + 1; + + clocking cb2 @(negedge clk); + input #cyc in; + input #(-1) out; + endclocking + + task write(output x); + x = 1; + endtask + + always ##1; + always cb1.out = clk; + assign cb1.out = clk; + always write(cb1.out); + always cb1.out <= @(posedge clk) 1; + always cb1.out <= #1 1; + always out <= ##1 1; + + always @(posedge clk) begin + cb1.in = 1; + $display(cb1.out); + end +endmodule diff --git a/test_regress/t/t_clocking_concat.pl b/test_regress/t/t_clocking_concat.pl new file mode 100755 index 000000000..f5e338520 --- /dev/null +++ b/test_regress/t/t_clocking_concat.pl @@ -0,0 +1,21 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(simulator => 1); + +compile( + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_concat.v b/test_regress/t/t_clocking_concat.v new file mode 100644 index 000000000..027264dad --- /dev/null +++ b/test_regress/t/t_clocking_concat.v @@ -0,0 +1,37 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + logic[3:0] D1, D2, Q1, Q2; + always @(posedge clk) begin + {Q1, Q2} <= {D1, D2}; + end + + always @(posedge clk) $display("[%0t] posedge clk", $time); + + clocking cb @(posedge clk); + input #0 Q = {Q1, Q2}; + output #0 D = {D1, D2}; + endclocking + + initial $monitor("[%0t] --> D=%x\t\tQ=%x\t\tcb.Q=%x", $time, {D1,D2}, {Q1,Q2}, cb.Q); + + int cyc = 0; + always @ (posedge clk) begin + cyc <= cyc + 1; + if (cyc > 1 && cb.Q != {D1 - 4'd1, D2 - 4'd1}) $stop; + cb.D <= {D1 + 4'd1, D2 + 4'd1}; + if (cyc==10) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule diff --git a/test_regress/t/t_clocking_notiming.out b/test_regress/t/t_clocking_notiming.out new file mode 100644 index 000000000..4a304c9d9 --- /dev/null +++ b/test_regress/t/t_clocking_notiming.out @@ -0,0 +1,6 @@ +%Error-NOTIMING: t/t_clocking_notiming.v:11:8: Clocking output skew greater than #0 requires --timing + : ... In instance t + 11 | output #1 out; + | ^~~~~~ + ... For error description see https://verilator.org/warn/NOTIMING?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_notiming.pl b/test_regress/t/t_clocking_notiming.pl new file mode 100755 index 000000000..5a683788e --- /dev/null +++ b/test_regress/t/t_clocking_notiming.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(simulator => 1); + +compile( + verilator_flags2 => ["--no-timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_notiming.v b/test_regress/t/t_clocking_notiming.v new file mode 100644 index 000000000..1c718fda8 --- /dev/null +++ b/test_regress/t/t_clocking_notiming.v @@ -0,0 +1,13 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic clk; + logic out; + clocking cb @(posedge clk); + output #1 out; + endclocking +endmodule diff --git a/test_regress/t/t_clocking_sched.out b/test_regress/t/t_clocking_sched.out new file mode 100644 index 000000000..8ae143c29 --- /dev/null +++ b/test_regress/t/t_clocking_sched.out @@ -0,0 +1,55 @@ +0 | posedge +0 | cb.y=0 +0 | b=0 +0 | x<=0 +0 | y=0 +0 | c<=0 +0 | c<=1 +0 | cb.a=1 +0 | cb.b=1 +0 | posedge +0 | x<=1 +0 | y=1 +0 | c<=0 +0 | cb.a=0 +0 | cb.b=1 +0 | cb.y=1 +0 | b=1 +0 | x<=0 +0 | y=0 +0 | 0 1 0 0 0 0 +20 | posedge +20 | c<=1 +20 | cb.a=1 +20 | cb.b=1 +20 | cb.y=0 +20 | b=0 +20 | posedge +20 | x<=1 +20 | y=1 +20 | c<=0 +20 | cb.a=0 +20 | cb.b=1 +20 | cb.y=1 +20 | b=1 +20 | x<=0 +20 | y=0 +20 | 0 1 0 0 0 0 +30 | posedge +30 | c<=1 +30 | cb.a=1 +30 | cb.b=1 +30 | cb.y=0 +30 | b=0 +30 | posedge +30 | x<=1 +30 | y=1 +30 | c<=0 +30 | cb.a=0 +30 | cb.b=1 +30 | cb.y=1 +30 | b=1 +30 | x<=0 +30 | y=0 +30 | 0 1 0 0 0 0 +*-* All Finished *-* diff --git a/test_regress/t/t_clocking_sched.pl b/test_regress/t/t_clocking_sched.pl new file mode 100755 index 000000000..d68dacfed --- /dev/null +++ b/test_regress/t/t_clocking_sched.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(vlt => 1); + +compile( + ); + +execute( + check_finished => 1, + expect_filename => $Self->{golden_filename} + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_sched.v b/test_regress/t/t_clocking_sched.v new file mode 100644 index 000000000..44007f266 --- /dev/null +++ b/test_regress/t/t_clocking_sched.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t (/*AUTOARG*/ + // Inputs + clk + ); + input clk; + int cyc = 0; + + always @(negedge clk) begin // negedge so there is nothing after $finish + cyc <= cyc + 1; + if (cyc == 2) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + logic genclk = 0, a = 0, b = 1, c = 0, x = 0, y, z = 0; + always @(edge clk) genclk = clk; + always @(posedge genclk) $display("%0t | posedge", $time); + + // Clocking block + clocking cb @(posedge genclk); + input #0 a, y; + input #1step b; + output #0 x; +`ifdef VERILATOR_TIMING + output #2 z; +`endif + endclocking + + // Print after Observed + always @(posedge genclk) a = ~a; + always @cb $display("%0t | cb.a=%b", $time, cb.a); + always @cb $display("%0t | cb.b=%b", $time, cb.b); + always @cb.y $display("%0t | cb.y=%b", $time, cb.y); + + // Retrigger everything after Observed + always @cb.a b = x; + always @b begin + $display("%0t | b=%b", $time, b); + if (b == 0) genclk = ~genclk; + end + + // Do an NBA + always @(posedge genclk) c <= ~c; + always @c begin + $display("%0t | c<=%b", $time, c); + end + + // Print after Re-NBA + always @(posedge genclk) cb.x <= ~x; + always @x $display("%0t | x<=%b", $time, x); + + // Retrigger everything after Re-NBA + always @x y = x; + always @y begin + $display("%0t | y=%b", $time, y); + if (y == 1) genclk = ~genclk; + end + +`ifdef VERILATOR_TIMING + // Print after delay and Re-NBA + always @(posedge genclk) cb.z <= ~z; + always @z $display("%0t | z<=%b", $time, z); +`endif + + // Print in Postponed + always @(posedge genclk) $strobe("%0t | %b %b %b %b %b %b", $time, a, b, c, x, y, z); +endmodule; diff --git a/test_regress/t/t_clocking_sched_timing.out b/test_regress/t/t_clocking_sched_timing.out new file mode 100644 index 000000000..f0fee8b8e --- /dev/null +++ b/test_regress/t/t_clocking_sched_timing.out @@ -0,0 +1,58 @@ +0 | posedge +0 | cb.y=0 +0 | b=0 +0 | z<=0 +0 | x<=0 +0 | y=0 +0 | c<=0 +0 | c<=1 +0 | cb.a=1 +0 | cb.b=1 +0 | posedge +0 | x<=1 +0 | y=1 +0 | c<=0 +0 | cb.a=0 +0 | cb.b=1 +0 | cb.y=1 +0 | b=1 +0 | x<=0 +0 | y=0 +0 | 0 1 0 0 0 0 +2 | z<=1 +3 | posedge +3 | c<=1 +3 | cb.a=1 +3 | cb.b=1 +3 | cb.y=0 +3 | b=0 +3 | posedge +3 | x<=1 +3 | y=1 +3 | c<=0 +3 | cb.a=0 +3 | cb.b=1 +3 | cb.y=1 +3 | b=1 +3 | x<=0 +3 | y=0 +3 | 0 1 0 0 0 1 +5 | posedge +5 | z<=0 +5 | c<=1 +5 | cb.a=1 +5 | cb.b=1 +5 | cb.y=0 +5 | b=0 +5 | posedge +5 | x<=1 +5 | y=1 +5 | c<=0 +5 | cb.a=0 +5 | cb.b=1 +5 | cb.y=1 +5 | b=1 +5 | x<=0 +5 | y=0 +5 | 0 1 0 0 0 0 +*-* All Finished *-* diff --git a/test_regress/t/t_clocking_sched_timing.pl b/test_regress/t/t_clocking_sched_timing.pl new file mode 100755 index 000000000..7fe0b1dee --- /dev/null +++ b/test_regress/t/t_clocking_sched_timing.pl @@ -0,0 +1,31 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(vlt => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_clocking_sched.v"); + + compile( + timing_loop => 1, + verilator_flags2 => ["--timing"], + ); + + execute( + check_finished => 1, + expect_filename => $Self->{golden_filename} + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_clocking_timing.v b/test_regress/t/t_clocking_timing.v new file mode 100644 index 000000000..f918f5665 --- /dev/null +++ b/test_regress/t/t_clocking_timing.v @@ -0,0 +1,90 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +`timescale 10ns/1ns + +`ifdef TEST_VERBOSE + `define WRITE_VERBOSE(args) $write args +`else + `define WRITE_VERBOSE(args) +`endif + +`ifndef TEST_WIDTH +`define TEST_WIDTH 4 +`endif +`ifndef TEST_BITS +`define TEST_BITS 4*`TEST_WIDTH +`endif + +`ifndef TEST_CLK_PERIOD +`define TEST_CLK_PERIOD 10 +`endif +`ifndef TEST_INPUT_SKEW +`define TEST_INPUT_SKEW 2 +`endif +`ifndef TEST_OUTPUT_SKEW +`define TEST_OUTPUT_SKEW 6 +`endif +`ifndef TEST_CYCLE_DELAY +`define TEST_CYCLE_DELAY 4 +`endif + +module t; + typedef logic[`TEST_BITS-1:0] sig_t; + sig_t D, Q; + always @(posedge clk) Q <= D; + + logic clk = 0; + always #(`TEST_CLK_PERIOD/2) clk = ~clk; + + always @(posedge clk) `WRITE_VERBOSE(("[%0t] posedge clk\n", $time)); + + default clocking cb @(posedge clk); + default input #`TEST_INPUT_SKEW output #`TEST_OUTPUT_SKEW; + input Q; + output D; + endclocking + +`ifdef TEST_VERBOSE + initial $monitor("[%0t] --> D=%x\t\tQ=%x\t\tcb.Q=%x", $time, D, Q, cb.Q); +`endif + + always + begin + sig_t val = '0; + cb.D <= val; + for (int i = 0; i < 5; i++) begin ##(`TEST_CYCLE_DELAY+`TEST_OUTPUT_SKEW/`TEST_CLK_PERIOD+1) + val = {`TEST_WIDTH{(`TEST_BITS/`TEST_WIDTH)'('ha + i)}}; + `WRITE_VERBOSE(("[%0t] cb.D <= ##%0d %x\n", $time, `TEST_CYCLE_DELAY, val)); + cb.D <= ##(`TEST_CYCLE_DELAY) val; + fork + #(`TEST_CYCLE_DELAY*`TEST_CLK_PERIOD+`TEST_OUTPUT_SKEW-0.1) begin + if (D == val) begin + `WRITE_VERBOSE(("[%0t] D == %x == %x\n", $time, D, val)); + $stop; + end + if (cb.Q != D) begin + `WRITE_VERBOSE(("[%0t] cb.Q == %x != %x\n", $time, cb.Q, D)); + $stop; + end + end + #(`TEST_CYCLE_DELAY*`TEST_CLK_PERIOD+`TEST_OUTPUT_SKEW+0.1) begin + if (D != val) begin + `WRITE_VERBOSE(("[%0t] D == %x != %x\n", $time, D, val)); + $stop; + end + if (cb.Q == D) begin + `WRITE_VERBOSE(("[%0t] cb.Q == %x == %x\n", $time, cb.Q, D)); + $stop; + end + end + join_none + end + ##4 + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_clocking_timing1.pl b/test_regress/t/t_clocking_timing1.pl new file mode 100755 index 000000000..ca8f6fa4e --- /dev/null +++ b/test_regress/t/t_clocking_timing1.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_clocking_timing.v"); + + compile( + verilator_flags2 => ["--exe --main --timing"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_clocking_timing2.pl b/test_regress/t/t_clocking_timing2.pl new file mode 100755 index 000000000..6dff6484b --- /dev/null +++ b/test_regress/t/t_clocking_timing2.pl @@ -0,0 +1,30 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(simulator => 1); + +if (!$Self->have_coroutines) { + skip("No coroutine support"); +} +else { + top_filename("t/t_clocking_timing.v"); + + compile( + verilator_flags2 => ["--exe --main --timing -DTEST_INPUT_SKEW=12 -DTEST_OUTPUT_SKEW=16"], + make_main => 0, + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_clocking_unsup1.out b/test_regress/t/t_clocking_unsup1.out new file mode 100644 index 000000000..1dce09ae4 --- /dev/null +++ b/test_regress/t/t_clocking_unsup1.out @@ -0,0 +1,23 @@ +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:14:26: Unsupported: Mixed input/output clocking items + 14 | input #1 output #1step x; + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:15:8: Unsupported: 'inout' clocking items + 15 | inout y; + | ^~~~~ +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:16:15: Unsupported: clocking event edge override + 16 | output posedge #1 a; + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:17:15: Unsupported: clocking event edge override + 17 | output negedge #1 b; + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:18:15: Unsupported: clocking event edge override + 18 | output edge #1 b; + | ^~~~ +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:13:20: Unsupported: global clocking + 13 | global clocking cb @(posedge clk); + | ^~ +%Error-UNSUPPORTED: t/t_clocking_unsup1.v:21:11: Unsupported: global clocking + 21 | global clocking @(posedge clk); + | ^~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_unsup1.pl b/test_regress/t/t_clocking_unsup1.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_clocking_unsup1.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_unsup1.v b/test_regress/t/t_clocking_unsup1.v new file mode 100644 index 000000000..0eac13dc7 --- /dev/null +++ b/test_regress/t/t_clocking_unsup1.v @@ -0,0 +1,23 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + global clocking cb @(posedge clk); + input #1 output #1step x; + inout y; + output posedge #1 a; + output negedge #1 b; + output edge #1 b; + endclocking + + global clocking @(posedge clk); + endclocking +endmodule diff --git a/test_regress/t/t_clocking_unsup2.out b/test_regress/t/t_clocking_unsup2.out new file mode 100644 index 000000000..644d2e206 --- /dev/null +++ b/test_regress/t/t_clocking_unsup2.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_clocking_unsup2.v:16:11: Unsupported: ##0 delays + : ... In instance t + 16 | always ##0; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_clocking_unsup2.pl b/test_regress/t/t_clocking_unsup2.pl new file mode 100755 index 000000000..8ab3c0995 --- /dev/null +++ b/test_regress/t/t_clocking_unsup2.pl @@ -0,0 +1,20 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(linter => 1); + +lint( + verilator_flags2 => ["--timing"], + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_clocking_unsup2.v b/test_regress/t/t_clocking_unsup2.v new file mode 100644 index 000000000..17babaa15 --- /dev/null +++ b/test_regress/t/t_clocking_unsup2.v @@ -0,0 +1,17 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2022 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +module t(/*AUTOARG*/ + // Inputs + clk + ); + input clk; + + default clocking @(posedge clk); + endclocking + + always ##0; +endmodule diff --git a/test_regress/t/t_past_funcs.v b/test_regress/t/t_past_funcs.v index 2ee24f4a5..8afd03029 100644 --- a/test_regress/t/t_past_funcs.v +++ b/test_regress/t/t_past_funcs.v @@ -94,12 +94,9 @@ module Test2 (/*AUTOARG*/ end default clocking @(posedge clk); endclocking + assert property ($rose(dly0[0]) || dly0%2==0); - - default clocking @(posedge clk); endclocking assert property ($fell(dly1[0]) || dly1%2==1); - - default clocking @(posedge clk); endclocking assert property ($stable(dly2[31:4])); assert property (!$changed(dly2[31:4])); endmodule diff --git a/test_regress/t/t_timing_unset3.out b/test_regress/t/t_timing_unset3.out new file mode 100644 index 000000000..3d91db2b4 --- /dev/null +++ b/test_regress/t/t_timing_unset3.out @@ -0,0 +1,6 @@ +%Error-NEEDTIMINGOPT: t/t_clocking_notiming.v:11:8: Use --timing or --no-timing to specify how clocking output skew greater than #0 should be handled + : ... In instance t + 11 | output #1 out; + | ^~~~~~ + ... For error description see https://verilator.org/warn/NEEDTIMINGOPT?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_timing_unset3.pl b/test_regress/t/t_timing_unset3.pl new file mode 100755 index 000000000..8c206a0f0 --- /dev/null +++ b/test_regress/t/t_timing_unset3.pl @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2022 by Antmicro Ltd. 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 + +scenarios(simulator => 1); + +top_filename("t/t_clocking_notiming.v"); + +compile( + # --timing/--no-timing not specified + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1;