verilator/src/V3Simulate.h
2011-01-01 18:21:19 -05:00

655 lines
22 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// -*- C++ -*-
//*************************************************************************
// DESCRIPTION: Verilator: Simulate code to determine output values/variables
//
// Code available from: http://www.veripool.org/verilator
//
// AUTHORS: Wilson Snyder with Paul Wasson, Duane Gabli
//
//*************************************************************************
//
// Copyright 2003-2011 by Wilson Snyder. 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.
//
// Verilator is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
//*************************************************************************
//
// void example_usage() {
// SimulateVisitor simvis (false, false);
// simvis.clear();
// // Set all inputs to the constant
// for (deque<AstVarScope*>::iterator it = m_inVarps.begin(); it!=m_inVarps.end(); ++it) {
// simvis.newNumber(invscp, #);
// }
// // Simulate
// simvis.main(nodep);
// // Read outputs
// for (deque<AstVarScope*>::iterator it = m_outVarps.begin(); it!=m_outVarps.end(); ++it) {
// V3Number* outnump = simvis.fetchOutNumberNull(outvscp);
//
//*************************************************************************
#ifndef _V3SIMULATE_H_
#define _V3SIMULATE_H_ 1
#include "config_build.h"
#include "verilatedos.h"
#include "V3Error.h"
#include "V3Ast.h"
#include "V3Width.h"
#include "V3Task.h"
#include <deque>
//============================================================================
//######################################################################
// Simulate class functions
class SimulateVisitor : public AstNVisitor {
// Simulate a node tree, returning value of variables
// Two major operating modes:
// Test the tree to see if it is conformant
// Given a set of input values, find the output values
// Both are done in this same visitor to reduce risk; if a visitor
// is missing, we will simply not apply the optimization, rather then bomb.
private:
// NODE STATE
// Cleared on each always/assignw
AstUser1InUse m_inuser1;
AstUser2InUse m_inuser2;
AstUser3InUse m_inuser3;
// Checking:
// AstVarScope::user1() -> VarUsage. Set true to indicate tracking as lvalue/rvalue
// Simulating:
// AstVarScope::user3() -> V3Number*. Input value of variable or node (and output for non-delayed assignments)
// AstVarScope::user2() -> V3Number*. Output value of variable (delayed assignments)
enum VarUsage { VU_NONE=0, VU_LV=1, VU_RV=2, VU_LVDLY=4 };
// STATE
// Major mode
bool m_checkOnly; ///< Checking only (no simulation) mode
bool m_scoped; ///< Running with AstVarScopes instead of AstVars
bool m_params; ///< Doing parameter propagation
// Checking:
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
bool m_inDlyAssign; ///< Under delayed assignment
int m_instrCount; ///< Number of nodes
int m_dataCount; ///< Bytes of data
AstJumpGo* m_jumpp; ///< Jump label we're branching from
// Simulating:
deque<V3Number*> m_numFreeps; ///< List of all numbers free and not in use
deque<V3Number*> m_numAllps; ///< List of all numbers free and in use
// Note level 8&9 include debugging each simulation value
static int debug() {
static int level = -1;
if (VL_UNLIKELY(level < 0)) level = v3Global.opt.debugSrcLevel(__FILE__);
return level;
}
// Checking METHODS
public:
/// Call other-this function on all new var references
virtual void varRefCb(AstVarRef* nodep) {}
void clearOptimizable(AstNode* nodep/*null ok*/, const string& why) {
// Something bad found. optimizable() will return false,
// and fetchNumber should not be called or it may assert.
if (!m_whyNotNodep) {
m_whyNotNodep = nodep;
if (debug()>=5) {
UINFO(0,"Clear optimizable: "<<why);
if (nodep) cout<<": "<<nodep;
cout<<endl;
}
m_whyNotOptimizable = why;
}
}
inline bool optimizable() const { return m_whyNotNodep==NULL; }
string whyNotMessage() const { return m_whyNotOptimizable; }
AstNode* whyNotNodep() const { return m_whyNotNodep; }
bool isAssignDly() const { return m_anyAssignDly; }
int instrCount() const { return m_instrCount; }
int dataCount() const { return m_dataCount; }
// Simulation METHODS
private:
V3Number* allocNumber(AstNode* nodep, uint32_t value) {
// Save time - kept a list of allocated but unused V3Numbers
// It would be more efficient to do this by size, but the extra accounting
// slows things down more than we gain.
V3Number* nump;
if (!m_numFreeps.empty()) {
//UINFO(7,"Num Reuse "<<nodep->width()<<endl);
nump = m_numFreeps.back(); m_numFreeps.pop_back();
nump->width(nodep->width());
nump->fileline(nodep->fileline());
nump->setLong(value); // We do support more than 32 bit numbers, just valuep=0 in that case
} else {
//UINFO(7,"Num New "<<nodep->width()<<endl);
nump = new V3Number (nodep->fileline(), nodep->width(), value);
m_numAllps.push_back(nump);
}
return nump;
}
public:
V3Number* newNumber(AstNode* nodep, uint32_t value=0) {
// Set a constant value for this node
if (!nodep->user3p()) {
V3Number* nump = allocNumber(nodep, value);
setNumber(nodep, nump);
return nump;
} else {
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));
}
}
V3Number* fetchNumberNull(AstNode* nodep) {
return ((V3Number*)nodep->user3p());
}
V3Number* fetchOutNumberNull(AstNode* nodep) {
return ((V3Number*)nodep->user2p());
}
V3Number* fetchNumber(AstNode* nodep) {
V3Number* nump = fetchNumberNull(nodep);
if (!nump) nodep->v3fatalSrc("No value found for node.");
//UINFO(9," fetch num "<<*nump<<" on "<<nodep<<endl);
return nump;
}
V3Number* fetchOutNumber(AstNode* nodep) {
V3Number* nump = fetchOutNumberNull(nodep);
if (!nump) nodep->v3fatalSrc("No value found for node.");
return nump;
}
private:
inline void setNumber(AstNode* nodep, const V3Number* nump) {
UINFO(9," set num "<<*nump<<" on "<<nodep<<endl);
nodep->user3p((AstNUser*)nump);
}
inline void setOutNumber(AstNode* nodep, const V3Number* nump) {
UINFO(9," set num "<<*nump<<" on "<<nodep<<endl);
nodep->user2p((AstNUser*)nump);
}
void checkNodeInfo(AstNode* nodep) {
if (m_checkOnly) {
m_instrCount += nodep->instrCount();
m_dataCount += nodep->width();
}
if (!nodep->isPredictOptimizable()) {
//UINFO(9," !predictopt "<<nodep<<endl);
clearOptimizable(nodep,"Isn't predictable");
}
}
void badNodeType(AstNode* nodep) {
// Call for default node types, or other node types we don't know how to handle
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: "<<nodep->prettyTypeName()<<endl);
#endif
}
}
AstNode* varOrScope(AstVarRef* nodep) {
AstNode* vscp;
if (m_scoped) vscp = nodep->varScopep();
else vscp = nodep->varp();
if (!vscp) nodep->v3fatalSrc("Not linked");
return vscp;
}
int unrollCount() {
return m_params ? v3Global.opt.unrollCount()*16
: v3Global.opt.unrollCount();
}
bool jumpingOver(AstNode* nodep) {
// True to jump over this node - all visitors must call this up front
return (m_jumpp && m_jumpp->labelp()!=nodep);
}
// VISITORS
virtual void visit(AstAlways* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
}
virtual void visit(AstSenTree* nodep, AstNUser*) {
// Sensitivities aren't inputs per se; we'll keep our tree under the same sens.
}
virtual void visit(AstVarRef* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
if (!optimizable()) return; // Accelerate
AstNode* vscp = varOrScope(nodep);
// We can't have non-delayed assignments with same value on LHS and RHS
// as we don't figure out variable ordering.
// Delayed is OK though, as we'll decode the next state separately.
if (!nodep->varp()->dtypeSkipRefp()->castBasicDType()) clearOptimizable(nodep,"Array references/not basic");
if (nodep->lvalue()) {
if (m_inDlyAssign) {
if (!(vscp->user1() & VU_LVDLY)) {
vscp->user1( vscp->user1() | VU_LVDLY);
if (m_checkOnly) varRefCb (nodep);
}
} else { // nondly asn
if (!(vscp->user1() & VU_LV)) {
if (!m_params && (vscp->user1() & VU_RV)) clearOptimizable(nodep,"Var read & write");
vscp->user1( vscp->user1() | VU_LV);
if (m_checkOnly) varRefCb (nodep);
}
}
} else {
if (!(vscp->user1() & VU_RV)) {
if (!m_params && (vscp->user1() & VU_LV)) clearOptimizable(nodep,"Var write & read");
vscp->user1( vscp->user1() | VU_RV);
if (m_checkOnly) varRefCb (nodep);
}
}
if (!m_checkOnly && optimizable()) { // simulating
if (nodep->lvalue()) {
nodep->v3fatalSrc("LHS varref should be handled in AstAssign visitor.");
} else {
// Return simulation value - copy by reference instead of value for speed
V3Number* nump = fetchNumberNull(vscp);
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 (jumpingOver(nodep)) return;
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 (jumpingOver(nodep)) return;
if (!m_params) { badNodeType(nodep); return; }
if (nodep->dpiImport()) { clearOptimizable(nodep,"DPI import functions aren't simulatable"); }
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
}
virtual void visit(AstNodeIf* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
UINFO(5," IF "<<nodep<<endl);
checkNodeInfo(nodep);
if (m_checkOnly) {
nodep->iterateChildren(*this);
} else {
nodep->condp()->iterateAndNext(*this);
if (optimizable()) {
if (fetchNumber(nodep->condp())->isNeqZero()) {
nodep->ifsp()->iterateAndNext(*this);
} else {
nodep->elsesp()->iterateAndNext(*this);
}
}
}
}
virtual void visit(AstConst* nodep, AstNUser*) {
checkNodeInfo(nodep);
if (!m_checkOnly && optimizable()) {
setNumber(nodep, &(nodep->num()));
}
}
virtual void visit(AstNodeUniop* nodep, AstNUser*) {
if (!optimizable()) return; // Accelerate
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
if (!m_checkOnly && optimizable()) {
nodep->numberOperate(*newNumber(nodep), *fetchNumber(nodep->lhsp()));
}
}
virtual void visit(AstNodeBiop* nodep, AstNUser*) {
if (!optimizable()) return; // Accelerate
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
if (!m_checkOnly && optimizable()) {
nodep->numberOperate(*newNumber(nodep), *fetchNumber(nodep->lhsp()), *fetchNumber(nodep->rhsp()));
}
}
virtual void visit(AstNodeTriop* nodep, AstNUser*) {
if (!optimizable()) return; // Accelerate
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
if (!m_checkOnly && optimizable()) {
nodep->numberOperate(*newNumber(nodep),
*fetchNumber(nodep->lhsp()),
*fetchNumber(nodep->rhsp()),
*fetchNumber(nodep->thsp()));
}
}
virtual void visit(AstNodeCond* nodep, AstNUser*) {
// We could use above visit(AstNodeTriop), but it's slower even O(n^2) to evaluate
// both sides when we really only need to evaluate one side.
if (!optimizable()) return; // Accelerate
checkNodeInfo(nodep);
if (m_checkOnly) {
nodep->iterateChildren(*this);
} else {
nodep->condp()->accept(*this);
if (optimizable()) {
if (fetchNumber(nodep->condp())->isNeqZero()) {
nodep->expr1p()->accept(*this);
newNumber(nodep)->opAssign(*fetchNumber(nodep->expr1p()));
} else {
nodep->expr2p()->accept(*this);
newNumber(nodep)->opAssign(*fetchNumber(nodep->expr2p()));
}
}
}
}
virtual void visit(AstNodeAssign* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
if (!optimizable()) return; // Accelerate
if (nodep->castAssignDly()) {
if (m_anyAssignComb) clearOptimizable(nodep, "Mix of dly/non dly assigns");
m_anyAssignDly = true;
m_inDlyAssign = true;
} else {
if (m_anyAssignDly) clearOptimizable(nodep, "Mix of dly/non dly assigns");
m_anyAssignComb = true;
}
if (!nodep->lhsp()->castVarRef()) {
clearOptimizable(nodep, "LHS isn't simple variable");
}
else if (m_checkOnly) {
nodep->iterateChildren(*this);
}
else if (optimizable()) {
nodep->rhsp()->iterateAndNext(*this);
if (optimizable()) {
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
newOutNumber(vscp)->opAssign(*fetchNumber(nodep->rhsp()));
} else {
newNumber(vscp)->opAssign(*fetchNumber(nodep->rhsp()));
newOutNumber(vscp)->opAssign(*fetchNumber(nodep->rhsp()));
}
}
}
m_inDlyAssign = false;
}
virtual void visit(AstBegin* nodep, AstNUser*) {
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
}
virtual void visit(AstNodeCase* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
UINFO(5," CASE "<<nodep<<endl);
checkNodeInfo(nodep);
if (m_checkOnly) {
nodep->iterateChildren(*this);
} else if (optimizable()) {
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);
if (optimizable()) {
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
if (jumpingOver(nodep)) return;
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
}
virtual void visit(AstComment*, AstNUser*) {}
virtual void visit(AstJumpGo* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
checkNodeInfo(nodep);
if (!m_checkOnly) {
UINFO(5," JUMP GO "<<nodep<<endl);
m_jumpp = nodep;
}
}
virtual void visit(AstJumpLabel* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
checkNodeInfo(nodep);
nodep->iterateChildren(*this);
if (m_jumpp && m_jumpp->labelp() == nodep) {
UINFO(5," JUMP DONE "<<nodep<<endl);
m_jumpp = NULL;
}
}
virtual void visit(AstStop* nodep, AstNUser*) {
if (m_params) { // This message seems better than an obscure $stop
// The spec says $stop is just ignored, it seems evil to ignore assertions
clearOptimizable(nodep,"$stop executed during function constification; maybe indicates assertion firing");
}
checkNodeInfo(nodep);
}
virtual void visit(AstNodeFor* nodep, AstNUser*) {
// Doing lots of Whiles is slow, so only for parameters
UINFO(5," FOR "<<nodep<<endl);
if (!m_params) { badNodeType(nodep); return; }
checkNodeInfo(nodep);
if (m_checkOnly) {
nodep->iterateChildren(*this);
} else if (optimizable()) {
int loops = 0;
nodep->initsp()->iterateAndNext(*this);
while (1) {
UINFO(5," FOR-ITER "<<nodep<<endl);
nodep->condp()->iterateAndNext(*this);
if (!optimizable()) break;
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
if (jumpingOver(nodep)) return;
UINFO(5," WHILE "<<nodep<<endl);
if (!m_params) { badNodeType(nodep); return; }
checkNodeInfo(nodep);
if (m_checkOnly) {
nodep->iterateChildren(*this);
} else if (optimizable()) {
int loops = 0;
while (1) {
UINFO(5," WHILE-ITER "<<nodep<<endl);
nodep->precondsp()->iterateAndNext(*this);
if (jumpingOver(nodep)) break;
nodep->condp()->iterateAndNext(*this);
if (jumpingOver(nodep)) break;
if (!optimizable()) break;
if (!fetchNumber(nodep->condp())->isNeqZero()) {
break;
}
nodep->bodysp()->iterateAndNext(*this);
if (jumpingOver(nodep)) break;
nodep->incsp()->iterateAndNext(*this);
if (jumpingOver(nodep)) break;
// Prep for next loop
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*) {
if (jumpingOver(nodep)) return;
UINFO(5," FUNCREF "<<nodep<<endl);
if (!m_params) { badNodeType(nodep); return; }
AstNodeFTask* funcp = nodep->taskp()->castNodeFTask(); if (!funcp) nodep->v3fatalSrc("Not linked");
if (m_params) { V3Width::widthParamsEdit(funcp); } funcp=NULL; // Make sure we've sized the function
funcp = nodep->taskp()->castNodeFTask(); if (!funcp) nodep->v3fatalSrc("Not linked");
// Apply function call values to function
// Note we'd need a stack if we allowed recursive functions!
V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp());
for (V3TaskConnects::iterator it=tconnects.begin(); it!=tconnects.end(); ++it) {
AstVar* portp = it->first;
AstNode* pinp = it->second;
if (pinp==NULL) {
// Too few arguments in function call - ignore it
} else {
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_checkOnly && optimizable()) {
newNumber(portp)->opAssign(*fetchNumber(pinp));
}
}
}
// Evaluate the function
funcp->accept(*this);
if (!m_checkOnly && optimizable()) {
// Grab return value from output variable (if it's a function)
if (!funcp->fvarp()) nodep->v3fatalSrc("Function reference points at non-function");
newNumber(nodep)->opAssign(*fetchNumber(funcp->fvarp()));
}
}
virtual void visit(AstVar* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
if (!m_params) { badNodeType(nodep); return; }
}
// default
// These types are definately not reducable
// AstCoverInc, AstDisplay, AstArraySel, AstStop, AstFinish,
// AstRand, AstTime, AstUCFunc, AstCCall, AstCStmt, AstUCStmt
virtual void visit(AstNode* nodep, AstNUser*) {
if (jumpingOver(nodep)) return;
badNodeType(nodep);
}
private:
// MEMBERS - called by constructor
void setMode(bool scoped, bool checkOnly, bool params) {
m_checkOnly = checkOnly;
m_scoped = scoped;
m_params = params;
}
void mainGuts(AstNode* nodep) {
nodep->accept(*this);
if (m_jumpp) {
m_jumpp->v3fatalSrc("JumpGo branched to label that wasn't found");
m_jumpp = NULL;
}
}
public:
// CONSTRUCTORS
SimulateVisitor() {
setMode(false,false,false);
clear(); // We reuse this structure in the main loop, so put initializers inside clear()
}
void clear() {
m_whyNotOptimizable = "";
m_whyNotNodep = NULL;
m_anyAssignComb = false;
m_anyAssignDly = false;
m_inDlyAssign = false;
m_instrCount = 0;
m_dataCount = 0;
m_jumpp = NULL;
AstNode::user1ClearTree(); // user1p() used on entire tree
AstNode::user2ClearTree(); // user2p() used on entire tree
AstNode::user3ClearTree(); // user3p() used on entire tree
// Move all allocated numbers to the free pool
m_numFreeps = m_numAllps;
}
void mainTableCheck (AstNode* nodep) {
setMode(true/*scoped*/,true/*checking*/, false/*params*/);
mainGuts(nodep);
}
void mainTableEmulate (AstNode* nodep) {
setMode(true/*scoped*/,false/*checking*/, false/*params*/);
mainGuts(nodep);
}
void mainParamEmulate (AstNode* nodep) {
setMode(false/*scoped*/,false/*checking*/, true/*params*/);
mainGuts(nodep);
}
virtual ~SimulateVisitor() {
for (deque<V3Number*>::iterator it = m_numAllps.begin(); it != m_numAllps.end(); ++it) {
delete (*it);
}
m_numFreeps.clear();
m_numAllps.clear();
}
};
#endif // Guard