Support unpacked array in DPI-C (#2648)

* Add tests for unpacked array in DPI-C

* Add more generic parameter generator to AstNodes

* Supports multi dimensional array in DPI ( DPI argmuments <=> Verilator internal type conversion)

consider typedef in V3Task

fix export test

fix inout for scalar

support export func of time

* V3Premit does not show an error for wide words nor ArraySel

* Unnecessary pack func for unapcked array does not appear anymore

* Support unpacked array in runtime header

- Add an overload for lvalue VL_CVT_PACK_STR_NN
- Allow conversion from void *

* touch up tests for codacy advices

* resolve free functions. no functional change intended.
This commit is contained in:
Yutetsu TAKATSUKASA
2020-11-19 22:02:58 +09:00
committed by GitHub
parent abfee1270f
commit 0102efd4ea
22 changed files with 6421 additions and 71 deletions
+104 -31
View File
@@ -455,38 +455,99 @@ string AstVar::cPubArgType(bool named, bool forReturn) const {
return arg;
}
string AstVar::dpiArgType(bool named, bool forReturn) const {
if (forReturn) named = false;
string arg;
if (isDpiOpenArray()) {
arg = "const svOpenArrayHandle";
} else if (!basicp()) {
arg = "UNKNOWN";
} else if (basicp()->isDpiBitVec()) {
if (forReturn) {
arg = "svBitVecVal";
} else if (isReadOnly()) {
arg = "const svBitVecVal*";
} else {
arg = "svBitVecVal*";
}
} else if (basicp()->isDpiLogicVec()) {
if (forReturn) {
arg = "svLogicVecVal";
} else if (isReadOnly()) {
arg = "const svLogicVecVal*";
} else {
arg = "svLogicVecVal*";
}
} else {
arg = basicp()->keyword().dpiType();
if (basicp()->keyword().isDpiUnsignable() && !basicp()->isSigned()) {
arg = "unsigned " + arg;
}
if (!forReturn && isWritable()) arg += "*";
class dpiTypesToStringConverter {
public:
virtual string openArray(const AstVar*) const { return "const svOpenArrayHandle"; }
virtual string bitLogicVector(const AstVar* varp, bool isBit) const {
return isBit ? "svBitVecVal" : "svLogicVecVal";
}
if (named) arg += " " + name();
return arg;
virtual string primitive(const AstVar* varp) const {
string type;
if (varp->basicp()->keyword().isDpiUnsignable() && !varp->basicp()->isSigned()) {
type = "unsigned ";
}
type += varp->basicp()->keyword().dpiType();
return type;
}
string convert(const AstVar* varp) const {
if (varp->isDpiOpenArray()) {
return openArray(varp);
} else if (!varp->basicp()) {
return "UNKNOWN";
} else if (varp->basicp()->isDpiBitVec() || varp->basicp()->isDpiLogicVec()) {
return bitLogicVector(varp, varp->basicp()->isDpiBitVec());
} else {
return primitive(varp);
}
}
};
string AstVar::dpiArgType(bool named, bool forReturn) const {
if (forReturn) {
return dpiTypesToStringConverter{}.convert(this);
} else {
class converter : public dpiTypesToStringConverter {
virtual string bitLogicVector(const AstVar* varp, bool isBit) const override {
return string(varp->isReadOnly() ? "const " : "")
+ dpiTypesToStringConverter::bitLogicVector(varp, isBit) + '*';
}
virtual string primitive(const AstVar* varp) const override {
string type = dpiTypesToStringConverter::primitive(varp);
if (varp->isWritable() || VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!varp->isWritable()
&& varp->basicp()->keyword() != AstBasicDTypeKwd::STRING)
type = "const " + type;
type += "*";
}
return type;
}
};
string arg = converter{}.convert(this);
if (named) arg += " " + name();
return arg;
}
}
string AstVar::dpiTmpVarType(const string& varName) const {
class converter : public dpiTypesToStringConverter {
string m_name;
string arraySuffix(const AstVar* varp, size_t n) const {
if (AstUnpackArrayDType* unpackp
= VN_CAST(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
// Convert multi dimensional unpacked array to 1D array
if (n == 0) n = 1;
n *= unpackp->arrayUnpackedElements();
return '[' + cvtToStr(n) + ']';
} else if (n > 0) {
return '[' + cvtToStr(n) + ']';
} else {
return "";
}
}
virtual string openArray(const AstVar* varp) const override {
return dpiTypesToStringConverter::openArray(varp) + ' ' + m_name
+ arraySuffix(varp, 0);
}
virtual string bitLogicVector(const AstVar* varp, bool isBit) const override {
string type = dpiTypesToStringConverter::bitLogicVector(varp, isBit);
type += ' ' + m_name + arraySuffix(varp, varp->widthWords());
return type;
}
virtual string primitive(const AstVar* varp) const override {
string type = dpiTypesToStringConverter::primitive(varp);
if (varp->isWritable() || VN_IS(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!varp->isWritable() && varp->basicp()->keyword() == AstBasicDTypeKwd::CHANDLE)
type = "const " + type;
}
type += ' ' + m_name + arraySuffix(varp, 0);
return type;
}
public:
explicit converter(const string& name)
: m_name(name) {}
};
return converter{varName}.convert(this);
}
string AstVar::scType() const {
@@ -1347,6 +1408,18 @@ string AstUnpackArrayDType::prettyDTypeName() const {
os << subp->prettyDTypeName() << "$" << ranges;
return os.str();
}
std::vector<AstUnpackArrayDType*> AstUnpackArrayDType::unpackDimensions() {
std::vector<AstUnpackArrayDType*> dims;
for (AstUnpackArrayDType* unpackp = this; unpackp;) {
dims.push_back(unpackp);
if (AstNodeDType* subp = unpackp->subDTypep()) {
unpackp = VN_CAST(subp, UnpackArrayDType);
} else {
unpackp = nullptr;
}
}
return dims;
}
void AstNetlist::dump(std::ostream& str) const {
this->AstNode::dump(str);
str << " [" << timeunit() << "/" << timeprecision() << "]";
+3
View File
@@ -721,6 +721,8 @@ public:
}
ASTNODE_NODE_FUNCS(UnpackArrayDType)
virtual string prettyDTypeName() const override;
// Outer dimension comes first. The first element is this node.
std::vector<AstUnpackArrayDType*> unpackDimensions();
};
class AstUnsizedArrayDType final : public AstNodeDType {
@@ -2026,6 +2028,7 @@ public:
// Return C /*public*/ type for argument: bool, uint32_t, uint64_t, etc.
string cPubArgType(bool named, bool forReturn) const;
string dpiArgType(bool named, bool forReturn) const; // Return DPI-C type for argument
string dpiTmpVarType(const string& varName) const;
// Return Verilator internal type for argument: CData, SData, IData, WData
string vlArgType(bool named, bool forReturn, bool forFunc, const string& namespc = "") const;
string vlEnumType() const; // Return VerilatorVarType: VLVT_UINT32, etc
+3 -1
View File
@@ -127,7 +127,9 @@ private:
} else if (VN_IS(nodep->backp(), Sel)
&& VN_CAST(nodep->backp(), Sel)->widthp() == nodep) {
// AstSel::width must remain a constant
} else if (nodep->firstAbovep() && VN_IS(nodep->firstAbovep(), ArraySel)) {
} else if ((nodep->firstAbovep() && VN_IS(nodep->firstAbovep(), ArraySel))
|| ((VN_IS(m_stmtp, CCall) || VN_IS(m_stmtp, CStmt))
&& VN_IS(nodep, ArraySel))) {
// ArraySel's are pointer refs, ignore
} else {
UINFO(4, "Cre Temp: " << nodep << endl);
+4 -3
View File
@@ -411,11 +411,12 @@ private:
string cInputConnection(AstVar* varp) {
string frstmt;
bool useSetWSvlv = V3Task::dpiToInternalFrStmt(varp, varp->name(), frstmt);
string ket;
bool useSetWSvlv = V3Task::dpiToInternalFrStmt(varp, varp->name(), frstmt, ket);
if (useSetWSvlv) {
return frstmt + " handlep__V->" + varp->name() + ", " + varp->name() + ");\n";
return frstmt + ket + " handlep__V->" + varp->name() + ", " + varp->name() + ");\n";
}
return "handlep__V->" + varp->name() + " = " + frstmt + ";\n";
return "handlep__V->" + varp->name() + " = " + frstmt + ket + ";\n";
}
void handleClock(AstVar* varp) {
+126 -35
View File
@@ -641,11 +641,7 @@ private:
}
AstNode* createDpiTemp(AstVar* portp, const string& suffix) {
string stmt = portp->dpiArgType(false, true) + " " + portp->name() + suffix;
if (!portp->basicp()->isDpiPrimitive()) {
stmt += "[" + cvtToStr(portp->widthWords()) + "]";
}
stmt += ";\n";
const string stmt = portp->dpiTmpVarType(portp->name() + suffix) + ";\n";
return new AstCStmt(portp->fileline(), stmt);
}
@@ -655,30 +651,80 @@ private:
return new AstCStmt(portp->fileline(), stmt);
}
static std::vector<std::pair<AstUnpackArrayDType*, int>> unpackDimsAndStrides(AstVar* varp) {
std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides;
if (AstUnpackArrayDType* dtypep = VN_CAST(varp->dtypep()->skipRefp(), UnpackArrayDType)) {
const std::vector<AstUnpackArrayDType*> dims = dtypep->unpackDimensions();
dimStrides.resize(dims.size(), {nullptr, 0});
dimStrides.back() = {dims.back(), 1};
for (ssize_t i = dims.size() - 2; i >= 0; --i) {
dimStrides[i].first = dims[i];
dimStrides[i].second = dimStrides[i + 1].second * dims[i + 1]->elementsConst();
}
}
return dimStrides;
}
AstNode* createAssignDpiToInternal(AstVarScope* portvscp, const string& frName) {
// Create assignment from DPI temporary into internal format
// DPI temporary is scalar or 1D array (if unpacked array)
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
AstVar* portp = portvscp->varp();
string frstmt;
bool useSetWSvlv = V3Task::dpiToInternalFrStmt(portp, frName, frstmt);
if (useSetWSvlv) {
AstNode* linesp = new AstText(portp->fileline(), frstmt);
linesp->addNext(new AstVarRef(portp->fileline(), portvscp, VAccess::WRITE));
linesp->addNext(new AstText(portp->fileline(), "," + frName + ");"));
return new AstCStmt(portp->fileline(), linesp);
}
string ket;
const bool useSetWSvlv = V3Task::dpiToInternalFrStmt(portp, frName, frstmt, ket);
// Use a AstCMath, as we want V3Clean to mask off bits that don't make sense.
int cwidth = VL_IDATASIZE;
if (portp->basicp()) {
if (!useSetWSvlv && portp->basicp()) {
if (portp->basicp()->keyword().isBitLogic()) {
cwidth = VL_EDATASIZE * portp->widthWords();
} else {
cwidth = portp->basicp()->keyword().width();
}
}
AstNode* newp = new AstAssign(
portp->fileline(), new AstVarRef(portp->fileline(), portvscp, VAccess::WRITE),
new AstSel(portp->fileline(), new AstCMath(portp->fileline(), frstmt, cwidth, false),
0, portp->width()));
const std::vector<std::pair<AstUnpackArrayDType*, int>> dimStrides
= unpackDimsAndStrides(portvscp->varp());
const int total = dimStrides.empty() ? 1
: dimStrides.front().first->elementsConst()
* dimStrides.front().second;
AstNode* newp = nullptr;
const int widthWords = portvscp->varp()->basicp()->widthWords();
for (int i = 0; i < total; ++i) {
AstNode* srcp = new AstVarRef(portvscp->fileline(), portvscp, VAccess::WRITE);
// extract a scalar from multi-dimensional array (internal format)
for (auto&& dimStride : dimStrides) {
const size_t dimIdx = (i / dimStride.second) % dimStride.first->elementsConst();
srcp = new AstArraySel(portvscp->fileline(), srcp, dimIdx);
}
AstNode* stmtp = nullptr;
// extract a scalar from DPI temporary var that is scalar or 1D array
if (useSetWSvlv) {
AstNode* linesp = new AstText(portvscp->fileline(), frstmt + ket);
linesp->addNext(srcp);
linesp->addNext(
new AstText(portvscp->fileline(),
"," + frName + " + " + cvtToStr(i * widthWords) + ");\n"));
stmtp = new AstCStmt(portvscp->fileline(), linesp);
} else {
string from = frstmt;
if (!dimStrides.empty()) {
// e.g. time is 64bit svLogicVector
const int coef = portvscp->varp()->basicp()->isDpiLogicVec() ? widthWords : 1;
from += "[" + cvtToStr(i * coef) + "]";
}
from += ket;
AstNode* rhsp = new AstSel(portp->fileline(),
new AstCMath(portp->fileline(), from, cwidth, false), 0,
portp->width());
stmtp = new AstAssign(portp->fileline(), srcp, rhsp);
}
if (i > 0) {
newp->addNext(stmtp);
} else {
newp = stmtp;
}
}
return newp;
}
@@ -749,7 +795,10 @@ private:
if (portp->isNonOutput()) {
std::string frName
= portp->isInoutish() && portp->basicp()->isDpiPrimitive() ? "*" : "";
= portp->isInoutish() && portp->basicp()->isDpiPrimitive()
&& portp->dtypep()->skipRefp()->arrayUnpackedElements() == 1
? "*"
: "";
frName += portp->name();
dpip->addStmtsp(createAssignDpiToInternal(outvscp, frName));
}
@@ -913,7 +962,7 @@ private:
args += "&" + name;
} else {
if (portp->isWritable() && portp->basicp()->isDpiPrimitive()) {
args += "&";
if (!VN_IS(portp->dtypep()->skipRefp(), UnpackArrayDType)) args += "&";
}
args += portp->name() + tmpSuffixp;
@@ -1532,37 +1581,76 @@ V3TaskConnects V3Task::taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp)
string V3Task::assignInternalToDpi(AstVar* portp, bool isPtr, const string& frSuffix,
const string& toSuffix, const string& frPrefix) {
// Create assignment from internal format into DPI temporary
// Internal representation is scalar, 1D, or multi-dimensional array (similar to SV)
// DPI temporary is scalar or 1D array (if unpacked array)
string stmt;
string ket;
// Someday we'll have better type support, and this can make variables and casts.
// But for now, we'll just text-bash it.
string frName = frPrefix + portp->name() + frSuffix;
string toName = portp->name() + toSuffix;
if (portp->basicp()->isDpiBitVec()) {
stmt += ("VL_SET_SVBV_" + string(portp->dtypep()->charIQWN()) + "("
+ cvtToStr(portp->width()) + ", " + toName + ", " + frName + ")");
} else if (portp->basicp()->isDpiLogicVec()) {
stmt += ("VL_SET_SVLV_" + string(portp->dtypep()->charIQWN()) + "("
+ cvtToStr(portp->width()) + ", " + toName + ", " + frName + ")");
size_t unpackSize = 1; // non-unpacked array is treated as size 1
int unpackDim = 0;
if (AstUnpackArrayDType* unpackp = VN_CAST(portp->dtypep()->skipRefp(), UnpackArrayDType)) {
unpackSize = unpackp->arrayUnpackedElements();
unpackDim = unpackp->dimensions(false).second;
if (unpackDim > 0) UASSERT_OBJ(unpackSize > 0, portp, "size must be greater than 0");
}
if (portp->basicp()->isDpiBitVec() || portp->basicp()->isDpiLogicVec()) {
const bool isBit = portp->basicp()->isDpiBitVec();
const string idx = portp->name() + "__Vidx";
stmt = "for (size_t " + idx + " = 0; " + idx + " < " + cvtToStr(unpackSize) + "; ++" + idx
+ ") ";
stmt += (isBit ? "VL_SET_SVBV_" : "VL_SET_SVLV_")
+ string(portp->dtypep()->skipRefp()->charIQWN()) + "(" + cvtToStr(portp->width())
+ ", ";
stmt += toName + " + " + cvtToStr(portp->dtypep()->skipRefp()->widthWords()) + " * " + idx
+ ", ";
if (unpackDim > 0) { // Access multi-dimensional array as a 1D array
stmt += "(&" + frName;
for (int i = 0; i < unpackDim; ++i) stmt += "[0]";
stmt += ")[" + idx + "])";
} else {
stmt += frName + ")";
}
} else {
if (isPtr) stmt += "*"; // DPI outputs are pointers
stmt += toName + " = ";
if (portp->basicp() && portp->basicp()->keyword() == AstBasicDTypeKwd::CHANDLE) {
const bool isChandle
= portp->basicp() && portp->basicp()->keyword() == AstBasicDTypeKwd::CHANDLE;
const bool isString
= portp->basicp() && portp->basicp()->keyword() == AstBasicDTypeKwd::STRING;
const string idx = portp->name() + "__Vidx";
stmt = "for (size_t " + idx + " = 0; " + idx + " < " + cvtToStr(unpackSize) + "; ++" + idx
+ ") ";
if (unpackDim > 0) {
stmt += toName + "[" + idx + "]";
} else {
if (isPtr) stmt += "*"; // DPI outputs are pointers
stmt += toName;
}
stmt += " = ";
if (isChandle) {
stmt += "VL_CVT_Q_VP(";
ket += ")";
}
stmt += frName;
if (portp->basicp() && portp->basicp()->keyword() == AstBasicDTypeKwd::STRING) {
stmt += ".c_str()";
if (unpackDim > 0) {
stmt += "(&" + frName;
for (int i = 0; i < unpackDim; ++i) stmt += "[0]";
stmt += ")[" + idx + "]";
} else {
stmt += frName;
}
if (isString) stmt += ".c_str()";
}
stmt += ket + ";\n";
return stmt;
}
bool V3Task::dpiToInternalFrStmt(AstVar* portp, const string& frName, string& frstmt) {
bool V3Task::dpiToInternalFrStmt(AstVar* portp, const string& frName, string& frstmt,
string& ket) {
ket.clear();
if (portp->basicp() && portp->basicp()->keyword() == AstBasicDTypeKwd::CHANDLE) {
frstmt = "VL_CVT_VP_Q(" + frName + ")";
frstmt = "VL_CVT_VP_Q(" + frName;
ket = ")";
} else if ((portp->basicp() && portp->basicp()->isDpiPrimitive())) {
frstmt = frName;
} else {
@@ -1572,8 +1660,11 @@ bool V3Task::dpiToInternalFrStmt(AstVar* portp, const string& frName, string& fr
frstmt = "VL_SET_W_" + frSvType + "(" + cvtToStr(portp->width()) + ",";
return true;
} else {
frstmt = "VL_SET_" + string(portp->dtypep()->charIQWN()) + "_" + frSvType + "("
+ frName + ")";
const AstNodeDType* dtypep = portp->dtypep()->skipRefp();
frstmt = "VL_SET_" + string(dtypep->charIQWN()) + "_" + frSvType + "(";
if (VN_IS(dtypep, UnpackArrayDType)) frstmt += "&";
frstmt += frName;
ket = ")";
}
}
return false;
+2 -1
View File
@@ -39,7 +39,8 @@ public:
static V3TaskConnects taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp);
static string assignInternalToDpi(AstVar* portp, bool isPtr, const string& frSuffix,
const string& toSuffix, const string& frPrefix = "");
static bool dpiToInternalFrStmt(AstVar* portp, const string& frName, string& frstmt);
static bool dpiToInternalFrStmt(AstVar* portp, const string& frName, string& frstmt,
string& ket);
static const char* dpiTemporaryVarSuffix();
};
+42
View File
@@ -4238,6 +4238,46 @@ private:
nodep->dtypeFrom(nodep->taskp());
// if (debug()) nodep->dumpTree(cout, " FuncOut: ");
}
// Returns true if dtypep0 and dtypep1 have same dimensions
static bool areSameSize(AstUnpackArrayDType* dtypep0, AstUnpackArrayDType* dtypep1) {
const std::vector<AstUnpackArrayDType*> dims0 = dtypep0->unpackDimensions();
const std::vector<AstUnpackArrayDType*> dims1 = dtypep1->unpackDimensions();
if (dims0.size() != dims1.size()) return false;
for (size_t i = 0; i < dims0.size(); ++i) {
if (dims0[i]->elementsConst() != dims1[i]->elementsConst()) return false;
}
return true;
}
// Makes sure that port and pin have same size and same datatype
void checkUnpackedArrayArgs(AstVar* portp, AstNode* pinp) {
if (AstUnpackArrayDType* portDtypep
= VN_CAST(portp->dtypep()->skipRefp(), UnpackArrayDType)) {
if (AstUnpackArrayDType* pinDtypep
= VN_CAST(pinp->dtypep()->skipRefp(), UnpackArrayDType)) {
if (!areSameSize(portDtypep, pinDtypep)) {
pinp->v3warn(E_UNSUPPORTED,
"Shape of the argument does not match the shape of the parameter "
<< "(" << pinDtypep->prettyDTypeNameQ() << " v.s. "
<< portDtypep->prettyDTypeNameQ() << ")");
}
if (portDtypep->basicp()->width() != pinDtypep->basicp()->width()
|| (portDtypep->basicp()->keyword() != pinDtypep->basicp()->keyword()
&& !(portDtypep->basicp()->keyword() == AstBasicDTypeKwd::LOGIC_IMPLICIT
&& pinDtypep->basicp()->keyword() == AstBasicDTypeKwd::LOGIC)
&& !(portDtypep->basicp()->keyword() == AstBasicDTypeKwd::LOGIC
&& pinDtypep->basicp()->keyword()
== AstBasicDTypeKwd::LOGIC_IMPLICIT))) {
pinp->v3warn(E_UNSUPPORTED,
"Shape of the argument does not match the shape of the parameter "
<< "(" << pinDtypep->basicp()->prettyDTypeNameQ() << " v.s. "
<< portDtypep->basicp()->prettyDTypeNameQ() << ")");
}
} else {
pinp->v3warn(E_UNSUPPORTED, "Argument is not an unpacked array while parameter "
<< portp->prettyNameQ() << " is");
}
}
}
void processFTaskRefArgs(AstNodeFTaskRef* nodep) {
// For arguments, is assignment-like context; see IEEE rules in AstNodeAssign
// Function hasn't been widthed, so make it so.
@@ -4288,6 +4328,7 @@ private:
else if (portp->basicp() && portp->basicp()->keyword() == AstBasicDTypeKwd::STRING
&& !VN_IS(pinp, CvtPackString)
&& !VN_IS(pinp, SFormatF) // Already generates a string
&& !VN_IS(portp->dtypep(), UnpackArrayDType) // Unpacked array must match
&& !(VN_IS(pinp, VarRef)
&& VN_CAST(pinp, VarRef)->varp()->basicp()->keyword()
== AstBasicDTypeKwd::STRING)) {
@@ -4311,6 +4352,7 @@ private:
AstNode* pinp = argp->exprp();
if (!pinp) continue; // Argument error we'll find later
// Change data types based on above accept completion
if (nodep->taskp()->dpiImport()) checkUnpackedArrayArgs(portp, pinp);
if (portp->isDouble()) VL_DO_DANGLING(spliceCvtD(pinp), pinp);
}
}