diff --git a/Changes b/Changes index 83bd65f2b..204fbbc9c 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,8 @@ indicates the contributor was also the author of the fix; Thanks! * Verilator 3.71*** +** Support constant function calls for parameters. [many!] + *** Support SystemVerilog "logic", bug101. [by Alex Duller] * Verilator 3.712 2009/07/14 diff --git a/src/V3Ast.h b/src/V3Ast.h index 7615f73cf..b37967894 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -980,7 +980,6 @@ struct AstNodeFor : public AstNodeStmt { AstNode* incsp() const { return op3p()->castNode(); } // op3= increment statements AstNode* bodysp() const { return op4p()->castNode(); } // op4= body of loop virtual bool isGateOptimizable() const { return false; } - virtual bool isPredictOptimizable() const { return false; } virtual int instrCount() const { return instrCountBranch(); } virtual V3Hash sameHash() const { return V3Hash(); } virtual bool same(AstNode* samep) const { return true; } diff --git a/src/V3AstNodes.h b/src/V3AstNodes.h index a212b8412..17cb7d36c 100644 --- a/src/V3AstNodes.h +++ b/src/V3AstNodes.h @@ -1429,7 +1429,6 @@ struct AstWhile : public AstNodeStmt { void addPrecondsp(AstNode* newp) { addOp1p(newp); } void addBodysp(AstNode* newp) { addOp3p(newp); } virtual bool isGateOptimizable() const { return false; } - virtual bool isPredictOptimizable() const { return false; } virtual int instrCount() const { return instrCountBranch(); } virtual V3Hash sameHash() const { return V3Hash(); } virtual bool same(AstNode* samep) const { return true; } diff --git a/src/V3Const.cpp b/src/V3Const.cpp index dc34f1f5f..41258613f 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -39,6 +39,7 @@ #include "V3Ast.h" #include "V3Width.h" #include "V3Signed.h" +#include "V3Simulate.h" //###################################################################### // Utilities @@ -827,6 +828,28 @@ private: if (debug()>=9) newp->dumpTree(cout," _new: "); } + void replaceWithSimulation(AstNode* nodep) { + SimulateVisitor simvis; + simvis.mainParamCheck(nodep); + if (simvis.optimizable()) { // Run it - may also be unoptimizable due to large for loop + simvis.mainParamEmulate(nodep); + } + if (!simvis.optimizable()) { + AstNode* errorp = simvis.whyNotNodep(); if (!errorp) errorp = nodep; + nodep->v3error("Expecting expression to be constant, but can't determine constant for " + <prettyTypeName()<fileline()<<"... Location of non-constant " + <prettyTypeName()<<": "<v3fatalSrc("No number returned from simulation"); + // Replace it + replaceNum(nodep,*outnump); nodep=NULL; + } + } + //---------------------------------------- // VISITORS @@ -1420,6 +1443,13 @@ private: } } + virtual void visit(AstFuncRef* nodep, AstNUser*) { + nodep->iterateChildren(*this); + if (m_params) { // Only parameters force us to do constant function call propagation + replaceWithSimulation(nodep); + } + } + virtual void visit(AstWhile* nodep, AstNUser*) { nodep->iterateChildren(*this); if (nodep->condp()->isZero()) { @@ -1708,10 +1738,7 @@ public: void V3Const::constifyParam(AstNode* nodep) { //if (debug()>0) nodep->dumpTree(cout," forceConPRE : "); - if (!nodep->width()) { - V3Width::widthParams(nodep); - V3Signed::signedParams(nodep); - } + V3Width::widthSignedIfNotAlready(nodep); // Make sure we've sized everything first ConstVisitor visitor (true,false,false,false); if (AstVar* varp=nodep->castVar()) { // If a var wants to be constified, it's really a param, and diff --git a/src/V3Simulate.h b/src/V3Simulate.h index accb78fd7..4dc2548ce 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -21,7 +21,7 @@ //************************************************************************* // // void example_usage() { -// SimulateVisitor simvis (false); +// SimulateVisitor simvis (false, false); // simvis.clear(); // // Set all inputs to the constant // for (deque::iterator it = m_inVarps.begin(); it!=m_inVarps.end(); ++it) { @@ -42,6 +42,7 @@ #include "verilatedos.h" #include "V3Error.h" #include "V3Ast.h" +#include "V3Width.h" //============================================================================ @@ -72,10 +73,12 @@ private: enum VarUsage { VU_NONE=0, VU_LV=1, VU_RV=2, VU_LVDLY=4 }; // STATE + // Major mode bool m_checking; ///< Checking vs. simulation mode bool m_scoped; ///< Running with AstVarScopes instead of AstVars + bool m_params; ///< Doing parameter propagation // Checking: - const char* m_whyNotOptimizable; ///< String explaining why not optimizable or NULL to optimize + string m_whyNotOptimizable; ///< String explaining why not optimizable or NULL to optimize AstNode* m_whyNotNodep; ///< First node not optimizable bool m_anyAssignDly; ///< True if found a delayed assignment bool m_anyAssignComb; ///< True if found a non-delayed assignment @@ -98,8 +101,8 @@ public: /// Call other-this function on all new var references virtual void varRefCb(AstVarRef* nodep) {} - void clearOptimizable(AstNode* nodep/*null ok*/, const char* why) { - if (!m_whyNotOptimizable) { + void clearOptimizable(AstNode* nodep/*null ok*/, const string& why) { + if (!m_whyNotNodep) { m_whyNotNodep = nodep; if (debug()>=5) { UINFO(0,"Clear optimizable: "<user3p()) { V3Number* nump = allocNumber(nodep, value); setNumber(nodep, nump); + return nump; + } else { + return (fetchNumber(nodep)); } - return (fetchNumber(nodep)); } V3Number* newOutNumber(AstNode* nodep, uint32_t value=0) { // Set a constant value for this node if (!nodep->user2p()) { V3Number* nump = allocNumber(nodep, value); setOutNumber(nodep, nump); + return nump; + } else { + return (fetchOutNumber(nodep)); } - return (fetchOutNumber(nodep)); } V3Number* fetchNumberNull(AstNode* nodep) { return ((V3Number*)nodep->user3p()); @@ -160,6 +170,7 @@ public: V3Number* fetchNumber(AstNode* nodep) { V3Number* nump = fetchNumberNull(nodep); if (!nump) nodep->v3fatalSrc("No value found for node."); + //UINFO(9," fetch num "<<*nump<<" on "<user3p((AstNUser*)nump); } - void setOutNumber(AstNode* nodep, const V3Number* nump) { + inline void setOutNumber(AstNode* nodep, const V3Number* nump) { UINFO(9," set num "<<*nump<<" on "<user2p((AstNUser*)nump); } @@ -186,6 +197,23 @@ private: } } + void badNodeType(AstNode* nodep) { + // Call for default node types, or other node types we don't know how to handle + if (m_checking) { + checkNodeInfo(nodep); + if (optimizable()) { + // Hmm, what is this then? + // In production code, we'll just not optimize. It should be fixed though. + clearOptimizable(nodep, "Unknown node type, perhaps missing visitor in SimulateVisitor"); +#ifdef VL_DEBUG + UINFO(0,"Unknown node type in SimulateVisitor: "<prettyTypeName()<v3fatalSrc("Optimizable should have been cleared in check step, and never reach simulation."); + } + } + AstNode* varOrScope(AstVarRef* nodep) { AstNode* vscp; if (m_scoped) vscp = nodep->varScopep(); @@ -194,6 +222,11 @@ private: return vscp; } + int unrollCount() { + return m_params ? v3Global.opt.unrollCount()*16 + : v3Global.opt.unrollCount(); + } + // VISITORS virtual void visit(AstAlways* nodep, AstNUser*) { if (m_checking) checkNodeInfo(nodep); @@ -218,14 +251,14 @@ private: } } else { // nondly asn if (!(vscp->user1() & VU_LV)) { - if (vscp->user1() & VU_RV) clearOptimizable(nodep,"Var read & write"); + if (!m_params && (vscp->user1() & VU_RV)) clearOptimizable(nodep,"Var read & write"); vscp->user1( vscp->user1() | VU_LV); varRefCb (nodep); } } } else { if (!(vscp->user1() & VU_RV)) { - if (vscp->user1() & VU_LV) clearOptimizable(nodep,"Var write & read"); + if (!m_params && (vscp->user1() & VU_LV)) clearOptimizable(nodep,"Var write & read"); vscp->user1( vscp->user1() | VU_RV); varRefCb (nodep); } @@ -235,14 +268,31 @@ private: if (nodep->lvalue()) { nodep->v3fatalSrc("LHS varref should be handled in AstAssign visitor."); } else { - // Return simulation value + // Return simulation value - copy by reference instead of value for speed V3Number* nump = fetchNumberNull(vscp); - if (!nump) nodep->v3fatalSrc("Variable value should have been set before any visitor called."); + if (!nump) { + if (m_params) { + clearOptimizable(nodep,"Language violation: reference to non-function-local variable"); + } else { + nodep->v3fatalSrc("Variable value should have been set before any visitor called."); + } + nump = allocNumber(nodep, 0); // Any value; just so recover from error + } setNumber(nodep, nump); } } } + virtual void visit(AstVarXRef* nodep, AstNUser*) { + if (m_scoped) { badNodeType(nodep); return; } + else { clearOptimizable(nodep,"Language violation: Dotted hierarchical references not allowed in constant functions"); } + } + virtual void visit(AstNodeFTask* nodep, AstNUser*) { + if (!m_params) { badNodeType(nodep); return; } + checkNodeInfo(nodep); + nodep->iterateChildren(*this); + } virtual void visit(AstNodeIf* nodep, AstNUser*) { + UINFO(5," IF "<iterateChildren(*this); @@ -332,49 +382,171 @@ private: else if (!m_checking) { nodep->rhsp()->iterateAndNext(*this); AstNode* vscp = varOrScope(nodep->lhsp()->castVarRef()); + // Copy by value, not reference, as we don't want a=a+1 to get right results if (nodep->castAssignDly()) { // Don't do setNumber, as value isn't yet visible to following statements - setOutNumber(vscp, fetchNumber(nodep->rhsp())); + newOutNumber(vscp)->opAssign(*fetchNumber(nodep->rhsp())); } else { - setNumber(vscp, fetchNumber(nodep->rhsp())); - setOutNumber(vscp, fetchNumber(nodep->rhsp())); + newNumber(vscp)->opAssign(*fetchNumber(nodep->rhsp())); + newOutNumber(vscp)->opAssign(*fetchNumber(nodep->rhsp())); } } m_inDlyAssign = false; } + virtual void visit(AstNodeCase* nodep, AstNUser*) { + UINFO(5," CASE "<iterateChildren(*this); + } else { + nodep->exprp()->iterateAndNext(*this); + bool hit = false; + for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp=itemp->nextp()->castCaseItem()) { + if (!itemp->isDefault()) { + for (AstNode* ep = itemp->condsp(); ep; ep=ep->nextp()) { + if (hit) break; + ep->iterateAndNext(*this); + V3Number match (nodep->fileline(), 1); + match.opEq(*fetchNumber(nodep->exprp()), *fetchNumber(ep)); + if (match.isNeqZero()) { + itemp->bodysp()->iterateAndNext(*this); + hit = true; + } + } + } + } + // Else default match + for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp=itemp->nextp()->castCaseItem()) { + if (hit) break; + if (!hit && itemp->isDefault()) { + itemp->bodysp()->iterateAndNext(*this); + hit = true; + } + } + } + } + + virtual void visit(AstCaseItem* nodep, AstNUser*) { + // Real handling is in AstNodeCase + checkNodeInfo(nodep); + nodep->iterateChildren(*this); + } virtual void visit(AstComment*, AstNUser*) {} + + virtual void visit(AstNodeFor* nodep, AstNUser*) { + // Doing lots of Whiles is slow, so only for parameters + UINFO(5," FOR "<iterateChildren(*this); + } else { + int loops = 0; + nodep->initsp()->iterateAndNext(*this); + while (1) { + UINFO(5," FOR-ITER "<condp()->iterateAndNext(*this); + if (!fetchNumber(nodep->condp())->isNeqZero()) { + break; + } + nodep->bodysp()->iterateAndNext(*this); + nodep->incsp()->iterateAndNext(*this); + if (loops++ > unrollCount()*16) { + clearOptimizable(nodep, "Loop unrolling took too long; probably this is an infinite loop, or set --unroll-count above "+cvtToStr(unrollCount())); + break; + } + } + } + } + + virtual void visit(AstWhile* nodep, AstNUser*) { + // Doing lots of Whiles is slow, so only for parameters + UINFO(5," WHILE "<iterateChildren(*this); + } else { + int loops = 0; + while (1) { + UINFO(5," WHILE-ITER "<precondsp()->iterateAndNext(*this); + nodep->condp()->iterateAndNext(*this); + if (!fetchNumber(nodep->condp())->isNeqZero()) { + break; + } + nodep->bodysp()->iterateAndNext(*this); + if (loops++ > unrollCount()*16) { + clearOptimizable(nodep, "Loop unrolling took too long; probably this is an infinite loop, or set --unroll-count above "+cvtToStr(unrollCount())); + break; + } + } + } + } + + virtual void visit(AstFuncRef* nodep, AstNUser*) { + UINFO(5," FUNCREF "<taskp()->castFunc(); if (!funcp) nodep->v3fatalSrc("Not linked"); + V3Width::widthSignedIfNotAlready(funcp); // Make sure we've sized the function + // Apply function call values to function + // Note we'd need a stack if we allowed recursive functions! + AstNode* pinp = nodep->pinsp(); AstNode* nextpinp = NULL; + for (AstNode* stmtp = funcp->stmtsp(); stmtp; pinp=nextpinp, stmtp=stmtp->nextp()) { + if (AstVar* portp = stmtp->castVar()) { + if (portp->isIO()) { + if (pinp==NULL) { + nodep->v3error("Too few arguments in function call"); + } else { + nextpinp = pinp->nextp(); + if (portp->isOutput()) { + clearOptimizable(portp,"Language violation: Outputs not allowed in constant functions"); + return; + } + // Evaluate pin value + pinp->accept(*this); + // Apply value to the function + if (!m_checking) { + newNumber(stmtp)->opAssign(*fetchNumber(pinp)); + } + } + } + } + } + // Evaluate the function + funcp->accept(*this); + if (!m_checking) { + // Grab return value from output variable + newNumber(nodep)->opAssign(*fetchNumber(funcp->fvarp())); + } + } + + virtual void visit(AstVar* nodep, AstNUser*) { + if (!m_params) { badNodeType(nodep); return; } + } + // default // These types are definately not reducable // AstCoverInc, AstNodePli, AstArraySel, AstStop, AstFinish, // AstRand, AstTime, AstUCFunc, AstCCall, AstCStmt, AstUCStmt - // In theory, we could follow the loop, but might be slow - // AstFor, AstWhile virtual void visit(AstNode* nodep, AstNUser*) { - if (m_checking) { - checkNodeInfo(nodep); - if (optimizable()) { - // Hmm, what is this then? - // In production code, we'll just not optimize. It should be fixed though. - clearOptimizable(nodep, "Unknown node type, perhaps missing visitor in SimulateVisitor"); -#ifdef VL_DEBUG - UINFO(0,"Unknown node type in SimulateVisitor: "<prettyTypeName()<v3fatalSrc("Optimizable should have been cleared in check step, and never reach simulation."); - } + badNodeType(nodep); } public: // CONSTRUCTORS - SimulateVisitor(bool scoped, bool checking) { - m_scoped = scoped; - m_checking = checking; + SimulateVisitor() { + setMode(false,false,false); clear(); // We reuse this structure in the main loop, so put initializers inside clear() } + void setMode(bool scoped, bool checking, bool params) { + m_checking = checking; + m_scoped = scoped; + m_params = params; + } void clear() { - m_whyNotOptimizable = NULL; + m_whyNotOptimizable = ""; m_whyNotNodep = NULL; m_anyAssignComb = false; m_anyAssignDly = false; @@ -389,7 +561,20 @@ public: // Move all allocated numbers to the free pool m_numFreeps = m_numAllps; } - void main (AstNode* nodep) { + void mainTableCheck (AstNode* nodep) { + setMode(true/*scoped*/,true/*checking*/, false/*params*/); + nodep->accept(*this); + } + void mainTableEmulate (AstNode* nodep) { + setMode(true/*scoped*/,false/*checking*/, false/*params*/); + nodep->accept(*this); + } + void mainParamCheck (AstNode* nodep) { + setMode(false/*scoped*/,true/*checking*/, true/*params*/); + nodep->accept(*this); + } + void mainParamEmulate (AstNode* nodep) { + setMode(false/*scoped*/,false/*checking*/, true/*params*/); nodep->accept(*this); } virtual ~SimulateVisitor() { diff --git a/src/V3Table.cpp b/src/V3Table.cpp index faacd54d4..9b0e8268a 100644 --- a/src/V3Table.cpp +++ b/src/V3Table.cpp @@ -61,8 +61,7 @@ public: virtual void varRefCb(AstVarRef* nodep); ///< Call other-this function on all new var references // CONSTRUCTORS - TableSimulateVisitor(TableVisitor* cbthis, bool checking) - : SimulateVisitor(true, checking) { + TableSimulateVisitor(TableVisitor* cbthis) { m_cbthis = cbthis; } virtual ~TableSimulateVisitor() {} @@ -115,8 +114,8 @@ private: m_outNotSet.clear(); // Collect stats - TableSimulateVisitor chkvis (this, true); - chkvis.main(nodep); + TableSimulateVisitor chkvis (this); + chkvis.mainTableCheck(nodep); m_assignDly = chkvis.isAssignDly(); // Also sets m_inWidth // Also sets m_outWidth @@ -273,7 +272,7 @@ private: m_outNotSet.push_back(false); } uint32_t inValueNextInitArray=0; - TableSimulateVisitor simvis (this, false); + TableSimulateVisitor simvis (this); for (uint32_t inValue=0; inValue <= VL_MASK_I(m_inWidth); inValue++) { // Make a new simulation structure so we can set new input values UINFO(8," Simulating "<width()) { + V3Width::widthParams(nodep); + V3Signed::signedParams(nodep); + } +} + void V3Width::widthCommit(AstNode* nodep) { UINFO(2,__FUNCTION__<<": "< ["--lint-only"], fails=>1, expect=> -q{%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't convert a FUNCREF 'f_bad_output' to constant. -%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't convert a FUNCREF 'f_bad_dotted' to constant. -%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't convert a FUNCREF 'f_bad_nonparam' to constant. +q{%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't determine constant for FUNCREF 'f_bad_output' +%Error: t/t_func_const_bad.v:\d+: ... Location of non-constant VAR 'o': Language violation: Outputs not allowed in constant functions +%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't determine constant for FUNCREF 'f_bad_dotted' +%Error: t/t_func_const_bad.v:\d+: ... Location of non-constant VARXREF 'EIGHT': Language violation: Dotted hierarchical references not allowed in constant functions +%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't determine constant for FUNCREF 'f_bad_nonparam' +%Error: t/t_func_const_bad.v:\d+: ... Location of non-constant VARREF 'modvar': Language violation: reference to non-function-local variable +%Error: t/t_func_const_bad.v:\d+: Expecting expression to be constant, but can't determine constant for FUNCREF 'f_bad_infinite' +%Error: t/t_func_const_bad.v:\d+: ... Location of non-constant WHILE: Loop unrolling took too long; probably this is an infinite loop, or set --unroll-count above 1024 %Error: Exiting due to.*}, ); diff --git a/test_regress/t/t_func_const_bad.v b/test_regress/t/t_func_const_bad.v index d84911234..8ee4f70c0 100644 --- a/test_regress/t/t_func_const_bad.v +++ b/test_regress/t/t_func_const_bad.v @@ -6,23 +6,36 @@ module t; // Speced ignored: system calls. I think this is nasty, so we error instead. + // Speced Illegal: inout/output/ref not allowed localparam B1 = f_bad_output(1,2); function integer f_bad_output(input [31:0] a, output [31:0] o); f_bad_output = 0; endfunction + // Speced Illegal: void + // Speced Illegal: dotted localparam EIGHT = 8; localparam B2 = f_bad_dotted(2); function integer f_bad_dotted(input [31:0] a); f_bad_dotted = t.EIGHT; endfunction + // Speced Illegal: ref to non-local var integer modvar; localparam B3 = f_bad_nonparam(3); function integer f_bad_nonparam(input [31:0] a); f_bad_nonparam = modvar; endfunction + // Speced Illegal: needs constant function itself + + // Our own - infinite loop + localparam B4 = f_bad_infinite(3); + function integer f_bad_infinite(input [31:0] a); + while (1) begin + f_bad_infinite = 0; + end + endfunction endmodule diff --git a/test_regress/t/t_func_plog.v b/test_regress/t/t_func_plog.v index 8e3387deb..fe3b9f98c 100644 --- a/test_regress/t/t_func_plog.v +++ b/test_regress/t/t_func_plog.v @@ -18,19 +18,21 @@ module t (/*AUTOARG*/ /*AUTOWIRE*/ // Beginning of automatic wires (for undeclared instantiated-module outputs) - wire [2:0] pos; // From test of Test.v + wire [2:0] pos1; // From test of Test.v + wire [2:0] pos2; // From test of Test.v // End of automatics Test test ( // Outputs - .pos (pos[2:0]), + .pos1 (pos1[2:0]), + .pos2 (pos2[2:0]), /*AUTOINST*/ // Inputs .clk (clk), .rst_n (rst_n)); // Aggregate outputs into a single result vector - wire [63:0] result = {61'h0, pos}; + wire [63:0] result = {61'h0, pos1}; // What checksum will we end up with `define EXPECTED_SUM 64'h039ea4d039c2e70b @@ -54,6 +56,7 @@ module t (/*AUTOARG*/ rst_n <= ~1'b1; end else if (cyc<90) begin + if (pos1 !== pos2) $stop; end else if (cyc==99) begin $write("[%0t] cyc==%0d crc=%x sum=%x\n",$time, cyc, crc, sum); @@ -69,11 +72,12 @@ endmodule module Test #(parameter SAMPLE_WIDTH = 5 ) ( -`ifdef verilator // UNSUPPORTED - output reg [$clog2(SAMPLE_WIDTH)-1:0] pos, +`ifdef verilator // Some simulators don't support clog2 + output reg [$clog2(SAMPLE_WIDTH)-1:0] pos1, `else - output reg [log2(SAMPLE_WIDTH-1)-1:0] pos, + output reg [log2(SAMPLE_WIDTH-1)-1:0] pos1, `endif + output reg [log2(SAMPLE_WIDTH-1)-1:0] pos2, // System input clk, input rst_n @@ -88,9 +92,11 @@ module Test always @ (posedge clk or negedge rst_n) if (!rst_n) begin - pos <= 0; + pos1 <= 0; + pos2 <= 0; end else begin - pos <= pos + 1; + pos1 <= pos1 + 1; + pos2 <= pos2 + 1; end endmodule