Support DPI exports

This commit is contained in:
Wilson Snyder 2009-12-20 08:27:00 -05:00
parent 45aa8742d8
commit ef3ed6e338
21 changed files with 878 additions and 160 deletions

View File

@ -12,7 +12,7 @@ indicates the contributor was also the author of the fix; Thanks!
** Support typedef. [Donal Casey]
** Support direct programming interface (DPI) "import".
** Support direct programming interface (DPI) "import" and "export".
*** Support "reg [1:0][1:0][1:0]" and "reg x [3][2]", bug176. [Byron Bradley]

View File

@ -1149,11 +1149,12 @@ order of magnitude.
=head1 DPI (DIRECT PROGRAMMING INTERFACE)
Verilator supports SystemVerilog Direct Programming Interface import
statements. Only the SystemVerilog form ("DPI-C") is supported, not the
original Synopsys-only DPI.
Verilator supports SystemVerilog Direct Programming Interface import and
export statements. Only the SystemVerilog form ("DPI-C") is supported, not
the original Synopsys-only DPI.
In the SYSTEMC example above, if you had in our.v:
In the SYSTEMC example above, if you wanted to import C++ functions into
Verilog, put in our.v:
import "DPI-C" function integer add (input integer a, input integer b);
@ -1181,6 +1182,36 @@ function name for the import, but note it must be escaped.
initial $display("myRand=%d", $myRand());
Going the other direction, you can export Verilog tasks so they can be
called from C++:
export "DPI-C" task publicSetBool;
task publicSetBool;
input bit in_bool;
var_bool = in_bool;
endtask
Then after Verilating, Verilator will create a file Vour__Dpi.h with the
prototype to call this function:
extern bool publicSetBool(bool in_bool);
From the sc_main.cpp file, you'd then:
#include "Vour__Dpi.h"
publicSetBool(value);
Or, alternatively, call the function under the design class. This isn't
DPI compatible but is easier to read and better supports multiple designs.
#include "Vour__Dpi.h"
Vour::publicSetBool(value);
// or top->publicSetBool(value);
Instead of DPI exporting, there's also Verilator public functions, which
are slightly faster, but less compatible.
=head1 CROSS COMPILATION
Verilator supports cross-compiling Verilated code. This is generally used
@ -1518,10 +1549,14 @@ be pure; they cannot reference any variables outside the task itself.
=item /*verilator public*/ (variable)
Used after a input, output, register, or wire declaration to indicate the
signal should be declared so that C code may read or write the value
of the signal. This will also declare this module public, otherwise use
signal should be declared so that C code may read or write the value of the
signal. This will also declare this module public, otherwise use
/*verilator public_flat*/.
Instead of using public variables, consider instead making a DPI or public
function that accesses the variable. This is nicer as it provides an
obvious entry point that is also compatible across simulators.
=item /*verilator public*/ (task/function)
Used inside the declaration section of a function or task declaration to
@ -1541,6 +1576,9 @@ Generally, only the values of stored state (flops) should be written, as
the model will NOT notice changes made to variables in these functions.
(Same as when a signal is declared public.)
You may want to use DPI exports instead, as it's compatible with other
simulators.
=item /*verilator public_flat*/ (variable)
Used after a input, output, register, or wire declaration to indicate the
@ -2555,42 +2593,17 @@ faster, but can result in more compilations when something changes.
=item How do I access functions/tasks in C?
Write a Verilog function or task with input/outputs that match what you
want to call in with C. Then mark that function public.
Verilog inputs of one bit become C++ bool inputs. Inputs 32 bits or
smaller become C uint32_t inputs, 64-32 bits become C uint64_t inputs, and
wider signals become arrays of 32 bits. Outputs are passed as references
to bool, uint32_t, uint64_t or uint32_t[] arrays.
Signals wider than 64 bits are passed as an array of 32-bit uint32_t's.
Thus to read bits 31:0, access signal[0], and for bits 63:32, access
signal[1]. Unused bits (for example bit numbers 65-96 of a 65 bit vector)
will always be zero. if you change the value you must make sure to pack
zeros in the unused bits or core-dumps may result. (Because Verilator
strips array bound checks where it believes them to be unnecessary.)
In the SYSTEMC example above, if you had in our.v:
task publicSetBool;
// verilator public
input in_bool;
var_bool = in_bool;
endtask
From the sc_main.cpp file, you'd then:
#include "Vour.h"
#include "Vour_our.h"
top->v->publicSetBool(value);
See additional notes under the /*verilator public*/ section.
Use the SystemVerilog Direct Programming Interface. You write a Verilog
function or task with input/outputs that match what you want to call in
with C. Then mark that function as an external function. See the DPI
chapter in the manual.
=item How do I access signals in C?
The best thing is to make a Verilator public task or function accessor that
can read or write that signal, as described in the previous FAQ. This will
allow Verilator to better optimize the model.
The best thing is to make a SystemVerilog "export DPI task" or function
that accesses that signal, as described in the DPI chapter in the manual
and DPI tutorials on the web. This will allow Verilator to better optimize
the model and should be portable across simulators.
If you really want raw access to the signals, declare the signals you will
be accessing with a /*verilator public*/ comment before the closing

View File

