diff --git a/Changes b/Changes index 7ae60b8a9..dfcdfcd1f 100644 --- a/Changes +++ b/Changes @@ -11,6 +11,8 @@ indicates the contributor was also the author of the fix; Thanks! ** SystemPerl mode is deprecated and now untested. +*** Support enum.first/name and similar methods, bug848. [Jonathon Donaldson] + *** Add 'string' printing and comparisons, bug746, bug747, etc. *** Inline C functions that are used only once, msg1525. [Jie Xu] diff --git a/src/V3Ast.h b/src/V3Ast.h index 466df43b7..4099d28a2 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -239,6 +239,13 @@ public: // DT_PUBLIC, // V3LinkParse moves to AstTypedef::attrPublic // + ENUM_FIRST, // V3Width processes + ENUM_LAST, // V3Width processes + ENUM_NUM, // V3Width processes + ENUM_NEXT, // V3Width processes + ENUM_PREV, // V3Width processes + ENUM_NAME, // V3Width processes + // MEMBER_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes // VAR_BASE, // V3LinkResolve creates for AstPreSel, V3LinkParam removes @@ -259,6 +266,7 @@ public: "DIM_BITS", "DIM_DIMENSIONS", "DIM_HIGH", "DIM_INCREMENT", "DIM_LEFT", "DIM_LOW", "DIM_RIGHT", "DIM_SIZE", "DIM_UNPK_DIMENSIONS", "DT_PUBLIC", + "ENUM_FIRST", "ENUM_LAST", "ENUM_NUM", "ENUM_NEXT", "ENUM_PREV", "ENUM_NAME", "MEMBER_BASE", "VAR_BASE", "VAR_CLOCK", "VAR_CLOCK_ENABLE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT", "VAR_PUBLIC_FLAT_RD","VAR_PUBLIC_FLAT_RW", diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index 143e90254..a33d7aef7 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -881,6 +881,32 @@ public: void fromp(AstNode* nodep) { setOp1p(nodep); } }; +class AstMethodSel : public AstNode { + // A reference to a member task (or function) + // We do not support generic member calls yet, so this is only enough to make built-in methods work +private: + string m_name; // Name of variable +public: + AstMethodSel(FileLine* fl, AstNode* fromp, VFlagChildDType, const string& name, AstNode* pinsp) + : AstNode(fl), m_name(name) { + setOp1p(fromp); + dtypep(NULL); // V3Width will resolve + addNOp2p(pinsp); + } + AstMethodSel(FileLine* fl, AstNode* fromp, const string& name, AstNode* pinsp) + :AstNode(fl), m_name(name) { + setOp1p(fromp); + addNOp2p(pinsp); + } + ASTNODE_NODE_FUNCS(MethodSel, METHODSEL) + virtual string name() const { return m_name; } // * = Var name + virtual void name(const string& name) { m_name = name; } + AstNode* fromp() const { return op1p()->castNode(); } // op1 = Extracting what (NULL=TBD during parsing) + void fromp(AstNode* nodep) { setOp1p(nodep); } + AstNode* pinsp() const { return op2p()->castNode(); } // op2 = Pin interconnection list + void addPinsp(AstNode* nodep) { addOp2p(nodep); } +}; + class AstVar : public AstNode { // A variable (in/out/wire/reg/param) inside a module private: diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 82b09376a..204d29be3 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1805,6 +1805,15 @@ private: // EnumItemRef may be under a dot. Should already be resolved. nodep->iterateChildren(*this); } + virtual void visit(AstMethodSel* nodep, AstNUser*) { + // Created here so should already be resolved. + DotStates lastStates = m_ds; + { + m_ds.init(m_curSymp); + nodep->iterateChildren(*this); + } + m_ds = lastStates; + } virtual void visit(AstVar* nodep, AstNUser*) { checkNoDot(nodep); nodep->iterateChildren(*this); @@ -1823,6 +1832,14 @@ private: m_ds.m_dotp = NULL; } else if (m_ds.m_dotp && m_ds.m_dotPos == DP_FINAL) { nodep->dotted(m_ds.m_dotText); // Maybe "" + } else if (m_ds.m_dotp && m_ds.m_dotPos == DP_MEMBER) { + // Found a Var, everything following is method call. {scope}.{var}.HERE {method} ( ARGS ) + AstNode* varEtcp = m_ds.m_dotp->lhsp()->unlinkFrBack(); + AstNode* argsp = NULL; if (nodep->pinsp()) argsp = nodep->pinsp()->unlinkFrBackWithNext(); + AstNode* newp = new AstMethodSel(nodep->fileline(), varEtcp, VFlagChildDType(), nodep->name(), argsp); + nodep->replaceWith(newp); + pushDeletep(nodep); nodep=NULL; + return; } else { checkNoDot(nodep); } diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index d1d9f706c..79e39e714 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -410,12 +410,16 @@ private: condp->deleteTree(); } else if (!lvalue - && !nodep->backp()->castArraySel()) { // Too complicated and slow if mid-multidimension + && !nodep->backp()->castArraySel()) { // Too complicated and slow if mid-multidimension // ARRAYSEL(...) -> COND(LT(bitunlinkFrBack(&replaceHandle); V3Number xnum (nodep->fileline(), nodep->width()); - xnum.setAllBitsX(); + if (nodep->isString()) { + xnum = V3Number(V3Number::String(), nodep->fileline(), ""); + } else { + xnum.setAllBitsX(); + } AstNode* newp = new AstCondBound (nodep->fileline(), condp, nodep, diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b8a9fb736..415b48ea0 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1314,21 +1314,29 @@ private: if (debug()>=9) nodep->dumpTree("-ms-in-"); nodep->iterateChildren(*this,WidthVP(SELF,BOTH).p()); // Find the fromp dtype - should be a class - AstNodeDType* fromDtp = nodep->fromp()->dtypep()->skipRefp(); + AstNodeDType* fromDtp = nodep->fromp()->dtypep()->skipRefToEnump(); UINFO(9," from dt "<castNodeClassDType(); AstMemberDType* memberp = NULL; // NULL=error below - if (!fromClassp) { - nodep->v3error("Member selection of non-struct/union object '" - <fromp()->prettyTypeName()<<"' which is a '"<fromp()->dtypep()->prettyTypeName()<<"'"); - } - else { - // No need to width-resolve the fromClassp, as it was done when we did the child - memberp = fromClassp->findMember(nodep->name()); + if (AstNodeClassDType* adtypep = fromDtp->castNodeClassDType()) { + // No need to width-resolve the class, as it was done when we did the child + memberp = adtypep->findMember(nodep->name()); if (!memberp) { nodep->v3error("Member '"<prettyName()<<"' not found in structure"); } } + else if (fromDtp->castEnumDType()) { + // Method call on enum without following parenthesis, e.g. "ENUM.next" + // Convert this into a method call, and let that visitor figure out what to do next + AstNode* newp = new AstMethodSel(nodep->fileline(), nodep->fromp()->unlinkFrBack(), nodep->name(), NULL); + nodep->replaceWith(newp); + pushDeletep(nodep); nodep=NULL; + newp->accept(*this,vup); + return; + } + else { + nodep->v3error("Member selection of non-struct/union object '" + <fromp()->prettyTypeName()<<"' which is a '"<fromp()->dtypep()->prettyTypeName()<<"'"); + } if (memberp) { if (m_attrp) { // Looking for the base of the attribute nodep->dtypep(memberp); @@ -1350,6 +1358,96 @@ private: } } + virtual void visit(AstMethodSel* nodep, AstNUser* vup) { + UINFO(5," METHODSEL "<=9) nodep->dumpTree("-ms-in-"); + // Should check types the method requires, but at present we don't do much + nodep->fromp()->accept(*this,WidthVP(SELF,BOTH).p()); + for (AstArg* argp = nodep->pinsp()->castArg(); argp; argp = argp->nextp()->castArg()) { + if (argp->exprp()) argp->exprp()->accept(*this,WidthVP(SELF,BOTH).p()); + } + // Find the fromp dtype - should be a class + if (!nodep->fromp() || !nodep->fromp()->dtypep()) nodep->v3fatalSrc("Unsized expression"); + AstNodeDType* fromDtp = nodep->fromp()->dtypep()->skipRefToEnump(); + UINFO(9," from dt "<castEnumDType()) { + // Method call on enum without following parenthesis, e.g. "ENUM.next" + // Convert this into a method call, and let that visitor figure out what to do next + if (adtypep) {} + if (nodep->name() == "num" + || nodep->name() == "first" + || nodep->name() == "last") { + // Constant value + AstConst* newp = NULL; + if (nodep->pinsp()) nodep->v3error("Arguments passed to enum.num method, but it does not take arguments"); + if (nodep->name() == "num") { + int items = 0; + for (AstNode* itemp = adtypep->itemsp(); itemp; itemp = itemp->nextp()) ++items; + newp = new AstConst(nodep->fileline(), AstConst::Signed32(), items); + } else if (nodep->name() == "first") { + AstEnumItem* itemp = adtypep->itemsp(); + if (!itemp) newp = new AstConst(nodep->fileline(), AstConst::Signed32(), 0); // Spec doesn't say what to do + else newp = itemp->valuep()->cloneTree(false)->castConst(); // A const + } else if (nodep->name() == "last") { + AstEnumItem* itemp = adtypep->itemsp(); + while (itemp && itemp->nextp()) itemp = itemp->nextp()->castEnumItem(); + if (!itemp) newp = new AstConst(nodep->fileline(), AstConst::Signed32(), 0); // Spec doesn't say what to do + else newp = itemp->valuep()->cloneTree(false)->castConst(); // A const + } + if (!newp) nodep->v3fatalSrc("Enum method (perhaps enum item) not const"); + newp->fileline(nodep->fileline()); // Use method's filename/line number to be clearer; may have warning disables + nodep->replaceWith(newp); + pushDeletep(nodep); nodep=NULL; + } + else if (nodep->name() == "name" + || nodep->name() == "next" + || nodep->name() == "prev") { + AstAttrType attrType; + if (nodep->name() == "name") attrType = AstAttrType::ENUM_NAME; + else if (nodep->name() == "next") attrType = AstAttrType::ENUM_NEXT; + else if (nodep->name() == "prev") attrType = AstAttrType::ENUM_PREV; + else nodep->v3fatalSrc("Bad case"); + + if (nodep->pinsp() && nodep->name() == "name") { + nodep->v3error("Arguments passed to enum.name method, but it does not take arguments"); + } else if (nodep->pinsp() && !(nodep->pinsp()->castArg()->exprp()->castConst() + && nodep->pinsp()->castArg()->exprp()->castConst()->toUInt()==1 + && !nodep->pinsp()->nextp())) { + nodep->v3error("Unsupported: Arguments passed to enum.next method"); + } + // Need a runtime lookup table. Yuk. + // Ideally we would have a fast algorithm when a number is + // of small width and complete and so can use an array, and + // a map for when the value is many bits and sparse. + uint64_t max = 0; + { + AstEnumItem* itemp = adtypep->itemsp(); + while (itemp && itemp->nextp()) { + itemp = itemp->nextp()->castEnumItem(); + AstConst* vconstp = itemp->valuep()->castConst(); + if (!vconstp) nodep->v3fatalSrc("Enum item without constified value"); + if (vconstp->toUQuad() >= max) max = vconstp->toUQuad(); + } + if (itemp->width() > 64 || max >= 1024) { + nodep->v3error("Unsupported; enum next/prev method on enum with > 10 bits"); + return; + } + } + AstVar* varp = enumVarp(adtypep, attrType, max); + AstVarRef* varrefp = new AstVarRef(nodep->fileline(), varp, false); + varrefp->packagep(v3Global.rootp()->dollarUnitPkgAddp()); + AstNode* newp = new AstArraySel(nodep->fileline(), varrefp, nodep->fromp()->unlinkFrBack()); + nodep->replaceWith(newp); nodep->deleteTree(); nodep=NULL; + } else { + nodep->v3error("Unknown built-in enum method '"<fromp()->prettyTypeName()<<"'"); + } + } + else { + nodep->v3error("Unsupported: Member call on non-enum object '" + <fromp()->prettyTypeName()<<"' which is a '"<fromp()->dtypep()->prettyTypeName()<<"'"); + } + } + virtual void visit(AstPattern* nodep, AstNUser* vup) { if (nodep->didWidthAndSet()) return; UINFO(9,"PATTERN "<second; + } + AstNodeDType* basep; + if (attrType == AstAttrType::ENUM_NAME) { + basep = nodep->findStringDType(); + } else { + basep = nodep->findSigned32DType(); + } + AstNodeArrayDType* vardtypep = new AstUnpackArrayDType(nodep->fileline(), + basep, + new AstRange(nodep->fileline(), maxdim, 0)); + AstInitArray* initp = new AstInitArray (nodep->fileline(), vardtypep, NULL); + v3Global.rootp()->typeTablep()->addTypesp(vardtypep); + AstVar* varp = new AstVar (nodep->fileline(), AstVarType::MODULETEMP, + "__Venumtab_" + VString::downcase(attrType.ascii()) + cvtToStr(m_dtTables++), + vardtypep); + varp->isConst(true); + varp->isStatic(true); + varp->valuep(initp); + // Add to root, as don't know module we are in, and aids later structure sharing + v3Global.rootp()->dollarUnitPkgAddp()->addStmtp(varp); + + // Find valid values and populate + if (!nodep->itemsp()) nodep->v3fatalSrc("enum without items"); + vector values; + values.reserve(maxdim); + for (unsigned i=0; i<(maxdim+1); ++i) values[i] = NULL; + { + AstEnumItem* firstp = nodep->itemsp(); + AstEnumItem* prevp = firstp; // Prev must start with last item + while (prevp->nextp()) prevp = prevp->nextp()->castEnumItem(); + for (AstEnumItem* itemp = firstp; itemp;) { + AstEnumItem* nextp = itemp->nextp()->castEnumItem(); + AstConst* vconstp = itemp->valuep()->castConst(); + if (!vconstp) nodep->v3fatalSrc("Enum item without constified value"); + uint32_t i = vconstp->toUInt(); + if (attrType == AstAttrType::ENUM_NAME) { + values[i] = new AstConst(nodep->fileline(), AstConst::String(), itemp->name()); + } else if (attrType == AstAttrType::ENUM_NEXT) { + values[i] = (nextp ? nextp : firstp)->valuep()->cloneTree(false); // A const + } else if (attrType == AstAttrType::ENUM_PREV) { + values[i] = prevp->valuep()->cloneTree(false); // A const + } else { + nodep->v3fatalSrc("Bad case"); + } + prevp = itemp; + itemp = nextp; + } + } + // Fill in all unspecified values and add to table + for (unsigned i=0; i<(maxdim+1); ++i) { + AstNode* valp = values[i]; + if (!valp) { + if (attrType == AstAttrType::ENUM_NAME) { + valp = new AstConst(nodep->fileline(), AstConst::String(), ""); + } else if (attrType == AstAttrType::ENUM_NEXT + || attrType == AstAttrType::ENUM_PREV) { + valp = new AstConst(nodep->fileline(), V3Number(nodep->fileline(), nodep->width(), 0)); + } else { + nodep->v3fatalSrc("Bad case"); + } + } + initp->addInitsp(valp); + } + varp->iterate(*this); // May have already done $unit so must do this var + m_tableMap.insert(make_pair(make_pair(nodep,attrType), varp)); + return varp; + } PatVecMap patVectorMap(AstPattern* nodep, const VNumRange& range) { PatVecMap patmap; diff --git a/test_regress/t/t_enum_type_methods.v b/test_regress/t/t_enum_type_methods.v index b05af53f7..ea2ffaec1 100644 --- a/test_regress/t/t_enum_type_methods.v +++ b/test_regress/t/t_enum_type_methods.v @@ -1,48 +1,10 @@ -// DESCRIPTION: Verilator: System Verilog test of enumerated type methods +// DESCRIPTION: Verilator: Verilog Test module // -// This code instantiates a module that uses a localparam with an enumerated -// type. -// -// This file ONLY is placed into the Public Domain, for any use, without -// warranty. - -// Contributed 2012 by M W Lund, Atmel Corporation and Jeremy Bennett, Embecosm. - - -// **** Pin Identifiers **** -typedef enum int -{ - PINID_A0 = 32'd0, // MUST BE ZERO! - // - Standard Ports - - PINID_A1, PINID_A2, PINID_A3, PINID_A4, PINID_A5, PINID_A6, PINID_A7, - PINID_B0, PINID_B1, PINID_B2, PINID_B3, PINID_B4, PINID_B5, PINID_B6, PINID_B7, - PINID_C0, PINID_C1, PINID_C2, PINID_C3, PINID_C4, PINID_C5, PINID_C6, PINID_C7, - PINID_D0, PINID_D1, PINID_D2, PINID_D3, PINID_D4, PINID_D5, PINID_D6, PINID_D7, - PINID_E0, PINID_E1, PINID_E2, PINID_E3, PINID_E4, PINID_E5, PINID_E6, PINID_E7, - PINID_F0, PINID_F1, PINID_F2, PINID_F3, PINID_F4, PINID_F5, PINID_F6, PINID_F7, - PINID_G0, PINID_G1, PINID_G2, PINID_G3, PINID_G4, PINID_G5, PINID_G6, PINID_G7, - PINID_H0, PINID_H1, PINID_H2, PINID_H3, PINID_H4, PINID_H5, PINID_H6, PINID_H7, -// PINID_I0, PINID_I1, PINID_I2, PINID_I3, PINID_I4, PINID_I5, PINID_I6, PINID_I7,-> DO NOT USE!!!! I == 1 - PINID_J0, PINID_J1, PINID_J2, PINID_J3, PINID_J4, PINID_J5, PINID_J6, PINID_J7, - PINID_K0, PINID_K1, PINID_K2, PINID_K3, PINID_K4, PINID_K5, PINID_K6, PINID_K7, - PINID_L0, PINID_L1, PINID_L2, PINID_L3, PINID_L4, PINID_L5, PINID_L6, PINID_L7, - PINID_M0, PINID_M1, PINID_M2, PINID_M3, PINID_M4, PINID_M5, PINID_M6, PINID_M7, - PINID_N0, PINID_N1, PINID_N2, PINID_N3, PINID_N4, PINID_N5, PINID_N6, PINID_N7, -// PINID_O0, PINID_O1, PINID_O2, PINID_O3, PINID_O4, PINID_O5, PINID_O6, PINID_O7,-> DO NOT USE!!!! O == 0 - PINID_P0, PINID_P1, PINID_P2, PINID_P3, PINID_P4, PINID_P5, PINID_P6, PINID_P7, - PINID_Q0, PINID_Q1, PINID_Q2, PINID_Q3, PINID_Q4, PINID_Q5, PINID_Q6, PINID_Q7, - PINID_R0, PINID_R1, PINID_R2, PINID_R3, PINID_R4, PINID_R5, PINID_R6, PINID_R7, - // - AUX Port (Custom) - - PINID_X0, PINID_X1, PINID_X2, PINID_X3, PINID_X4, PINID_X5, PINID_X6, PINID_X7, - // - PDI Port - - PINID_D2W_DAT, PINID_D2W_CLK, - // - Power Pins - - PINID_VDD0, PINID_VDD1, PINID_VDD2, PINID_VDD3, - PINID_GND0, PINID_GND1, PINID_GND2, PINID_GND3, - // - Maximum number of pins - - PINID_MAX - } t_pinid; +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2014 by Wilson Snyder. +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); +`define checks(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); $stop; end while(0); module t (/*AUTOARG*/ // Inputs @@ -50,26 +12,85 @@ module t (/*AUTOARG*/ ); input clk; - wire a = clk; - wire b = 1'b0; - reg c; - - simple_test_1 simple_test_1_i (/*AUTOINST*/ - // Outputs - .c (c), - // Inputs - .a (a), - .b (b)); + typedef enum { + E01 = 1, + E03 = 3, + E04 = 4 + } my_t; - // This is a compile time only test. Immediately finish - always @(posedge clk) begin - $write("*-* All Finished *-*\n"); - $finish; + integer cyc=0; + my_t e; + + int arrayfits [e.num]; // Check can use as constant + + string all; + + // Check constification + initial begin + e = E03; + `checkh(e.first, E01); + `checkh(e.last, E04); + `checkh(e.last(), E04); + `checkh(e.next, E04); + `checkh(e.next(), E04); + `checkh(e.next(1), E04); + //Unsup: `checkh(e.next(2), E01); + `checkh(e.prev, E01); + `checkh(e.prev(1), E01); + //Unsup: `checkh(e.prev(2), E04); + `checkh(e.num, 3); + `checks(e.name, "E03"); + // + all = ""; + for (my_t e = e.first; e != e.last; e = e.next) begin + all = {all, e.name}; + end + e = e.last; + all = {all, e.name}; + `checks(all, "E01E03E04"); + end + + // Check runtime + always @ (posedge clk) begin + cyc <= cyc + 1; + if (cyc==0) begin + // Setup + e <= E01; + end + else if (cyc==1) begin + `checks(e.name, "E01"); + `checkh(e.next, E03); + `checkh(e.next(1), E03); + //Unsup: `checkh(e.next(2), E04); + `checkh(e.prev, E04); + `checkh(e.prev(1), E04); + //Unsup: `checkh(e.prev(2), E03); + e <= E03; + end + else if (cyc==2) begin + `checks(e.name, "E03"); + `checkh(e.next, E04); + `checkh(e.next(1), E04); + //Unsup: `checkh(e.next(2), E01); + `checkh(e.prev, E01); + `checkh(e.prev(1), E01); + //Unsup: `checkh(e.prev(2), E04); + e <= E04; + end + else if (cyc==3) begin + `checks(e.name, "E04"); + `checkh(e.next, E01); + `checkh(e.next(1), E01); + //Unsup: `checkh(e.next(2), E03); + `checkh(e.prev, E03); + `checkh(e.prev(1), E03); + //Unsup: `checkh(e.prev(2), E01); + e <= E01; + end + else if (cyc==99) begin + $write("*-* All Finished *-*\n"); + $finish; + end end endmodule - -// **** Simple use of parameters for sizing an array **** -module simple_test_1 (input a, b, output c); - int myarray1 [PINID_MAX]; -endmodule