diff --git a/Changes b/Changes index a57ebbf3d..ffa68c30d 100644 --- a/Changes +++ b/Changes @@ -4,6 +4,8 @@ The contributors that suggested a given feature are shown in []. Thanks! * Verilator 4.103 devel +**** Support queue slicing (#2326). + * Verilator 4.102 2020-10-15 diff --git a/include/verilated_heavy.h b/include/verilated_heavy.h index 8b7412f03..86c8a5c5b 100644 --- a/include/verilated_heavy.h +++ b/include/verilated_heavy.h @@ -272,6 +272,32 @@ public: } ~VlQueue() {} // Standard copy constructor works. Verilog: assoca = assocb + static VlQueue cons(const T_Value& lhs) { + VlQueue out; + out.push_back(lhs); + return out; + } + static VlQueue cons(const T_Value& lhs, const T_Value& rhs) { + VlQueue out; + out.push_back(rhs); + out.push_back(lhs); + return out; + } + static VlQueue cons(const VlQueue& lhs, const T_Value& rhs) { + VlQueue out = lhs; + out.push_front(rhs); + return out; + } + static VlQueue cons(const T_Value& lhs, const VlQueue& rhs) { + VlQueue out = rhs; + out.push_back(lhs); + return out; + } + static VlQueue cons(const VlQueue& lhs, const VlQueue& rhs) { + VlQueue out = rhs; + for (const auto& i : lhs.m_deque) out.push_back(i); + return out; + } // METHODS T_Value& atDefault() { return m_defaultValue; } @@ -352,6 +378,15 @@ public: m_deque.insert(m_deque.begin() + index, value); } + // Return slice q[lsb:msb] + VlQueue slice(size_t lsb, size_t msb) const { + VlQueue out; + if (VL_UNLIKELY(lsb >= m_deque.size())) lsb = m_deque.size() - 1; + if (VL_UNLIKELY(msb >= m_deque.size())) msb = m_deque.size() - 1; + for (size_t i = lsb; i <= msb; ++i) out.push_back(m_deque[i]); + return out; + } + // For save/restore const_iterator begin() const { return m_deque.begin(); } const_iterator end() const { return m_deque.end(); } diff --git a/src/V3Ast.h b/src/V3Ast.h index fa4b9083c..47395909c 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1254,7 +1254,10 @@ public: /// along with all children and next(s). This is often better to use /// than an immediate deleteTree, as any pointers into this node will /// persist for the lifetime of the visitor - void pushDeletep(AstNode* nodep) { m_deleteps.push_back(nodep); } + void pushDeletep(AstNode* nodep) { + UASSERT_STATIC(nodep, "Cannot delete nullptr node"); + m_deleteps.push_back(nodep); + } /// Call deleteTree on all previously pushDeletep()'ed nodes void doDeletes(); diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 25400841a..69e448bd8 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -4599,6 +4599,28 @@ public: virtual bool same(const AstNode* samep) const override { return true; } }; +class AstConsQueue : public AstNodeMath { + // Construct a queue and return object, '{}. '{lhs}, '{lhs. rhs} + // Parents: math + // Children: expression (elements or other queues) +public: + AstConsQueue(FileLine* fl, AstNode* lhsp = nullptr, AstNode* rhsp = nullptr) + : ASTGEN_SUPER(fl) { + setNOp1p(lhsp); + setNOp2p(rhsp); + } + ASTNODE_NODE_FUNCS(ConsQueue) + virtual string emitVerilog() override { return "'{%l, %r}"; } + virtual string emitC() override { V3ERROR_NA_RETURN(""); } + virtual string emitSimpleOperator() override { V3ERROR_NA_RETURN(""); } + virtual bool cleanOut() const override { return true; } + virtual int instrCount() const override { return widthInstrs(); } + AstNode* lhsp() const { return op1p(); } // op1 = expression + AstNode* rhsp() const { return op2p(); } // op2 = expression + virtual V3Hash sameHash() const override { return V3Hash(); } + virtual bool same(const AstNode* samep) const override { return true; } +}; + class AstBegin : public AstNodeBlock { // A Begin/end named block, only exists shortly after parsing until linking // Parents: statement diff --git a/src/V3EmitC.cpp b/src/V3EmitC.cpp index b9b267cb9..e84182200 100644 --- a/src/V3EmitC.cpp +++ b/src/V3EmitC.cpp @@ -1575,6 +1575,22 @@ class EmitCImp : EmitCStmts { } } + virtual void visit(AstConsQueue* nodep) override { + putbs(nodep->dtypep()->cType("", false, false)); + if (!nodep->lhsp()) { + puts("()"); + } else { + puts("::cons("); + iterateAndNextNull(nodep->lhsp()); + if (nodep->rhsp()) { + puts(", "); + putbs(""); + } + iterateAndNextNull(nodep->rhsp()); + puts(")"); + } + } + virtual void visit(AstChangeDet* nodep) override { // m_blkChangeDetVec.push_back(nodep); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index ebba38d68..f2b7ca4ab 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -484,12 +484,20 @@ private: // LHS, RHS is self-determined // signed: Unsigned (11.8.1) // width: LHS + RHS + AstNodeDType* vdtypep = m_vup->dtypeNullSkipRefp(); + if (VN_IS(vdtypep, QueueDType)) { + // Queue "element 0" is lhsp, so we need to swap arguments + auto* newp = new AstConsQueue(nodep->fileline(), nodep->rhsp()->unlinkFrBack(), + nodep->lhsp()->unlinkFrBack()); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + userIterateChildren(newp, m_vup); + return; + } if (m_vup->prelim()) { - AstNodeDType* vdtypep = m_vup->dtypeNullSkipRefp(); - if (vdtypep - && (VN_IS(vdtypep, AssocArrayDType) // - || VN_IS(vdtypep, DynArrayDType) // - || VN_IS(vdtypep, QueueDType))) { + if (VN_IS(vdtypep, AssocArrayDType) // + || VN_IS(vdtypep, DynArrayDType) // + || VN_IS(vdtypep, QueueDType)) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: Concatenation to form " << vdtypep->prettyDTypeNameQ() << "data type"); } @@ -609,14 +617,6 @@ private: // LHS, RHS is self-determined // width: value(LHS) * width(RHS) if (m_vup->prelim()) { - AstNodeDType* vdtypep = m_vup->dtypeNullSkipRefp(); - if (vdtypep - && (VN_IS(vdtypep, AssocArrayDType) || VN_IS(vdtypep, DynArrayDType) - || VN_IS(vdtypep, QueueDType) || VN_IS(vdtypep, UnpackArrayDType))) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: Replication to form " - << vdtypep->prettyDTypeNameQ() << " data type"); - } - iterateCheckSizedSelf(nodep, "LHS", nodep->lhsp(), SELF, BOTH); iterateCheckSizedSelf(nodep, "RHS", nodep->rhsp(), SELF, BOTH); V3Const::constifyParamsEdit(nodep->rhsp()); // rhsp may change const AstConst* constp = VN_CAST(nodep->rhsp(), Const); @@ -631,6 +631,26 @@ private: "1800-2017 11.4.12.1)"); times = 1; } + + AstNodeDType* vdtypep = m_vup->dtypeNullSkipRefp(); + if (VN_IS(vdtypep, QueueDType)) { + if (times != 1) + nodep->v3warn(E_UNSUPPORTED, "Unsupported: Non-1 replication to form " + << vdtypep->prettyDTypeNameQ() + << " data type"); + // Don't iterate lhsp as SELF, the potential Concat below needs + // the adtypep passed down to recognize the QueueDType + userIterateAndNext(nodep->lhsp(), WidthVP(vdtypep, BOTH).p()); + nodep->replaceWith(nodep->lhsp()->unlinkFrBack()); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + if (VN_IS(vdtypep, AssocArrayDType) || VN_IS(vdtypep, DynArrayDType) + || VN_IS(vdtypep, UnpackArrayDType)) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: Replication to form " + << vdtypep->prettyDTypeNameQ() << " data type"); + } + iterateCheckSizedSelf(nodep, "LHS", nodep->lhsp(), SELF, BOTH); if (nodep->lhsp()->isString()) { AstNode* newp = new AstReplicateN(nodep->fileline(), nodep->lhsp()->unlinkFrBack(), nodep->rhsp()->unlinkFrBack()); @@ -1133,10 +1153,21 @@ private: } virtual void visit(AstUnbounded* nodep) override { nodep->dtypeSetSigned32(); // Used in int context - if (!VN_IS(nodep->backp(), IsUnbounded) && !VN_IS(nodep->backp(), BracketArrayDType) - && !(VN_IS(nodep->backp(), Var) && VN_CAST(nodep->backp(), Var)->isParam())) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported/illegal unbounded ('$') in this context."); + if (VN_IS(nodep->backp(), IsUnbounded)) return; // Ok, leave + if (VN_IS(nodep->backp(), BracketArrayDType)) return; // Ok, leave + if (auto* varp = VN_CAST(nodep->backp(), Var)) { + if (varp->isParam()) return; // Ok, leave } + // queue_slice[#:$] + if (auto* selp = VN_CAST(nodep->backp(), SelExtract)) { + if (VN_IS(selp->fromp()->dtypep(), QueueDType)) { + nodep->replaceWith( + new AstConst(nodep->fileline(), AstConst::Signed32(), 0x7FFFFFFF)); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + } + nodep->v3warn(E_UNSUPPORTED, "Unsupported/illegal unbounded ('$') in this context."); } virtual void visit(AstIsUnbounded* nodep) override { if (m_vup->prelim()) { @@ -1928,6 +1959,38 @@ private: } nodep->dtypeFrom(nodep->itemp()); } + virtual void visit(AstConsQueue* nodep) override { // + // Type computed when constructed here + AstQueueDType* vdtypep = VN_CAST(m_vup->dtypep(), QueueDType); + UASSERT_OBJ(vdtypep, nodep, "ConsQueue requires queue upper parent data type"); + if (m_vup->prelim()) { + userIterateAndNext(nodep->lhsp(), WidthVP(vdtypep, PRELIM).p()); + userIterateAndNext(nodep->rhsp(), WidthVP(vdtypep, PRELIM).p()); + nodep->dtypeFrom(vdtypep); + } + if (m_vup->final()) { + // Arguments can be either elements of the queue or a queue itself + // Concats (part of tree of concats) must always become ConsQueue's + if (nodep->lhsp()) { + if (VN_IS(nodep->lhsp()->dtypep(), QueueDType) + || VN_IS(nodep->lhsp(), ConsQueue)) { + userIterateAndNext(nodep->lhsp(), WidthVP(vdtypep, FINAL).p()); + } else { + // Sub elements are not queues, but concats, must always pass concats down + iterateCheckTyped(nodep, "LHS", nodep->lhsp(), vdtypep->subDTypep(), FINAL); + } + } + if (nodep->rhsp()) { + if (VN_IS(nodep->rhsp()->dtypep(), QueueDType) + || VN_IS(nodep->rhsp(), ConsQueue)) { + userIterateAndNext(nodep->rhsp(), WidthVP(vdtypep, FINAL).p()); + } else { + iterateCheckTyped(nodep, "RHS", nodep->rhsp(), vdtypep->subDTypep(), FINAL); + } + } + nodep->dtypeFrom(vdtypep); + } + } virtual void visit(AstInitItem* nodep) override { // userIterateChildren(nodep, m_vup); } @@ -2490,12 +2553,10 @@ private: newp->didWidth(true); newp->makeStatement(); } else { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: Queue .delete(index) method, as is O(n) " - "complexity and slow."); newp = new AstCMethodHard(nodep->fileline(), nodep->fromp()->unlinkFrBack(), "erase", index_exprp->unlinkFrBack()); newp->protect(false); + newp->didWidth(true); newp->makeStatement(); } } @@ -2511,9 +2572,6 @@ private: newp->protect(false); newp->makeStatement(); } else { - nodep->v3warn( - E_UNSUPPORTED, - "Unsupported: Queue .insert method, as is O(n) complexity and slow."); newp = new AstCMethodHard(nodep->fileline(), nodep->fromp()->unlinkFrBack(), nodep->name(), index_exprp->unlinkFrBack()); newp->addPinsp(argp->exprp()->unlinkFrBack()); @@ -2874,10 +2932,12 @@ private: while (const AstConstDType* vdtypep = VN_CAST(dtypep, ConstDType)) { dtypep = vdtypep->subDTypep()->skipRefp(); } - if (AstNodeUOrStructDType* vdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (auto* vdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { VL_DO_DANGLING(patternUOrStruct(nodep, vdtypep, defaultp), nodep); - } else if (AstNodeArrayDType* vdtypep = VN_CAST(dtypep, NodeArrayDType)) { + } else if (auto* vdtypep = VN_CAST(dtypep, NodeArrayDType)) { VL_DO_DANGLING(patternArray(nodep, vdtypep, defaultp), nodep); + } else if (auto* vdtypep = VN_CAST(dtypep, QueueDType)) { + VL_DO_DANGLING(patternQueue(nodep, vdtypep, defaultp), nodep); } else if (VN_IS(dtypep, BasicDType) && VN_CAST(dtypep, BasicDType)->isRanged()) { VL_DO_DANGLING(patternBasic(nodep, dtypep, defaultp), nodep); } else { @@ -3038,6 +3098,21 @@ private: // if (debug() >= 9) newp->dumpTree("-apat-out: "); VL_DO_DANGLING(pushDeletep(nodep), nodep); // Deletes defaultp also, if present } + void patternQueue(AstPattern* nodep, AstQueueDType* arrayp, AstPatMember* defaultp) { + AstNode* newp = new AstConsQueue(nodep->fileline()); + newp->dtypeFrom(arrayp); + for (AstPatMember* patp = VN_CAST(nodep->itemsp(), PatMember); patp; + patp = VN_CAST(patp->nextp(), PatMember)) { + patp->dtypep(arrayp->subDTypep()); + AstNode* valuep = patternMemberValueIterate(patp); + auto* newap = new AstConsQueue(nodep->fileline(), valuep, newp); + newap->dtypeFrom(arrayp); + newp = newap; + } + nodep->replaceWith(newp); + // if (debug() >= 9) newp->dumpTree("-apat-out: "); + VL_DO_DANGLING(pushDeletep(nodep), nodep); // Deletes defaultp also, if present + } void patternBasic(AstPattern* nodep, AstNodeDType* vdtypep, AstPatMember* defaultp) { AstBasicDType* bdtypep = VN_CAST(vdtypep, BasicDType); VNumRange range = bdtypep->declRange(); diff --git a/src/V3WidthSel.cpp b/src/V3WidthSel.cpp index ce6cdcee8..8e3b09316 100644 --- a/src/V3WidthSel.cpp +++ b/src/V3WidthSel.cpp @@ -439,6 +439,15 @@ private: // if (debug() >= 9) newp->dumpTree(cout, "--SELEXnew: "); nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); + } else if (VN_IS(ddtypep, QueueDType)) { + auto* newp = new AstCMethodHard(nodep->fileline(), fromp, "slice", msbp); + msbp->addNext(lsbp); + newp->dtypep(ddtypep); + newp->didWidth(true); + newp->protect(false); + UINFO(6, " new " << newp << endl); + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); } else { // nullptr=bad extract, or unknown node type nodep->v3error("Illegal range select; type already selected, or bad dimension: " << "data type is " << fromdata.m_errp->prettyDTypeNameQ()); diff --git a/src/verilog.y b/src/verilog.y index 414043f6b..eee186507 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3403,8 +3403,7 @@ assignment_pattern: // ==IEEE: assignment_pattern // // also IEEE "''{' array_pattern_key ':' ... | yP_TICKBRA patternMemberList '}' { $$ = new AstPattern($1,$2); } // // IEEE: Not in grammar, but in VMM - | yP_TICKBRA '}' - { $$ = new AstPattern($1, nullptr); $1->v3warn(E_UNSUPPORTED, "Unsupported: Empty '{}"); } + | yP_TICKBRA '}' { $$ = new AstPattern($1, nullptr); } ; // "datatype id = x {, id = x }" | "yaId = x {, id=x}" is legal diff --git a/test_regress/t/t_queue_slice.out b/test_regress/t/t_queue_slice.out deleted file mode 100644 index ec304cf36..000000000 --- a/test_regress/t/t_queue_slice.out +++ /dev/null @@ -1,4 +0,0 @@ -%Error-UNSUPPORTED: t/t_queue_slice.v:21:11: Unsupported: Empty '{} - 21 | q = '{}; - | ^~ -%Error: Exiting due to diff --git a/test_regress/t/t_queue_slice.pl b/test_regress/t/t_queue_slice.pl index be66c40e6..b46d46042 100755 --- a/test_regress/t/t_queue_slice.pl +++ b/test_regress/t/t_queue_slice.pl @@ -11,13 +11,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di scenarios(simulator => 1); compile( - fails => $Self->{vlt_all}, - expect_filename => $Self->{golden_filename}, ); execute( check_finished => 1, - ) if !$Self->{vlt_all}; + ); ok(1); 1;