mirror of
https://github.com/verilator/verilator.git
synced 2025-04-05 12:12:39 +00:00
Support DPI exports
This commit is contained in:
parent
45aa8742d8
commit
ef3ed6e338
2
Changes
2
Changes
@ -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]
|
||||
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
//=========================================================================
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
@ -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]";
|
||||
}
|
||||
|
@ -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; }
|
||||
//
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
323
src/V3Task.cpp
323
src/V3Task.cpp
@ -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);
|
||||
|
@ -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());
|
||||
|
19
test_regress/t/t_dpi_2exp_bad.pl
Executable file
19
test_regress/t/t_dpi_2exp_bad.pl
Executable 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;
|
17
test_regress/t/t_dpi_2exp_bad.v
Normal file
17
test_regress/t/t_dpi_2exp_bad.v
Normal 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
22
test_regress/t/t_dpi_export.pl
Executable 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;
|
60
test_regress/t/t_dpi_export.v
Normal file
60
test_regress/t/t_dpi_export.v
Normal 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
|
125
test_regress/t/t_dpi_export_c.cpp
Normal file
125
test_regress/t/t_dpi_export_c.cpp
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user