Emit parameter values as 'static constexpr' instead of enum

All parameters that are required in the output are now emitted as
'static constexpr, except for string or array of strings parameters,
which  are still emitted as 'static const' (required as std::string is
not a literal type, so cannot be constexpr).  This simplifies handling
of parameters and supports 'real' parameters.
This commit is contained in:
Geza Lore 2021-07-22 18:59:03 +01:00
parent 9907d211ff
commit 4ab4c0c8ba
11 changed files with 163 additions and 68 deletions

View File

@ -16,6 +16,8 @@ Verilator 4.211 devel
* Fix -G to treat simple integer literals as signed (#3060). [Anikin1610]
* Output files are split based on the set of headers required
in order to aid incremental compilation via ccache (#3071). [Geza Lore]
* Parameter values are now emitted as 'static constexpr' instead of enum.
C++ direct references to parameters might require updating (#3077). [Geza Lore]
Verilator 4.210 2021-07-07

View File

@ -529,6 +529,25 @@ public:
bool isEventValue() const { return m_e == EVENTVALUE; }
bool isString() const { return m_e == STRING; }
bool isMTaskState() const { return m_e == MTASKSTATE; }
// Does this represent a C++ LiteralType? (can be constexpr)
bool isLiteralType() const {
switch (m_e) {
case BIT:
case BYTE:
case CHANDLE:
case INT:
case INTEGER:
case LOGIC:
case LONGINT:
case DOUBLE:
case SHORTINT:
case SCOPEPTR:
case CHARPTR:
case UINT32:
case UINT64: return true;
default: return false;
}
}
};
inline bool operator==(const AstBasicDTypeKwd& lhs, const AstBasicDTypeKwd& rhs) {
return lhs.m_e == rhs.m_e;
@ -2436,6 +2455,7 @@ public:
return (isString() ? "N" : isWide() ? "W" : isQuad() ? "Q" : "I");
}
string cType(const string& name, bool forFunc, bool isRef) const;
bool isLiteralType() const; // Does this represent a C++ LiteralType? (can be constexpr)
private:
class CTypeRecursed;

View File

@ -776,6 +776,20 @@ int AstNodeDType::widthPow2() const {
return 1;
}
bool AstNodeDType::isLiteralType() const {
if (auto* const dtypep = VN_CAST_CONST(skipRefp(), BasicDType)) {
return dtypep->keyword().isLiteralType();
} else if (auto* const dtypep = VN_CAST_CONST(skipRefp(), UnpackArrayDType)) {
return dtypep->basicp()->isLiteralType();
} else if (auto* const dtypep = VN_CAST_CONST(skipRefp(), StructDType)) {
// Currently all structs are packed, later this can be expanded to
// 'forall members _.isLiteralType()'
return dtypep->packed();
} else {
return false;
}
}
/// What is the base variable (or const) this dereferences?
AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) {
// Else AstArraySel etc; search for the base

View File

@ -183,7 +183,8 @@ void V3CCtors::cctorsAll() {
for (AstNode* np = modp->stmtsp(); np; np = np->nextp()) {
if (AstVar* const varp = VN_CAST(np, Var)) {
if (!varp->isIfaceParent() && !varp->isIfaceRef() && !varp->noReset()) {
if (!varp->isIfaceParent() && !varp->isIfaceRef() && !varp->noReset()
&& !varp->isParam()) {
const auto vrefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE};
var_reset.add(new AstCReset{varp->fileline(), vrefp});
}

View File

@ -21,7 +21,7 @@
#include "verilatedos.h"
#include "V3Global.h"
#include "V3EmitCBase.h"
#include "V3EmitCConstInit.h"
#include <algorithm>
#include <map>
@ -113,13 +113,14 @@ public:
//######################################################################
// Emit statements and math operators
class EmitCFunc VL_NOT_FINAL : public EmitCBaseVisitor {
class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
private:
AstVarRef* m_wideTempRefp; // Variable that _WW macros should be setting
int m_labelNum; // Next label number
int m_splitSize; // # of cfunc nodes placed into output file
bool m_inUC = false; // Inside an AstUCStmt or AstUCMath
std::vector<AstChangeDet*> m_blkChangeDetVec; // All encountered changes in block
bool m_emitConstInit = false; // Emitting constant initializer
protected:
EmitCLazyDecls m_lazyDecls; // Visitor for emitting lazy declarations
@ -178,8 +179,16 @@ public:
AstNodeDType* dtypep, int depth, const string& suffix);
void doubleOrDetect(AstChangeDet* changep, bool& gotOne);
void emitChangeDet();
void emitConstInit(AstNode* initp) {
// We should refactor emit to produce output into a provided buffer, not go through members
// variables. That way we could just invoke the appropriate emitter as needed.
VL_RESTORER(m_emitConstInit);
m_emitConstInit = true;
iterate(initp);
}
// VISITORS
using EmitCConstInit::visit;
virtual void visit(AstCFunc* nodep) override {
VL_RESTORER(m_useSelfForThis);
VL_RESTORER(m_cfuncp);
@ -1120,7 +1129,9 @@ public:
puts(funcNameProtect(funcp));
}
virtual void visit(AstConst* nodep) override {
if (nodep->isWide()) {
if (m_emitConstInit) {
EmitCConstInit::visit(nodep);
} else if (nodep->isWide()) {
UASSERT_OBJ(m_wideTempRefp, nodep, "Wide Constant w/ no temp");
emitConstant(nodep, m_wideTempRefp, "");
m_wideTempRefp = nullptr; // We used it, barf if set it a second time

View File

@ -103,8 +103,7 @@ class EmitCHeader final : public EmitCConstInit {
// Emit variables in consecutive anon and non-anon batches
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp()
|| (varp->isParam() && !VN_IS(varp->valuep(), Const))) {
if (varp->isIO() || varp->isSignal() || varp->isClassMember() || varp->isTemp()) {
const bool anon = isAnonOk(varp);
if (anon != lastAnon) emitCurrentList();
lastAnon = anon;
@ -126,31 +125,19 @@ class EmitCHeader final : public EmitCConstInit {
bool first = true;
for (AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST(nodep, Var)) {
if (varp->isParam() && (varp->isUsedParam() || varp->isSigPublic())) {
if (varp->isParam()) {
decorateFirst(first, "\n// PARAMETERS\n");
UASSERT_OBJ(varp->valuep(), nodep, "No init for a param?");
// These should be static const values, however older MSVC++ did't
// support them; should be ok now under C++11, need to refactor.
if (varp->isWide()) { // Unsupported for output
putsDecoration("// enum WData " + varp->nameProtect() + " //wide");
} else if (varp->isString()) {
puts("static const std::string " + protect("var_" + varp->name()) + ";\n");
} else if (!VN_IS(varp->valuep(), Const)) { // Unsupported for output
// putsDecoration("// enum ..... "+varp->nameProtect()
// +"not simple value, see variable above instead");
} else if (VN_IS(varp->dtypep(), BasicDType)
&& VN_CAST(varp->dtypep(), BasicDType)
->isOpaque()) { // Can't put out e.g. doubles
} else {
// enum
puts(varp->isQuad() ? "enum _QData" : "enum _IData");
puts("" + varp->nameProtect() + " { " + varp->nameProtect() + " = ");
iterateAndNextNull(varp->valuep());
puts("};\n");
// var
puts(varp->isQuad() ? "static const QData " : "static const IData ");
puts(protect("var_" + varp->name()) + ";\n");
// Only C++ LiteralTypes can be constexpr
const bool canBeConstexpr = varp->dtypep()->isLiteralType();
puts("static ");
puts(canBeConstexpr ? "constexpr " : "const ");
puts(varp->dtypep()->cType(varp->nameProtect(), false, false));
if (canBeConstexpr) {
puts(" = ");
iterate(varp->valuep());
}
puts(";\n");
}
}
}

View File

@ -202,35 +202,31 @@ class EmitCImp final : EmitCFunc {
}
}
void emitParamDefns(const AstNodeModule* modp) {
const string modName = prefixNameProtect(modp);
bool first = true;
for (const AstNode* nodep = modp->stmtsp(); nodep; nodep = nodep->nextp()) {
if (const AstVar* const varp = VN_CAST_CONST(nodep, Var)) {
if (varp->isParam() && (varp->isUsedParam() || varp->isSigPublic())) {
UASSERT_OBJ(varp->valuep(), nodep, "No init for a param?");
// These should be static const values, however older MSVC++ did't
// support them; should be ok now under C++11, need to refactor.
if (varp->isWide()) { // Unsupported for output
} else if (varp->isString()) {
puts("const std::string ");
puts(prefixNameProtect(modp) + "::" + protect("var_" + varp->name())
+ "(");
iterateAndNextNull(varp->valuep());
puts(");\n");
} else if (!VN_IS(varp->valuep(), Const)) { // Unsupported for output
// putsDecoration("// enum ..... "+varp->nameProtect()
// +"not simple value, see variable above instead");
} else if (VN_IS(varp->dtypep(), BasicDType)
&& VN_CAST(varp->dtypep(), BasicDType)
->isOpaque()) { // Can't put out e.g. doubles
} else {
puts(varp->isQuad() ? "const QData " : "const IData ");
puts(prefixNameProtect(modp) + "::" + protect("var_" + varp->name())
+ "(");
iterateAndNextNull(varp->valuep());
puts(");\n");
if (varp->isParam()) {
if (first) {
puts("\n");
putsDecoration("// Parameter definitions for " + modName + "\n");
first = false;
}
UASSERT_OBJ(varp->valuep(), nodep, "No init for a param?");
// Only C++ LiteralTypes can be constexpr
const bool canBeConstexpr = varp->dtypep()->isLiteralType();
puts(canBeConstexpr ? "constexpr " : "const ");
const string scopedName = modName + "::" + varp->nameProtect();
puts(varp->dtypep()->cType(scopedName, false, false));
if (!canBeConstexpr) {
puts(" = ");
emitConstInit(varp->valuep());
}
puts(";\n");
}
}
}
if (!first) puts("\n");
}
void emitCtorImp(const AstNodeModule* modp) {
const string modName = prefixNameProtect(modp);

View File

@ -857,13 +857,8 @@ void EmitCSyms::emitSymImp() {
putsQuoted(protect(it->second.m_varBasePretty));
std::string varName;
varName += (protectIf(scopep->nameDotless(), scopep->protect()) + ".");
if (varp->isParam()) {
varName += protect("var_" + varp->name());
} else {
varName += protect(varp->name());
}
varName += protectIf(scopep->nameDotless(), scopep->protect()) + ".";
varName += protect(varp->name());
if (varp->isParam()) {
if (varp->vlEnumType() == "VLVT_STRING") {

View File

@ -962,17 +962,8 @@ class ParamVisitor final : public AstNVisitor {
V3Const::constifyParamsEdit(nodep); // The variable, not just the var->init()
if (!VN_IS(nodep->valuep(), Const)
&& !VN_IS(nodep->valuep(), Unbounded)) { // Complex init, like an array
// Make a new INITIAL to set the value.
// This allows the normal array/struct handling code to properly
// initialize the parameter.
nodep->addNext(new AstInitial(
nodep->fileline(),
new AstAssign(nodep->fileline(),
new AstVarRef(nodep->fileline(), nodep, VAccess::WRITE),
nodep->valuep()->cloneTree(true))));
if (nodep->isFuncLocal()) {
// We put the initial in wrong place under a function. We
// should move the parameter out of the function and to the
// We should move the parameter out of the function and to the
// module, with appropriate dotting, but this confuses LinkDot
// (as then name isn't found later), so punt - probably can
// treat as static function variable when that is supported.

View File

@ -0,0 +1,21 @@
#!/usr/bin/env 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.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
scenarios(simulator => 1);
compile(
);
execute(
check_finished => 1,
);
ok(1);
1;

View File

@ -0,0 +1,57 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2021 by Geza Lore.
// SPDX-License-Identifier: CC0-1.0
typedef struct packed {
longint a;
longint b;
longint c;
} s_t;
module t;
localparam int c0 [4] = '{5, 6, 7, 8};
localparam bit [255:0] c1 [4] = '{9, 10, 11, 12};
localparam string c2 [2] = '{"baz", "quux"};
localparam s_t c3 [2] = '{'{a: 100, b: 200, c: 300},
'{a: 1000, b: 2000, c: 3000}};
a #(
.p0(c0),
.p1(c1),
.p2(c2),
.p3(c3)
) i_a ();
endmodule
module a
#(
parameter int p0 [4] = '{1, 2, 3, 4},
parameter bit [255:0] p1 [4] = '{1, 2, 3, 4},
parameter string p2 [2] = '{"foo", "bar"},
parameter s_t p3 [2] = '{'{a: 1, b: 2, c: 3},
'{a: 1, b: 2, c: 3}}
);
initial begin
// Go via $c to ensure parameters are emitted
if (p0[$c("0")] != 5) $stop;
if (p0[$c("1")] != 6) $stop;
if (p0[$c("2")] != 7) $stop;
if (p0[$c("3")] != 8) $stop;
if (p1[$c("0")] != 9) $stop;
if (p1[$c("1")] != 10) $stop;
if (p1[$c("2")] != 11) $stop;
if (p1[$c("3")] != 12) $stop;
if (p2[$c("0")] != "baz") $stop;
if (p2[$c("1")] != "quux") $stop;
if (p3[$c("0")].a != 100) $stop;
if (p3[$c("0")].b != 200) $stop;
if (p3[$c("0")].c != 300) $stop;
if (p3[$c("1")].a != 1000) $stop;
if (p3[$c("1")].b != 2000) $stop;
if (p3[$c("1")].c != 3000) $stop;
$write("*-* All Finished *-*\n");
$finish;
end
endmodule