@ -919,6 +919,10 @@ void Verilated::scopesDump() {
VerilatedImp::scopesDump();
}
int Verilated::exportFuncNum(const char* namep) {
return VerilatedImp::exportFind(namep);
}
//===========================================================================
// VerilatedModule:: Methods
@ -933,14 +937,23 @@ VerilatedModule::~VerilatedModule() {
//======================================================================
// VerilatedScope:: Methods
VerilatedScope::~VerilatedScope() {
VerilatedImp::scopeErase(this);
delete [] m_namep; m_namep = NULL;
VerilatedScope::VerilatedScope() {
m_callbacksp = NULL;
m_namep = NULL;
m_funcnumMax = 0;
}
void VerilatedScope::configure(const char* prefixp, const char* suffixp) {
// Slow ok - called once/scope at construction
VerilatedScope::~VerilatedScope() {
VerilatedImp::scopeErase(this);
if (m_namep) { delete [] m_namep; m_namep = NULL; }
if (m_callbacksp) { delete [] m_callbacksp; m_callbacksp = NULL; }
m_funcnumMax = 0; // Force callback table to empty
}
void VerilatedScope::configure(VerilatedSyms* symsp, const char* prefixp, const char* suffixp) {
// Slowpath - called once/scope at construction
// We don't want the space and reference-count access overhead of strings.
m_symsp = symsp;
char* namep = new char[strlen(prefixp)+strlen(suffixp)+2];
strcpy(namep, prefixp);
strcat(namep, suffixp);
@ -948,4 +961,52 @@ void VerilatedScope::configure(const char* prefixp, const char* suffixp) {
VerilatedImp::scopeInsert(this);
}
void VerilatedScope::exportInsert(bool finalize, const char* namep, void* cb) {
// Slowpath - called once/scope*export at construction
// Insert a exported function into scope table
int funcnum = VerilatedImp::exportInsert(namep);
if (!finalize) {
// Need two passes so we know array size to create
// Alternative is to dynamically stretch the array, which is more code, and slower.
if (funcnum >= m_funcnumMax) { m_funcnumMax = funcnum+1; }
} else {
if (funcnum >= m_funcnumMax) {
vl_fatal(__FILE__,__LINE__,"","Internal: Bad funcnum vs. pre-finalize maximum");
}
if (!m_callbacksp) { // First allocation
m_callbacksp = new void* [m_funcnumMax];
memset(m_callbacksp, 0, m_funcnumMax*sizeof(void*));
}
m_callbacksp[funcnum] = cb;
}
}
void* VerilatedScope::exportFindNullError(int funcnum) const {
// Slowpath - Called only when find has failed
string msg = (string("%Error: Testbench C called '")
+VerilatedImp::exportName(funcnum)
+"' but scope wasn't set, perhaps due to dpi import call without 'context'");
vl_fatal("unknown",0,"", msg.c_str());
return NULL;
}
void* VerilatedScope::exportFindError(int funcnum) const {
// Slowpath - Called only when find has failed
string msg = (string("%Error: Testbench C called '")
+VerilatedImp::exportName(funcnum)
+"' but this DPI export function exists only in other scopes, not scope '"
+name()+"'");
vl_fatal("unknown",0,"", msg.c_str());
return NULL;
}
void VerilatedScope::scopeDump() const {
VL_PRINTF(" SCOPE: %s\n", name());
for (int i=0; i<m_funcnumMax; i++) {
if (m_callbacksp && m_callbacksp[i]) {
VL_PRINTF(" DPI-EXPORT: %s\n", VerilatedImp::exportName(i));
}
}
}
//===========================================================================

View File

@ -52,7 +52,6 @@ typedef WData* WDataOutP; ///< Array output from a function
class SpTraceVcd;
class SpTraceVcdCFile;
class VerilatedScope;
//=========================================================================
/// Base class for all Verilated module classes
@ -124,18 +123,43 @@ public:
# define VL_PRINTF printf ///< Print ala printf; may redefine if desired
#endif
//===========================================================================
/// Verilator symbol table base class
class VerilatedSyms {
// VerilatedSyms base class exists just so symbol tables have a common pointer type
};
//===========================================================================
/// Verilator global static information class
class VerilatedScope {
const char* m_namep; // Scope name
public:
// Internals - called from VerilatedModule's
VerilatedScope() {}
// Fastpath:
VerilatedSyms* m_symsp; ///< Symbol table
void** m_callbacksp; ///< Callback table pointer (Fastpath)
int m_funcnumMax; ///< Maxium function number stored (Fastpath)
// 4 bytes padding (on -m64), for rent.
const char* m_namep; ///< Scope name (Slowpath)
public: // But internals only - called from VerilatedModule's
VerilatedScope();
~VerilatedScope();
void configure(const char* prefixp, const char* suffixp);
// Accessors
void configure(VerilatedSyms* symsp, const char* prefixp, const char* suffixp);
void exportInsert(bool finalize, const char* namep, void* cb);
// ACCESSORS
const char* name() const { return m_namep; }
inline VerilatedSyms* symsp() const { return m_symsp; }
void* exportFindError(int funcnum) const;
void* exportFindNullError(int funcnum) const;
void scopeDump() const;
inline void* exportFind(int funcnum) const {
if (VL_UNLIKELY(!this)) return exportFindNullError(funcnum);
if (VL_LIKELY(funcnum < m_funcnumMax)) {
// m_callbacksp must be declared, as Max'es are > 0
return m_callbacksp[funcnum];
} else {
return exportFindError(funcnum);
}
}
};
//===========================================================================
@ -208,6 +232,7 @@ public:
static bool dpiInContext() { return t_dpiScopep != NULL; }
static const char* dpiFilenamep() { return t_dpiFilename; }
static int dpiLineno() { return t_dpiLineno; }
static int exportFuncNum(const char* namep);
};
//=========================================================================

View File

@ -53,18 +53,22 @@ class VerilatedImp {
typedef vector<string> ArgVec;
typedef map<pair<const void*,void*>,void*> UserMap;
typedef map<const char*, const VerilatedScope*, VerilatedCStrCmp> ScopeNameMap;
typedef map<const char*, int> ExportNameMap;
// MEMBERS
static VerilatedImp s_s; ///< Static Singleton; One and only static this
ArgVec m_argVec; // Argument list
bool m_argVecLoaded; // Ever loaded argument list
ArgVec m_argVec; ///< Argument list
bool m_argVecLoaded; ///< Ever loaded argument list
UserMap m_userMap; ///< Map of <(scope,userkey), userData>
ScopeNameMap m_nameMap; ///< Map of <scope name, scope pointer>
ScopeNameMap m_nameMap; ///< Map of <scope_name, scope pointer>
// Slow - somewhat static:
ExportNameMap m_exportMap; ///< Map of <export_func_proto, func number>
int m_exportNext; ///< Next export funcnum
public: // But only for verilated*.cpp
// CONSTRUCTORS
VerilatedImp() : m_argVecLoaded(false) {}
VerilatedImp() : m_argVecLoaded(false), m_exportNext(0) {}
~VerilatedImp() {}
// METHODS - arguments
@ -137,15 +141,52 @@ public: // But only for verilated*.cpp
ScopeNameMap::iterator it=s_s.m_nameMap.find(scopep->name());
if (it != s_s.m_nameMap.end()) s_s.m_nameMap.erase(it);
}
static void scopesDump() {
VL_PRINTF("scopesDump:\n");
for (ScopeNameMap::iterator it=s_s.m_nameMap.begin();
it!=s_s.m_nameMap.end(); ++it) {
for (ScopeNameMap::iterator it=s_s.m_nameMap.begin(); it!=s_s.m_nameMap.end(); ++it) {
const VerilatedScope* scopep = it->second;
VL_PRINTF(" %s\n", scopep->name());
scopep->scopeDump();
}
VL_PRINTF("\n");
}
public: // But only for verilated*.cpp
// METHODS - export names
// Each function prototype is converted to a function number which we
// then use to index a 2D table also indexed by scope number, because we
// can't know at Verilation time what scopes will exist in other modules
// in the design that also happen to have our same callback function.
// Rather than a 2D map, the integer scheme saves 500ish ns on a likely
// miss at the cost of a multiply, and all lookups move to slowpath.
static int exportInsert(const char* namep) {
// Slow ok - called once/function at creation
ExportNameMap::iterator it=s_s.m_exportMap.find(namep);
if (it == s_s.m_exportMap.end()) {
s_s.m_exportMap.insert(it, make_pair(namep, s_s.m_exportNext++));
return s_s.m_exportNext++;
} else {
return it->second;
}
}
static int exportFind(const char* namep) {
ExportNameMap::iterator it=s_s.m_exportMap.find(namep);
if (it != s_s.m_exportMap.end()) return it->second;
string msg = (string("%Error: Testbench C called ")+namep
+" but no such DPI export function name exists in ANY model");
vl_fatal("unknown",0,"", msg.c_str());
return -1;
}
static const char* exportName(int funcnum) {
// Slowpath; find name for given export; errors only so no map to reverse-map it
for (ExportNameMap::iterator it=s_s.m_exportMap.begin(); it!=s_s.m_exportMap.end(); ++it) {
if (it->second == funcnum) return it->first;
}
return "*UNKNOWN*";
}
// We don't free up m_exportMap until the end, because we can't be sure
// what other models are using the assigned funcnum's.
};
#endif // Guard

View File

@ -1,4 +1,4 @@
export GLIBCPP_FORCE_NEW=1
compile with -DVL_LEAK_CHECKS
valgrind --tool=memcheck --leak-check=yes /home/wsnyder/src/verilator/v4/verilator/verilator_bin_dbg -MMD --bin /home/wsnyder/src/verilator/v4/verilator/verilator_bin_dbg --cc -f /home/wsnyder/src/verilator/v4/verilator/test_c/../test_v/input.vc top.v --no-skip-identical 2>&1 | tee ~/d/aa
valgrind --tool=memcheck --leak-check=yes /home/wsnyder/src/verilator/v4/verilator/verilator_bin_dbg -MMD --bin /home/wsnyder/src/verilator/v4/verilator/verilator_bin_dbg --cc /home/wsnyder/src/verilator/v4/verilator/test_regress/t/t_case_huge.v --no-skip-identical -I/home/wsnyder/src/verilator/v4/verilator/test_regress/t 2>&1 | tee ~/d/aa
valgrind --tool=memcheck --leak-check=yes $VERILATOR_ROOT/verilator_bin_dbg -MMD --bin $VERILATOR_ROOT/verilator_bin_dbg --cc -f $VERILATOR_ROOT/test_c/../test_v/input.vc top.v --no-skip-identical 2>&1 | tee ~/d/aa
valgrind --tool=memcheck --leak-check=yes $VERILATOR_ROOT/verilator_bin_dbg -MMD --bin $VERILATOR_ROOT/verilator_bin_dbg --cc $VERILATOR_ROOT/test_regress/t/t_case_huge.v --no-skip-identical -I$VERILATOR_ROOT/test_regress/t 2>&1 | tee ~/d/aa

View File

@ -1256,6 +1256,7 @@ private:
bool m_didSigning:1; // V3Signed completed; can skip iteration
bool m_attrIsolateAssign:1;// User isolate_assignments attribute
bool m_prototype:1; // Just a prototype
bool m_dpiExport:1; // DPI exported
bool m_dpiImport:1; // DPI imported
bool m_dpiContext:1; // DPI import context
bool m_dpiTask:1; // DPI import task (vs. void function)
@ -1265,7 +1266,8 @@ public:
: AstNode(fileline)
, m_name(name), m_taskPublic(false), m_didSigning(false)
, m_attrIsolateAssign(false), m_prototype(false)
, m_dpiImport(false), m_dpiContext(false), m_dpiTask(false), m_pure(false) {
, m_dpiExport(false), m_dpiImport(false), m_dpiContext(false)
, m_dpiTask(false), m_pure(false) {
addNOp3p(stmtsp);
cname(name); // Might be overridden by dpi import/export
}
@ -1284,6 +1286,9 @@ public:
// op3 = Statements/Ports/Vars
AstNode* stmtsp() const { return op3p()->castNode(); } // op3 = List of statements
void addStmtsp(AstNode* nodep) { addNOp3p(nodep); }
// op4 = scope name
AstScopeName* scopeNamep() const { return op4p()->castScopeName(); }
void scopeNamep(AstNode* nodep) { setNOp4p(nodep); }
void taskPublic(bool flag) { m_taskPublic=flag; }
bool taskPublic() const { return m_taskPublic; }
void didSigning(bool flag) { m_didSigning=flag; }
@ -1292,6 +1297,8 @@ public:
bool attrIsolateAssign() const { return m_attrIsolateAssign; }
void prototype(bool flag) { m_prototype = flag; }
bool prototype() const { return m_prototype; }
void dpiExport(bool flag) { m_dpiExport = flag; }
bool dpiExport() const { return m_dpiExport; }
void dpiImport(bool flag) { m_dpiImport = flag; }
bool dpiImport() const { return m_dpiImport; }
void dpiContext(bool flag) { m_dpiContext = flag; }

View File

@ -564,7 +564,8 @@ void AstNodeFTask::dump(ostream& str) {
if (taskPublic()) str<<" [PUBLIC]";
if (prototype()) str<<" [PROTOTYPE]";
if (dpiImport()) str<<" [DPII]";
if (dpiImport() && cname()!=name()) str<<" [c="<<cname()<<"]";
if (dpiExport()) str<<" [DPIX]";
if ((dpiImport() || dpiExport()) && cname()!=name()) str<<" [c="<<cname()<<"]";
}
void AstBegin::dump(ostream& str) {
this->AstNode::dump(str);
@ -619,4 +620,6 @@ void AstCFunc::dump(ostream& str) {
if (slow()) str<<" [SLOW]";
if (pure()) str<<" [PURE]";
if (dpiImport()) str<<" [DPII]";
if (dpiExport()) str<<" [DPIX]";
if (dpiExportWrapper()) str<<" [DPIXWR]";
}

View File

@ -2006,11 +2006,14 @@ struct AstScopeName : public AstNodeMath {
// For display %m and DPI context imports
// Parents: DISPLAY
// Children: TEXT
AstScopeName(FileLine* fl) : AstNodeMath(fl) {
private:
bool m_dpiExport; // Is for dpiExport
public:
AstScopeName(FileLine* fl) : AstNodeMath(fl), m_dpiExport(false) {
width(64,64); }
ASTNODE_NODE_FUNCS(ScopeName, SCOPENAME)
virtual V3Hash sameHash() const { return V3Hash(); }
virtual bool same(AstNode* samep) const { return true; }
virtual bool same(AstNode* samep) const { return m_dpiExport==samep->castScopeName()->m_dpiExport; }
virtual string emitVerilog() { return ""; }
virtual string emitC() { V3ERROR_NA; return ""; }
virtual bool cleanOut() { return true; }
@ -2018,6 +2021,8 @@ struct AstScopeName : public AstNodeMath {
void scopeAttrp(AstNode* nodep) { addOp1p(nodep); }
string scopeSymName() const; // Name for __Vscope variable including children
string scopePrettyName() const; // Name for __Vscope printing
bool dpiExport() const { return m_dpiExport; }
void dpiExport(bool flag) { m_dpiExport=flag; }
};
struct AstUdpTable : public AstNode {
@ -3080,6 +3085,7 @@ private:
AstCFuncType m_funcType;
AstScope* m_scopep;
string m_name;
string m_cname; // C name, for dpiExports
string m_rtnType; // void, bool, or other return type
string m_argTypes;
bool m_dontCombine:1; // V3Combine shouldn't compare this func tree, it's special
@ -3092,6 +3098,8 @@ private:
bool m_symProlog:1; // Setup symbol table for later instructions
bool m_entryPoint:1; // User may call into this top level function
bool m_pure:1; // Pure function
bool m_dpiExport:1; // From dpi export
bool m_dpiExportWrapper:1; // From dpi export; static function with dispatch table
bool m_dpiImport:1; // From dpi import
public:
AstCFunc(FileLine* fl, const string& name, AstScope* scopep, const string& rtnType="")
@ -3110,6 +3118,8 @@ public:
m_symProlog = false;
m_entryPoint = false;
m_pure = false;
m_dpiExport = false;
m_dpiExportWrapper = false;
m_dpiImport = false;
}
ASTNODE_NODE_FUNCS(CFunc, CFUNC)
@ -3121,10 +3131,13 @@ public:
virtual bool same(AstNode* samep) const { return ((funcType()==samep->castCFunc()->funcType())
&& (rtnTypeVoid()==samep->castCFunc()->rtnTypeVoid())
&& (argTypes()==samep->castCFunc()->argTypes())
&& (!dpiImport() || name()==samep->castCFunc()->name())); }
&& (!(dpiImport() || dpiExport())
|| name()==samep->castCFunc()->name())); }
//
virtual void name(const string& name) { m_name = name; }
virtual int instrCount() const { return dpiImport() ? instrCountDpi() : 0; }
void cname(const string& name) { m_cname = name; }
string cname() const { return m_cname; }
AstScope* scopep() const { return m_scopep; }
void scopep(AstScope* nodep) { m_scopep = nodep; }
string rtnTypeVoid() const { return ((m_rtnType=="") ? "void" : m_rtnType); }
@ -3152,6 +3165,10 @@ public:
void entryPoint(bool flag) { m_entryPoint = flag; }
bool pure() const { return m_pure; }
void pure(bool flag) { m_pure = flag; }
bool dpiExport() const { return m_dpiExport; }
void dpiExport(bool flag) { m_dpiExport = flag; }
bool dpiExportWrapper() const { return m_dpiExportWrapper; }
void dpiExportWrapper(bool flag) { m_dpiExportWrapper = flag; }
bool dpiImport() const { return m_dpiImport; }
void dpiImport(bool flag) { m_dpiImport = flag; }
//

View File

@ -208,7 +208,9 @@ public:
}
virtual void visit(AstScopeName* nodep, AstNUser*) {
// For use under AstCCalls for dpiImports. ScopeNames under displays are handled in AstDisplay
putbs("(&(vlSymsp->__Vscope_"+nodep->scopeSymName()+"))");
if (!nodep->dpiExport()) {
putbs("(&(vlSymsp->__Vscope_"+nodep->scopeSymName()+"))");
}
}
virtual void visit(AstSFormat* nodep, AstNUser*) {
displayNode(nodep, nodep->text(), nodep->exprsp(), false);

View File

@ -144,8 +144,10 @@ public:
if (AstVar* portp = stmtp->castVar()) {
if (portp->isIO() && !portp->isFuncReturn()) {
if (args != "") args+= ", ";
if (nodep->dpiImport()) args += portp->dpiArgType(true,false);
else if (nodep->funcPublic()) args += portp->cPubArgType(true,false);
if (nodep->dpiImport() || nodep->dpiExportWrapper())
args += portp->dpiArgType(true,false);
else if (nodep->funcPublic())
args += portp->cPubArgType(true,false);
else args += portp->vlArgType(true,false);
}
}

View File

@ -45,6 +45,11 @@ class EmitCSyms : EmitCBaseVisitor {
AstUser1InUse m_inuser1;
// TYPES
struct ScopeFuncData { AstScopeName* m_scopep; AstCFunc* m_funcp; AstNodeModule* m_modp;
ScopeFuncData(AstScopeName* scopep, AstCFunc* funcp, AstNodeModule* modp)
: m_scopep(scopep), m_funcp(funcp), m_modp(modp) {}
};
typedef map<string,ScopeFuncData> ScopeFuncs;
typedef map<string,AstScopeName*> ScopeNames;
typedef pair<AstScope*,AstNodeModule*> ScopeModPair;
struct CmpName {
@ -52,12 +57,22 @@ class EmitCSyms : EmitCBaseVisitor {
return lhsp.first->name() < rhsp.first->name();
}
};
struct CmpDpi {
inline bool operator () (const AstCFunc* lhsp, const AstCFunc* rhsp) const {
if (lhsp->dpiImport() != rhsp->dpiImport()) {
return lhsp->dpiImport() < rhsp->dpiImport();
}
return lhsp->name() < rhsp->name();
}
};
// STATE
AstCFunc* m_funcp; // Current function
AstNodeModule* m_modp; // Current module
vector<ScopeModPair> m_scopes; // Every scope by module
vector<AstCFunc*> m_dpis; // DPI functions
ScopeNames m_scopeNames; // Each unique AstScopeName
ScopeFuncs m_scopeFuncs; // Each {scope,dpiexportfunc}
V3LanguageWords m_words; // Reserved word detector
int m_coverBins; // Coverage bin number
@ -84,14 +99,16 @@ class EmitCSyms : EmitCBaseVisitor {
// Collect list of scopes
nodep->iterateChildren(*this);
// Sort m_scopes by scope name
// Sort by names, so line/process order matters less
sort(m_scopes.begin(), m_scopes.end(), CmpName());
sort(m_dpis.begin(), m_dpis.end(), CmpDpi());
// Output
emitSymHdr();
emitSymImp();
if (v3Global.dpi()) {
emitDpiHdr();
emitDpiImp();
}
}
virtual void visit(AstNodeModule* nodep, AstNUser*) {
@ -109,6 +126,11 @@ class EmitCSyms : EmitCBaseVisitor {
if (m_scopeNames.find(name) == m_scopeNames.end()) {
m_scopeNames.insert(make_pair(name, nodep));
}
if (nodep->dpiExport()) {
if (!m_funcp) nodep->v3fatalSrc("ScopeName not under DPI function");
m_scopeFuncs.insert(make_pair(name + " " + m_funcp->name(),
ScopeFuncData(nodep, m_funcp, m_modp)));
}
}
virtual void visit(AstCoverDecl* nodep, AstNUser*) {
// Assign numbers to all bins, so we know how big of an array to use
@ -117,10 +139,12 @@ class EmitCSyms : EmitCBaseVisitor {
}
}
virtual void visit(AstCFunc* nodep, AstNUser*) {
nodep->iterateChildren(*this);
if (nodep->dpiImport()) {
if (nodep->dpiImport() || nodep->dpiExportWrapper()) {
m_dpis.push_back(nodep);
}
m_funcp = nodep;
nodep->iterateChildren(*this);
m_funcp = NULL;
}
// NOPs
virtual void visit(AstConst*, AstNUser*) {}
@ -133,6 +157,7 @@ class EmitCSyms : EmitCBaseVisitor {
// ACCESSORS
public:
EmitCSyms(AstNetlist* nodep) {
m_funcp = NULL;
m_modp = NULL;
m_coverBins = 0;
nodep->accept(*this);
@ -162,11 +187,24 @@ void EmitCSyms::emitSymHdr() {
puts("#include \""+modClassName(nodep)+".h\"\n");
}
puts("\n// SYMS CLASS\n");
puts((string)"class "+symClassName()+" {\n");
ofp()->putsPrivate(false); // public:
if (v3Global.dpi()) {
puts ("\n// DPI TYPES for DPI Export callbacks (Internal use)\n");
map<string,int> types; // Remove duplicates and sort
for (ScopeFuncs::iterator it = m_scopeFuncs.begin(); it != m_scopeFuncs.end(); ++it) {
AstCFunc* funcp = it->second.m_funcp;
if (funcp->dpiExport()) {
string cbtype = v3Global.opt.prefix()+"__Vcb_"+funcp->cname()+"_t";
types["typedef void (*"+cbtype+") ("+cFuncArgs(funcp)+");\n"] = 1;
}
}
for (map<string,int>::iterator it = types.begin(); it != types.end(); ++it) {
puts(it->first);
}
}
//puts("\n// STATIC STATE\n");
puts("\n// SYMS CLASS\n");
puts((string)"class "+symClassName()+" : public VerilatedSyms {\n");
ofp()->putsPrivate(false); // public:
puts("\n// LOCAL STATE\n");
ofp()->putAlign(V3OutFile::AL_AUTO, sizeof(vluint64_t));
@ -287,11 +325,31 @@ void EmitCSyms::emitSymImp() {
puts("// Setup scope names\n");
for (ScopeNames::iterator it = m_scopeNames.begin(); it != m_scopeNames.end(); ++it) {
puts("__Vscope_"+it->second->scopeSymName()+".configure(name(),");
puts("__Vscope_"+it->second->scopeSymName()+".configure(this,name(),");
putsQuoted(it->second->scopePrettyName());
puts(");\n");
}
if (v3Global.dpi()) {
puts("// Setup export functions\n");
puts("for (int __Vfinal=0; __Vfinal<2; __Vfinal++) {\n");
for (ScopeFuncs::iterator it = m_scopeFuncs.begin(); it != m_scopeFuncs.end(); ++it) {
AstScopeName* scopep = it->second.m_scopep;
AstCFunc* funcp = it->second.m_funcp;
AstNodeModule* modp = it->second.m_modp;
if (funcp->dpiExport()) {
puts("__Vscope_"+scopep->scopeSymName()+".exportInsert(__Vfinal,");
putsQuoted(funcp->cname());
puts(", (void*)(&");
puts(modClassName(modp));
puts("::");
puts(funcp->name());
puts("));\n");
}
}
puts("}\n");
}
puts("}\n");
puts("\n");
}
@ -318,10 +376,18 @@ void EmitCSyms::emitDpiHdr() {
puts("#endif\n");
puts("\n");
bool firstExp = false;
bool firstImp = false;
for (vector<AstCFunc*>::iterator it = m_dpis.begin(); it != m_dpis.end(); ++it) {
AstCFunc* nodep = *it;
if (nodep->dpiImport()) {
puts("// dpi import at "+nodep->fileline()->ascii()+"\n");
if (nodep->dpiExportWrapper()) {
if (!firstExp++) puts("\n// DPI EXPORTS\n");
puts("// DPI Export at "+nodep->fileline()->ascii()+"\n");
puts("extern "+nodep->rtnTypeVoid()+" "+nodep->name()+" ("+cFuncArgs(nodep)+");\n");
}
else if (nodep->dpiImport()) {
if (!firstImp++) puts("\n// DPI IMPORTS\n");
puts("// DPI Import at "+nodep->fileline()->ascii()+"\n");
puts("extern "+nodep->rtnTypeVoid()+" "+nodep->name()+" ("+cFuncArgs(nodep)+");\n");
}
}
@ -332,6 +398,59 @@ void EmitCSyms::emitDpiHdr() {
puts("#endif\n");
}
//######################################################################
void EmitCSyms::emitDpiImp() {
UINFO(6,__FUNCTION__<<": "<<endl);
string filename = v3Global.opt.makeDir()+"/"+topClassName()+"__Dpi.cpp";
AstCFile* cfilep = newCFile(filename, false/*slow*/, true/*source*/);
cfilep->support(true);
V3OutCFile hf (filename);
m_ofp = &hf;
m_ofp->putsHeader();
puts("// DESCR" "IPTION: Verilator output: Implementation of DPI export functions.\n");
puts("//\n");
puts("// Verilator compiles this file in when DPI functions are used.\n");
puts("// If you have multiple Verilated designs with the same DPI exported\n");
puts("// function names, you will get multiple definition link errors from here.\n");
puts("// This is an unfortunate result of the DPI specification.\n");
puts("// To solve this, either\n");
puts("// 1. Call "+topClassName()+"::{export_function} instead,\n");
puts("// and do not even bother to compile this file\n");
puts("// or 2. Compile all __Dpi.cpp files in the same compiler run,\n");
puts("// and #ifdefs already inserted here will sort everything out.\n");
puts("\n");
puts("#include \""+topClassName()+"__Dpi.h\"\n");
puts("#include \""+topClassName()+".h\"\n");
puts("\n");
for (vector<AstCFunc*>::iterator it = m_dpis.begin(); it != m_dpis.end(); ++it) {
AstCFunc* nodep = *it;
if (nodep->dpiExportWrapper()) {
puts("#ifndef _VL_DPIDECL_"+nodep->name()+"\n");
puts("#define _VL_DPIDECL_"+nodep->name()+"\n");
puts(nodep->rtnTypeVoid()+" "+nodep->name()+" ("+cFuncArgs(nodep)+") {\n");
puts("// DPI Export at "+nodep->fileline()->ascii()+"\n");
puts("return "+topClassName()+"::"+nodep->name()+"(");
string args;
for (AstNode* stmtp = nodep->argsp(); stmtp; stmtp=stmtp->nextp()) {
if (AstVar* portp = stmtp->castVar()) {
if (portp->isIO() && !portp->isFuncReturn()) {
if (args != "") args+= ", ";
args += portp->name();
}
}
}
puts(args+");\n");
puts("}\n");
puts("#endif\n");
puts("\n");
}
}
}
//######################################################################
// EmitC class functions

View File

@ -409,6 +409,22 @@ private:
}
nodep->iterateChildren(*this);
}
virtual void visit(AstDpiExport* nodep, AstNUser*) {
// AstDpiExport: Make sure the function referenced exists, then dump it
nodep->iterateChildren(*this);
if (m_idState==ID_RESOLVE) {
AstNodeFTask* taskp;
taskp = m_curVarsp->findIdUpward(nodep->name())->castNodeFTask();
if (!taskp) { nodep->v3error("Can't find definition of exported task/function: "<<nodep->prettyName()); }
else if (taskp->dpiExport()) {
nodep->v3error("Function was already DPI Exported, duplicate not allowed: "<<nodep->prettyName());
} else {
taskp->dpiExport(true);
if (nodep->cname()!="") taskp->cname(nodep->cname());
}
nodep->unlinkFrBack()->deleteTree();
}
}
virtual void visit(AstTypedef* nodep, AstNUser*) {
// Remember its name for later resolution

View File

@ -108,10 +108,13 @@ private:
m_ftaskp = nodep;
nodep->iterateChildren(*this);
m_ftaskp = NULL;
if (nodep->dpiExport()) {
nodep->scopeNamep(new AstScopeName(nodep->fileline()));
}
}
virtual void visit(AstNodeFTaskRef* nodep, AstNUser*) {
nodep->iterateChildren(*this);
if (nodep->taskp() && nodep->taskp()->dpiContext()) {
if (nodep->taskp() && (nodep->taskp()->dpiContext() || nodep->taskp()->dpiExport())) {
nodep->scopeNamep(new AstScopeName(nodep->fileline()));
}
}

View File

@ -324,6 +324,15 @@ private:
return level;
}
AstVarScope* createFuncVar(AstCFunc* funcp, const string& name, AstVar* examplep) {
AstVar* newvarp = new AstVar (funcp->fileline(), AstVarType::BLOCKTEMP, name,
examplep);
newvarp->funcLocal(true);
funcp->addInitsp(newvarp);
AstVarScope* newvscp = new AstVarScope(funcp->fileline(), m_scopep, newvarp);
m_scopep->addVarp(newvscp);
return newvscp;
}
AstVarScope* createInputVar(AstCFunc* funcp, const string& name, AstBasicDTypeKwd kwd) {
AstVar* newvarp = new AstVar (funcp->fileline(), AstVarType::BLOCKTEMP, name,
new AstBasicDType(funcp->fileline(), kwd));
@ -514,9 +523,6 @@ private:
ccallp->addArgsp(new AstConst(refp->fileline(), refp->fileline()->lineno()));
}
if (outvscp) {
ccallp->addArgsp(new AstVarRef(refp->fileline(), outvscp, true));
}
// Create connections
AstNode* nextpinp;
for (AstNode* pinp = refp->pinsp(); pinp; pinp=nextpinp) {
@ -525,11 +531,191 @@ private:
pinp->unlinkFrBack();
ccallp->addArgsp(pinp);
}
if (outvscp) {
ccallp->addArgsp(new AstVarRef(refp->fileline(), outvscp, true));
}
if (debug()>=9) { beginp->dumpTree(cout,"-nitask: "); }
return beginp;
}
AstCFunc* makeDpiCFunc(AstNodeFTask* nodep, AstVar* rtnvarp) {
string dpiprotoName(AstNodeFTask* nodep, AstVar* rtnvarp) {
// Return fancy export-ish name for DPI function
// Variable names are NOT included so differences in only IO names won't matter
string dpiproto;
if (nodep->pure()) dpiproto += "pure ";
if (nodep->dpiContext()) dpiproto += "context ";
dpiproto += rtnvarp ? rtnvarp->dpiArgType(true,true):"void";
dpiproto += " "+nodep->cname()+" (";
string args;
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp=stmtp->nextp()) {
if (AstVar* portp = stmtp->castVar()) {
if (portp->isIO() && !portp->isFuncReturn() && portp!=rtnvarp) {
if (args != "") { args+= ", "; dpiproto+= ", "; }
args += portp->name(); // Leftover so ,'s look nice
if (nodep->dpiImport()) dpiproto += portp->dpiArgType(false,false);
}
}
}
dpiproto += ")";
return dpiproto;
}
AstNode* createDpiTemp(AstVar* portp, const string& suffix) {
bool bitvec = (portp->basicp()->isBitLogic() && portp->width() > 32);
string stmt;
if (bitvec) {
stmt += "svBitVecVal "+portp->name()+suffix;
stmt += " ["+cvtToStr(portp->widthWords())+"]";
} else {
stmt += portp->dpiArgType(true,true);
stmt += " "+portp->name()+suffix;
}
stmt += ";\n";
return new AstCStmt(portp->fileline(), stmt);
}
AstNode* createAssignInternalToDpi(AstVar* portp, bool isPtr, const string& frSuffix, const string& toSuffix) {
// Create assignment from internal format into DPI temporary
bool bitvec = (portp->basicp()->isBitLogic() && portp->width() > 32);
string stmt;
// Someday we'll have better type support, and this can make variables and casts.
// But for now, we'll just text-bash it.
if (bitvec) {
// We only support quads, so don't need to sweat longer stuff
stmt += "VL_SET_WQ("+portp->name()+toSuffix+", "+portp->name()+frSuffix+")";
} else {
if (isPtr) stmt += "*"; // DPI outputs are pointers
stmt += portp->name()+toSuffix+" = ";
if (portp->basicp() && portp->basicp()->keyword()==AstBasicDTypeKwd::CHANDLE) {
stmt += "(void*)";
}
stmt += portp->name()+frSuffix;
}
stmt += ";\n";
return new AstCStmt(portp->fileline(), stmt);
}
AstNode* createAssignDpiToInternal(AstVarScope* portvscp, const string& frName) {
// Create assignment from DPI temporary into internal format
AstVar* portp = portvscp->varp();
string stmt;
if (portp->basicp() && portp->basicp()->keyword()==AstBasicDTypeKwd::CHANDLE) {
stmt += "(QData)";
}
stmt += frName;
// Use a AstCMath, as we want V3Clean to mask off bits that don't make sense.
int cwidth = VL_WORDSIZE; if (portp->basicp()) cwidth = portp->basicp()->keyword().width();
if (portp->basicp() && portp->basicp()->isBitLogic()) cwidth = VL_WORDSIZE*portp->widthWords();
AstNode* newp = new AstAssign(portp->fileline(),
new AstVarRef(portp->fileline(), portvscp, true),
new AstSel(portp->fileline(),
new AstCMath(portp->fileline(), stmt, cwidth, false),
0, portp->width()));
return newp;
}
AstCFunc* makeDpiExportWrapper(AstNodeFTask* nodep, AstVar* rtnvarp) {
string dpiproto = dpiprotoName(nodep,rtnvarp);
AstCFunc* dpip = new AstCFunc(nodep->fileline(),
nodep->cname(),
m_scopep,
(rtnvarp ? rtnvarp->dpiArgType(true,true) : ""));
dpip->dontCombine(true);
dpip->entryPoint(true);
dpip->isStatic(true);
dpip->dpiExportWrapper(true);
dpip->cname(nodep->cname());
// Add DPI reference to top, since it's a global function
m_topScopep->scopep()->addActivep(dpip);
{// Create dispatch wrapper
// Note this function may dispatch to myfunc on a different class.
// Thus we need to be careful not to assume a particular function layout.
//
// Func numbers must be the same for each function, even when there are
// completely different models with the same function name.
// Thus we can't just use a constant computed at Verilation time.
// We could use 64-bits of a MD5/SHA hash rather than a string here,
// but the compare is only done on first call then memoized, so it's not worth optimizing.
string stmt;
stmt += "static int __Vfuncnum = -1;\n";
// First time init (faster than what the compiler does if we did a singleton
stmt += "if (VL_UNLIKELY(__Vfuncnum==-1)) { __Vfuncnum = Verilated::exportFuncNum(\""+nodep->cname()+"\"); }\n";
// If the find fails, it will throw an error
stmt += "const VerilatedScope* __Vscopep = Verilated::dpiScope();\n";
// If dpiScope is fails and is null; the exportFind function throws and error
string cbtype = v3Global.opt.prefix()+"__Vcb_"+nodep->cname()+"_t";
stmt += cbtype+" __Vcb = ("+cbtype+")__Vscopep->exportFind(__Vfuncnum);\n";
// If __Vcb is null the exportFind function throws and error
dpip->addStmtsp(new AstCStmt(nodep->fileline(), stmt));
}
// Convert input/inout DPI arguments to Internal types
string args;
args += "("+v3Global.opt.prefix()+"__Syms*)(__Vscopep->symsp())"; // Upcast w/o overhead
AstNode* argnodesp = NULL;
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp=stmtp->nextp()) {
if (AstVar* portp = stmtp->castVar()) {
if (portp->isIO() && !portp->isFuncReturn() && portp != rtnvarp) {
// No createDpiTemp; we make a real internal variable instead
// SAME CODE BELOW
args+= ", ";
if (args != "") { argnodesp = argnodesp->addNext(new AstText(portp->fileline(), args, true)); args=""; }
AstVarScope* outvscp = createFuncVar (dpip, portp->name()+"__Vcvt", portp);
AstVarRef* refp = new AstVarRef(portp->fileline(), outvscp, portp->isOutput());
argnodesp = argnodesp->addNextNull(refp);
if (portp->isInput()) {
dpip->addStmtsp(createAssignDpiToInternal(outvscp, portp->name()));
}
}
}
}
if (rtnvarp) {
AstVar* portp = rtnvarp;
// SAME CODE ABOVE
args+= ", ";
if (args != "") { argnodesp = argnodesp->addNext(new AstText(portp->fileline(), args, true)); args=""; }
AstVarScope* outvscp = createFuncVar (dpip, portp->name()+"__Vcvt", portp);
AstVarRef* refp = new AstVarRef(portp->fileline(), outvscp, portp->isOutput());
argnodesp = argnodesp->addNextNull(refp);
}
{// Call the user function
// Add the variables referenced as VarRef's so that lifetime analysis
// doesn't rip up the variables on us
string stmt;
stmt += "(*__Vcb)(";
args += ");\n";
AstCStmt* newp = new AstCStmt(nodep->fileline(), stmt);
newp->addBodysp(argnodesp); argnodesp=NULL;
newp->addBodysp(new AstText(nodep->fileline(), args, true));
dpip->addStmtsp(newp);
}
// Convert output/inout arguments back to internal type
for (AstNode* stmtp = nodep->stmtsp(); stmtp; stmtp=stmtp->nextp()) {
if (AstVar* portp = stmtp->castVar()) {
if (portp->isIO() && portp->isOutput() && !portp->isFuncReturn()) {
dpip->addStmtsp(createAssignInternalToDpi(portp,true,"__Vcvt",""));
}
}
}
if (rtnvarp) {
dpip->addStmtsp(createDpiTemp(rtnvarp,""));
dpip->addStmtsp(createAssignInternalToDpi(rtnvarp,false,"__Vcvt",""));
string stmt = "return "+rtnvarp->name()+";\n";
dpip->addStmtsp(new AstCStmt(nodep->fileline(), stmt));
}
return dpip;
}
AstCFunc* makeDpiImportWrapper(AstNodeFTask* nodep, AstVar* rtnvarp) {
if (nodep->cname() != AstNode::prettyName(nodep->cname())) {
nodep->v3error("DPI function has illegal characters in C identifier name: "<<AstNode::prettyName(nodep->cname()));
}
@ -551,7 +737,7 @@ private:
return dpip;
}
void bodyDpiCFunc(AstNodeFTask* nodep, AstVarScope* rtnvscp, AstCFunc* cfuncp) {
void bodyDpiImportFunc(AstNodeFTask* nodep, AstVarScope* rtnvscp, AstCFunc* cfuncp) {
// Convert input/inout arguments to DPI types
string args;
for (AstNode* stmtp = cfuncp->argsp(); stmtp; stmtp=stmtp->nextp()) {
@ -568,32 +754,12 @@ private:
else if (portp->isOutput()) args += "&";
else if (portp->basicp() && portp->basicp()->isBitLogic() && portp->widthMin() != 1) args += "&"; // it's a svBitVecVal
args += "__Vcvt_"+portp->name();
args += portp->name()+"__Vcvt";
string stmt;
if (bitvec) {
stmt += "svBitVecVal __Vcvt_"+portp->name();
stmt += " ["+cvtToStr(portp->widthWords())+"]";
} else {
stmt += portp->dpiArgType(true,true);
stmt += " __Vcvt_"+portp->name();
}
cfuncp->addStmtsp(createDpiTemp(portp,"__Vcvt"));
if (portp->isInput()) {
// Someday we'll have better type support, and this can make variables and casts.
// But for now, we'll just text-bash it.
if (bitvec) {
// We only support quads, so don't need to sweat longer stuff
stmt += "; VL_SET_WQ(__Vcvt_"+portp->name()+", "+portp->name()+")";
} else {
stmt += " = ";
if (portp->basicp() && portp->basicp()->keyword()==AstBasicDTypeKwd::CHANDLE) {
stmt += "(void*)";
}
stmt += portp->name();
}
cfuncp->addStmtsp(createAssignInternalToDpi(portp,false,"","__Vcvt"));
}
stmt += ";\n";
cfuncp->addStmtsp(new AstCStmt(portp->fileline(), stmt));
}
}
}
@ -607,7 +773,7 @@ private:
{// Call the user function
string stmt;
if (rtnvscp) { // isFunction will no longer work as we unlinked the return var
stmt += rtnvscp->varp()->dpiArgType(true,true) + " __Vcvt_"+rtnvscp->varp()->name() + " = ";
stmt += rtnvscp->varp()->dpiArgType(true,true) + " "+rtnvscp->varp()->name()+"__Vcvt = ";
}
stmt += nodep->cname()+"("+args+");\n";
cfuncp->addStmtsp(new AstCStmt(nodep->fileline(), stmt));
@ -616,23 +782,9 @@ private:
// Convert output/inout arguments back to internal type
for (AstNode* stmtp = cfuncp->argsp(); stmtp; stmtp=stmtp->nextp()) {
if (AstVar* portp = stmtp->castVar()) {
if (portp->isIO()) {
if (portp->isIO() && (portp->isOutput() || portp->isFuncReturn())) {
AstVarScope* portvscp = portp->user2p()->castNode()->castVarScope(); // Remembered when we created it earlier
if (portp->isOutput() || portp->isFuncReturn()) {
string stmt;
if (portp->basicp() && portp->basicp()->keyword()==AstBasicDTypeKwd::CHANDLE) {
stmt += "(QData)";
}
stmt += "__Vcvt_"+portp->name();
// Use a AstCMath, as we want V3Clean to mask off bits that don't make sense.
int cwidth = VL_WORDSIZE; if (portp->basicp()) cwidth = portp->basicp()->keyword().width();
if (portp->basicp() && portp->basicp()->isBitLogic()) cwidth = VL_WORDSIZE*portp->widthWords();
cfuncp->addStmtsp(new AstAssign(portp->fileline(),
new AstVarRef(portp->fileline(), portvscp, true),
new AstSel(portp->fileline(),
new AstCMath(portp->fileline(), stmt, cwidth, false),
0, portp->width())));
}
cfuncp->addStmtsp(createAssignDpiToInternal(portvscp,portp->name()+"__Vcvt"));
}
}
}
@ -650,7 +802,7 @@ private:
if (NULL!=(portp = nodep->fvarp()->castVar())) {
if (!portp->isFuncReturn()) nodep->v3error("Not marked as function return var");
if (portp->isWide()) nodep->v3error("Unsupported: Public functions with return > 64 bits wide. (Make it a output instead.)");
if (ftaskNoInline) portp->funcReturn(false); // Converting return to 'outputs'
if (ftaskNoInline || nodep->dpiExport()) portp->funcReturn(false); // Converting return to 'outputs'
portp->unlinkFrBack();
rtnvarp = portp;
rtnvarp->funcLocal(true);
@ -664,21 +816,26 @@ private:
}
string prefix = "";
if (nodep->dpiImport()) prefix = "__Vdpiimwrap_";
else if (nodep->dpiExport()) prefix = "__Vdpiexp_";
else if (ftaskNoInline) prefix = "__VnoInFunc_";
// Unless public, v3Descope will not uniquify function names even if duplicate per-scope.
// Unless public, v3Descope will not uniquify function names even if duplicate per-scope,
// so make it unique now.
string suffix = ""; // So, make them unique
if (!nodep->taskPublic()) suffix = "_"+m_scopep->nameDotless();
AstCFunc* cfuncp = new AstCFunc(nodep->fileline(),
prefix + nodep->name() + suffix,
m_scopep,
((nodep->taskPublic() && rtnvarp)?rtnvarp->cPubArgType(true,true):""));
((nodep->taskPublic() && rtnvarp) ? rtnvarp->cPubArgType(true,true)
: ""));
// It's ok to combine imports because this is just a wrapper; duplicate wrappers can get merged.
cfuncp->dontCombine(!nodep->dpiImport());
cfuncp->entryPoint (!nodep->dpiImport());
cfuncp->funcPublic (nodep->taskPublic());
cfuncp->dpiExport (nodep->dpiExport());
cfuncp->isStatic (!(nodep->dpiImport()||nodep->taskPublic()));
cfuncp->pure (nodep->pure());
//cfuncp->dpiImport // Not set in the wrapper - the called function has it set
if (cfuncp->dpiExport()) cfuncp->cname (nodep->cname());
bool needSyms = !nodep->dpiImport();
if (needSyms) {
@ -699,31 +856,44 @@ private:
createInputVar (cfuncp, "__Vlineno", AstBasicDTypeKwd::INT);
}
// Fake output variable if was a function
if (rtnvarp) cfuncp->addArgsp(rtnvarp);
if (!nodep->dpiImport()) {
cfuncp->addInitsp(new AstCStmt(nodep->fileline(), EmitCBaseVisitor::symTopAssign()+"\n"));
}
if (nodep->dpiExport()) {
AstScopeName* snp = nodep->scopeNamep(); if (!snp) nodep->v3fatalSrc("Missing scoping context");
snp->dpiExport(true); // The AstScopeName is really a statement(ish) for tracking, not a function
snp->unlinkFrBack();
cfuncp->addInitsp(snp);
}
AstCFunc* dpip = NULL;
string dpiproto;
if (nodep->dpiImport()) {
if (nodep->pure()) dpiproto += "pure ";
if (nodep->dpiContext()) dpiproto += "context ";
dpiproto += rtnvarp ? rtnvarp->dpiArgType(true,true):"void";
dpiproto += " "+nodep->cname()+" (";
if (nodep->dpiImport() || nodep->dpiExport()) {
dpiproto = dpiprotoName(nodep, rtnvarp);
// Only create one DPI extern for each specified cname,
// as it's legal for the user to attach multiple tasks to one dpi cname
if (m_dpiNames.find(nodep->cname()) == m_dpiNames.end()) {
DpiNames::iterator iter = m_dpiNames.find(nodep->cname());
if (iter == m_dpiNames.end()) {
// m_dpiNames insert below
dpip = makeDpiCFunc(nodep, rtnvarp);
if (nodep->dpiImport()) {
dpip = makeDpiImportWrapper(nodep, rtnvarp);
} else if (nodep->dpiExport()) {
dpip = makeDpiExportWrapper(nodep, rtnvarp);
cfuncp->addInitsp(new AstComment(dpip->fileline(), (string)("Function called from: ")+dpip->cname()));
}
m_dpiNames.insert(make_pair(nodep->cname(), make_pair(dpip, dpiproto)));
}
else if (iter->second.second != dpiproto) {
nodep->v3error("Duplicate declaration of DPI function with different formal arguments: "<<nodep->prettyName());
nodep->v3error("... New prototype: "<<dpiproto);
iter->second.first->v3error("... Original prototype: "<<iter->second.second);
}
}
// Create list of arguments and move to function
string args;
for (AstNode* nextp, *stmtp = nodep->stmtsp(); stmtp; stmtp=nextp) {
nextp = stmtp->nextp();
if (AstVar* portp = stmtp->castVar()) {
@ -739,11 +909,6 @@ private:
portp->v3error("... For best portability, use bit, byte, int, or longint");
}
}
if (!portp->isFuncReturn()) {
if (args != "") { args+= ", "; dpiproto+= ", "; }
args += portp->name(); // Leftover so ,'s look nice
if (nodep->dpiImport()) dpiproto += portp->dpiArgType(false,false);
}
} else {
// "Normal" variable, mark inside function
portp->funcLocal(true);
@ -753,26 +918,18 @@ private:
portp->user2p(newvscp);
}
}
dpiproto += ")";
if (nodep->dpiImport()) {
// Only create one DPI extern for each specified cname,
// as it's legal for the user to attach multiple tasks to one dpi cname
DpiNames::iterator iter = m_dpiNames.find(nodep->cname());
if (iter == m_dpiNames.end()) {
m_dpiNames.insert(make_pair(nodep->cname(), make_pair(dpip, dpiproto)));
} else if (iter->second.second != dpiproto) {
nodep->v3error("Duplicate declaration of DPI function with different formal arguments: "<<nodep->prettyName());
nodep->v3error("... New prototype: "<<dpiproto);
iter->second.first->v3error("... Original prototype: "<<iter->second.second);
}
}
// Fake output variable if was a function. It's more efficient to
// have it last, rather than first, as the C compiler can sometimes
// avoid copying variables when calling shells if argument 1
// remains argument 1 (which it wouldn't if a return got added).
if (rtnvarp) cfuncp->addArgsp(rtnvarp);
// Move body
AstNode* bodysp = nodep->stmtsp();
if (bodysp) { bodysp->unlinkFrBackWithNext(); cfuncp->addStmtsp(bodysp); }
if (nodep->dpiImport()) {
bodyDpiCFunc(nodep, rtnvscp, cfuncp);
bodyDpiImportFunc(nodep, rtnvscp, cfuncp);
}
// Return statement
@ -904,7 +1061,15 @@ private:
if (!nodep->user1()) { // Just one creation needed per function
// Expand functions in it
nodep->user1(true);
if (nodep->dpiImport() || nodep->taskPublic() || m_statep->ftaskNoInline(nodep)) {
int modes = 0;
if (nodep->dpiImport()) modes++;
if (nodep->dpiExport()) modes++;
if (nodep->taskPublic()) modes++;
if (modes > 1) nodep->v3error("Cannot mix DPI import, DPI export and/or public on same function: "<<nodep->prettyName());
if (nodep->dpiImport() || nodep->dpiExport()
|| nodep->taskPublic() || m_statep->ftaskNoInline(nodep)) {
// Clone it first, because we may have later FTaskRef's that still need
// the original version.
if (m_statep->ftaskNoInline(nodep)) m_statep->checkPurity(nodep);

View File

@ -622,7 +622,8 @@ private:
}
V3GraphVertex* funcVtxp = getCFuncVertexp(nodep);
if (!m_finding) { // If public, we need a unique activity code to allow for sets directly in this func
if (nodep->funcPublic() || nodep->name() == "_eval") {
if (nodep->funcPublic() || nodep->dpiExport()
|| nodep->name() == "_eval") {
// Need a non-null place to remember to later add a statement; make one
if (!nodep->stmtsp()) nodep->addStmtsp(new AstComment(nodep->fileline(), "Tracing activity check"));
V3GraphVertex* activityVtxp = getActivityVertexp(nodep->stmtsp(), nodep->slow());

View File

@ -0,0 +1,19 @@
#!/usr/bin/perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2003 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.
compile (
v_flags2 => ["--lint-only"],
fails=>$Self->{v3},
expect=>
'%Error: t/t_dpi_2exp_bad.v:11: Function was already DPI Exported, duplicate not allowed: dpix_twice
%Error: Exiting due to .*'
);
ok(1);
1;

View File

@ -0,0 +1,17 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Copyright 2009 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.
module t;
export "DPI-C" task dpix_twice;
export "DPI-C" dpix_t_int_renamed = task dpix_twice;
task dpix_twice(input int i, output int o); o = ~i; endtask
initial begin
$stop;
end
endmodule

22
test_regress/t/t_dpi_export.pl Executable file
View File

@ -0,0 +1,22 @@
#!/usr/bin/perl
if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; }
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# Copyright 2003 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.
# irun -sv top.v t_dpi_export.v -cpost t_dpi_export_c.c -end
compile (
# Amazingly VCS, NC and Verilator all just accept the C file here!
v_flags2 => ["t/t_dpi_export_c.cpp"],
);
execute (
check_finished=>1,
);
ok(1);
1;

View File

@ -0,0 +1,60 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// Copyright 2009 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.
module t;
sub a (.inst(1));
sub b (.inst(2));
// Returns integer line number, or -1 for all ok
import "DPI-C" context function int dpix_run_tests();
export "DPI-C" task dpix_t_int;
task dpix_t_int(input int i, output int o); o = ~i; endtask
export "DPI-C" dpix_t_renamed = task dpix_t_ren;
task dpix_t_ren(input int i, output int o); o = i+2; endtask
export "DPI-C" function dpix_int123;
function int dpix_int123(); dpix_int123 = 32'h123; endfunction
export "DPI-C" function dpix_f_bit;
export "DPI-C" function dpix_f_int;
export "DPI-C" function dpix_f_byte;
export "DPI-C" function dpix_f_shortint;
export "DPI-C" function dpix_f_longint;
export "DPI-C" function dpix_f_chandle;
function bit dpix_f_bit (bit i); dpix_f_bit = ~i; endfunction
function int dpix_f_int (int i); dpix_f_int = ~i; endfunction
function byte dpix_f_byte (byte i); dpix_f_byte = ~i; endfunction
function shortint dpix_f_shortint(shortint i); dpix_f_shortint = ~i; endfunction
function longint dpix_f_longint (longint i); dpix_f_longint = ~i; endfunction
function chandle dpix_f_chandle (chandle i); dpix_f_chandle = i; endfunction
int lineno;
initial begin
lineno = dpix_run_tests();
if (lineno != -1) begin
$display("[%0t] %%Error: t_dpix_ort_c.c:%0d: dpix_run_tests returned an error", $time, lineno);
$stop;
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule
module sub (input int inst);
export "DPI-C" function dpix_sub_inst;
function int dpix_sub_inst (int i); dpix_sub_inst = inst + i; endfunction
endmodule

View File

@ -0,0 +1,125 @@
// -*- C++ -*-
//*************************************************************************
//
// Copyright 2009-2009 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.
//
//*************************************************************************
#include <stdio.h>
#include <svdpi.h>
//======================================================================
#if defined(VERILATOR)
# include "Vt_dpi_export__Dpi.h"
#elif defined(VCS)
# include "../vc_hdrs.h"
#elif defined(CADENCE)
# define NEED_EXTERNS
#else
# error "Unknown simulator for DPI test"
#endif
#ifdef NEED_EXTERNS
extern "C" {
extern int dpix_run_tests();
extern int dpix_t_int(int i, int* o);
extern int dpix_t_renamed(int i, int* o);
extern int dpix_int123();
extern unsigned char dpix_f_bit(unsigned char i);
extern int dpix_f_int(int i);
extern char dpix_f_byte(char i);
extern short int dpix_f_shortint(short int i);
extern long long dpix_f_longint(long long i);
extern void* dpix_f_chandle(void* i);
extern int dpix_sub_inst (int i);
}
#endif
//======================================================================
#define CHECK_RESULT(got, exp) \
if ((got) != (exp)) { \
printf("%%Error: %s:%d: GOT = %llx EXP = %llx\n", __FILE__,__LINE__, (long long)(got), (long long)(exp)); \
return __LINE__; \
}
#define CHECK_RESULT_NNULL(got) \
if (!(got)) { \
printf("%%Error: %s:%d: GOT = %p EXP = !NULL\n", __FILE__,__LINE__, (got)); \
return __LINE__; \
}
static int check_sub(const char* name, int i) {
svScope scope = svGetScopeFromName(name);
#ifdef TEST_VERBOSE
printf("svGetScopeFromName(\"%s\") -> %p\n", name, scope);
#endif
CHECK_RESULT_NNULL (scope);
svScope prev = svGetScope();
svScope sout = svSetScope(scope);
CHECK_RESULT(sout, prev);
CHECK_RESULT(svGetScope(), scope);
int out = dpix_sub_inst(100*i);
CHECK_RESULT(out, 100*i + i);
return 0; // OK
}
// Called from our Verilog code to run the tests
int dpix_run_tests() {
printf("dpix_run_tests:\n");
#ifdef VERILATOR
static int didDump = 0;
if (didDump++ == 0) {
# ifdef TEST_VERBOSE
Verilated::scopesDump();
# endif
}
#endif
CHECK_RESULT (dpix_int123(), 0x123 );
#ifndef CADENCE // No export calls from an import
int o;
dpix_t_int(0x456, &o);
CHECK_RESULT (o, ~0x456UL);
dpix_t_renamed(0x456, &o);
CHECK_RESULT (o, 0x458UL);
#endif
CHECK_RESULT (dpix_f_bit(1), 0x0);
CHECK_RESULT (dpix_f_bit(0), 0x1);
// Simulators disagree over the next three's sign extension unless we mask the upper bits
CHECK_RESULT (dpix_f_int(1) & 0xffffffffUL, 0xfffffffeUL);
CHECK_RESULT (dpix_f_byte(1) & 0xffUL, 0xfe);
CHECK_RESULT (dpix_f_shortint(1) & 0xffffUL, 0xfffeUL);
CHECK_RESULT (dpix_f_longint(1), 0xfffffffffffffffeULL);
CHECK_RESULT (dpix_f_chandle((void*)(12345)), (void*)(12345));
#ifdef VERILATOR
if (int bad=check_sub("top.v.a",1)) return bad;
if (int bad=check_sub("top.v.b",2)) return bad;
#else
if (int bad=check_sub("top.t.a",1)) return bad;
if (int bad=check_sub("top.t.b",2)) return bad;
#endif
return -1; // OK status
}