Support 1800-2023 preprocessor ifdef expressions; add PREPROC zero warning.

This commit is contained in:
Wilson Snyder 2024-03-02 09:06:22 -05:00
parent 3786f59e03
commit 214173c6b8
22 changed files with 783 additions and 10 deletions

View File

@ -14,6 +14,7 @@ Verilator 5.023 devel
**Major:**
* Support 1800-2023 keywords.
* Support 1800-2023 preprocessor ifdef expressions.
* Support 1800-2023 DPI headers, svGetTime/svgGetTimeUnit/svGetTimePrecision methods.
**Minor:**

View File

@ -68,6 +68,9 @@ SystemVerilog 2023 (IEEE 1800-2023) Support
Verilator supports some of the 2023 improvements, including triple-quoted
string blocks that may include newlines and single quotes.
Verilator implements a full IEEE 1800-2023 compliant preprocessor,
including triple-quoted strings, and \`ifdef expressions.
Verilog AMS Support
-------------------

View File

@ -1332,6 +1332,31 @@ List Of Warnings
be declared before being used.
.. option:: PREPROCZERO
Warns that a preprocessor \`ifdef/\`ifndef expression (added in IEEE
1800-2023) evaluates a define value which has a value of :code:`0`.
This will evaluate in the expression as :code:`1` because the define has
a definition, unlike in the C preprocessor, which evaluates using the
define's value (of :code:`1`).
Referring to a define with an empty value does not give this warning, as
in C, the preprocessor will give an error on a preprocessor expression
of a define that is empty.
.. code-block:: sv
:linenos:
:emphasize-lines: 5-6
`define ZERO 0
`ifdef (ZERO || ZERO) //<--- warning PREPROCZERO
`error This_will_error_which_might_be_not_the_intent
`endif
The portable way to suppress this warning is to use a define value other
than zero to when used in a preprocessor expression.
.. option:: PROCASSWIRE
.. TODO better example

View File

@ -132,6 +132,7 @@ set(HEADERS
V3PartitionGraph.h
V3PchAstMT.h
V3PchAstNoMT.h
V3PreExpr.h
V3PreLex.h
V3PreProc.h
V3PreShell.h

View File

@ -127,6 +127,7 @@ public:
PINNOCONNECT, // Cell pin not connected
PINNOTFOUND, // instance port name not found in it's module
PKGNODECL, // Error: Package/class needs to be predeclared
PREPROCZERO, // Preprocessor expression with zero
PROCASSWIRE, // Procedural assignment on wire
PROFOUTOFDATE, // Profile data out of date
PROTECTED, // detected `pragma protected
@ -203,7 +204,7 @@ public:
"INCABSPATH", "INFINITELOOP", "INITIALDLY", "INSECURE",
"LATCH", "LITENDIAN", "MINTYPMAXDLY", "MISINDENT", "MODDUP",
"MULTIDRIVEN", "MULTITOP", "NEWERSTD", "NOLATCH", "NULLPORT", "PINCONNECTEMPTY",
"PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PROCASSWIRE",
"PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PREPROCZERO", "PROCASSWIRE",
"PROFOUTOFDATE", "PROTECTED", "RANDC", "REALCVT", "REDEFMACRO", "RISEFALLDLY",
"SELRANGE", "SHORTREAL", "SIDEEFFECT", "SPLITVAR",
"STATICVAR", "STMTDLY", "SYMRSVDWORD", "SYNCASYNCNET",
@ -242,9 +243,10 @@ public:
return (m_e == ALWCOMBORDER || m_e == ASCRANGE || m_e == BSSPACE || m_e == CASEINCOMPLETE
|| m_e == CASEOVERLAP || m_e == CASEWITHX || m_e == CASEX || m_e == CASTCONST
|| m_e == CMPCONST || m_e == COLONPLUS || m_e == IMPLICIT || m_e == IMPLICITSTATIC
|| m_e == LATCH || m_e == MISINDENT || m_e == NEWERSTD || m_e == PINMISSING
|| m_e == REALCVT || m_e == STATICVAR || m_e == UNSIGNED || m_e == WIDTH
|| m_e == WIDTHTRUNC || m_e == WIDTHEXPAND || m_e == WIDTHXZEXPAND);
|| m_e == LATCH || m_e == MISINDENT || m_e == NEWERSTD || m_e == PREPROCZERO
|| m_e == PINMISSING || m_e == REALCVT || m_e == STATICVAR || m_e == UNSIGNED
|| m_e == WIDTH || m_e == WIDTHTRUNC || m_e == WIDTHEXPAND
|| m_e == WIDTHXZEXPAND);
}
// Warnings that are style only
bool styleError() const VL_MT_SAFE {

293
src/V3PreExpr.h Normal file
View File

@ -0,0 +1,293 @@
// -*- mode: C++; c-file-style: "cc-mode" -*-
//*************************************************************************
// DESCRIPTION: Verilog::Preproc: Preprocess verilog code
//
// Code available from: https://verilator.org
//
//*************************************************************************
//
// Copyright 2000-2023 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 LOR Artistic-2.0
//
//*************************************************************************
#ifndef VERILATOR_V3PREEXPR_H_
#define VERILATOR_V3PREEXPR_H_
#include "config_build.h"
#include "verilatedos.h"
#include "V3FileLine.h"
#include "V3Global.h"
#include <deque>
using namespace std;
class V3PreExprToken final {
public:
// TYPES
// Order of enum must match token table
enum token_t : uint8_t { ZERO, ONE, END, BRA, KET, LNOT, LAND, LOR, IMP, EQV, MAX };
private:
// MEMBERS
FileLine* const m_fileline; // Token fileline
token_t const m_token; // Token value
public:
// METHODS
V3PreExprToken(FileLine* fileline, token_t token)
: m_fileline{fileline}
, m_token{token} {}
V3PreExprToken(FileLine* fileline, bool value)
: m_fileline{fileline}
, m_token{value ? ONE : ZERO} {}
~V3PreExprToken() = default;
const char* ascii() const {
static const char* names[] = {"0", "1", "$", "(", ")", "!", "&&", "||", "->", "<->"};
return names[m_token];
}
FileLine* fileline() const { return m_fileline; }
token_t token() const { return m_token; }
bool isValue() const { return m_token == ZERO || m_token == ONE; }
bool value() const {
UASSERT(isValue(), "preproc expr fetch of non-value");
return m_token == ONE;
}
};
class V3PreExpr final {
// MEMBERS
std::deque<V3PreExprToken> m_inputs; // Input stack
std::deque<V3PreExprToken> m_values; // Value stack
std::deque<V3PreExprToken> m_ops; // Operator stack
FileLine* m_firstFileline = nullptr;
VL_DEFINE_DEBUG_FUNCTIONS;
// PARSER DEFINITION
enum action_t : uint8_t {
VV, // Value
AA, // Accept
RR, // Reduce
SS, // Shift
EE // Error
};
static const char* actionAscii(action_t en) {
static const char* names[] = {"VV", "AA", "RR", "SS", "EE"};
return names[en];
}
// Operators Associativity Precedence
// --------- ------------- ----------
// () Left Highest
// ! (unary)
// && Left
// || Left
// -> <-> Right Lowest
//
// If different op precedence, shift for higher to input, else reduce for lower
// If same op precedence, shift for right assoc, reduce for left assoc
action_t parseTable[V3PreExprToken::MAX][V3PreExprToken::MAX] = {
// stack ------------- inputs ------------------
// 0 1 $ ( ) ! && || -> <->
/* 0 */ {EE, EE, EE, EE, EE, EE, EE, EE, EE, EE}, // 0 never on op stack
/* 1 */ {EE, EE, EE, EE, EE, EE, EE, EE, EE, EE}, // 1 never on op stack
/* $ */ {VV, VV, AA, SS, EE, SS, SS, SS, SS, SS},
/* ( */ {VV, VV, EE, SS, RR, SS, SS, SS, SS, SS},
/* ) */ {VV, VV, RR, EE, RR, RR, RR, RR, RR, RR},
/* ! */ {VV, VV, RR, SS, RR, SS, RR, RR, RR, RR},
/* && */ {VV, VV, RR, SS, RR, SS, SS, RR, RR, RR},
/* || */ {VV, VV, RR, SS, RR, SS, SS, SS, RR, RR},
/* -> */ {VV, VV, RR, SS, RR, SS, SS, SS, RR, RR},
/* <-> */ {VV, VV, RR, SS, RR, SS, SS, SS, RR, RR}};
static void selfTestImp() {
selfTestOne("0", false);
selfTestOne("1", true);
selfTestOne("! 0", true);
selfTestOne("! 1", false);
selfTestOne("0 || 0", false);
selfTestOne("0 || 1", true);
selfTestOne("1 || 0", true);
selfTestOne("1 || 1", true);
selfTestOne("0 && 0", false);
selfTestOne("0 && 1", false);
selfTestOne("1 && 0", false);
selfTestOne("1 && 1", true);
selfTestOne("0 -> 0", true);
selfTestOne("0 -> 1", true);
selfTestOne("1 -> 0", false);
selfTestOne("1 -> 1", true);
selfTestOne("0 <-> 0", true);
selfTestOne("0 <-> 1", false);
selfTestOne("1 <-> 0", false);
selfTestOne("1 <-> 1", true);
selfTestOne("1 || 0 && 1", false);
selfTestOne("( 1 || 0 ) && 1", true);
selfTestOne("! 1 || ! 1", false);
selfTestOne("! 0 && ! 0", true);
}
static void selfTestOne(const string& expr, bool expect) {
// This hacky self-test parser just looks at first character of
// operator, and requires space separation of operators/values
UINFO(9, "V3PreExpr selfTestOne " << expr << endl);
FileLine* const flp = nullptr;
V3PreExpr parser;
parser.reset(flp);
bool tstart = true;
for (const char* cp = expr.c_str(); *cp; ++cp) {
if (tstart) {
tstart = false;
switch (*cp) {
case '0': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::ZERO}); break;
case '1': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::ONE}); break;
case '!': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::LNOT}); break;
case '|': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::LOR}); break;
case '&': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::LAND}); break;
case '-': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::IMP}); break;
case '<': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::EQV}); break;
case '(': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::BRA}); break;
case ')': parser.pushInput(V3PreExprToken{flp, V3PreExprToken::KET}); break;
default: break;
}
} else if (*cp == ' ') {
tstart = true;
}
}
const bool got = parser.result();
UASSERT_SELFTEST(bool, got, expect);
}
// METHODS
void pushOp(const V3PreExprToken& token) {
// UINFO(9, " pushOp " << token.ascii() << endl);
m_ops.push_back(token);
}
void pushValue(const V3PreExprToken& token) {
// UINFO(9, " pushValue " << token.ascii() << endl);
m_values.push_back(token);
}
V3PreExprToken popValue() {
if (m_values.empty()) {
m_firstFileline->v3error("Syntax error in `ifdef () expression");
return V3PreExprToken{m_firstFileline, false};
}
const V3PreExprToken tok = m_values.back();
m_values.pop_back();
// UINFO(9, " popValue " << tok.ascii() << endl;
return tok;
}
void reduce() {
UASSERT(!m_ops.empty(), "lost op stack beginning END");
V3PreExprToken tok = m_ops.back();
// UINFO(9, "Reduce " << tok.ascii() << endl);
m_ops.pop_back();
switch (tok.token()) {
case V3PreExprToken::KET: {
while (m_ops.back().token() != V3PreExprToken::END
&& m_ops.back().token() != V3PreExprToken::BRA)
reduce();
if (m_ops.back().token() == V3PreExprToken::BRA) {
m_ops.pop_back();
} else {
tok.fileline()->v3error("Syntax error in `ifdef () expression:" // LCOV_EXCL_LINE
" ) without matching )");
return;
}
break;
}
case V3PreExprToken::LNOT: {
const V3PreExprToken lhs = popValue();
pushValue(V3PreExprToken{tok.fileline(), !lhs.value()});
break;
}
case V3PreExprToken::LAND: {
const V3PreExprToken rhs = popValue();
const V3PreExprToken lhs = popValue();
pushValue(V3PreExprToken{tok.fileline(), lhs.value() && rhs.value()});
break;
}
case V3PreExprToken::LOR: {
const V3PreExprToken rhs = popValue();
const V3PreExprToken lhs = popValue();
pushValue(V3PreExprToken{tok.fileline(), lhs.value() || rhs.value()});
break;
}
case V3PreExprToken::IMP: {
const V3PreExprToken rhs = popValue();
const V3PreExprToken lhs = popValue();
pushValue(V3PreExprToken{tok.fileline(), !lhs.value() || rhs.value()});
break;
}
case V3PreExprToken::EQV: {
const V3PreExprToken rhs = popValue();
const V3PreExprToken lhs = popValue();
pushValue(V3PreExprToken{tok.fileline(), lhs.value() == rhs.value()});
break;
}
default: {
v3fatalSrc("bad case on operand stack");
break;
}
}
}
void parse() {
while (!m_inputs.empty()) {
V3PreExprToken tok = m_inputs.front();
m_inputs.pop_front();
UINFO(9, "input read " << tok.ascii() << endl);
if (tok.isValue()) {
pushValue(tok);
continue;
}
UASSERT(!m_ops.empty(), "lost op stack beginning END");
V3PreExprToken topTok = m_ops.back();
auto action = parseTable[topTok.token()][tok.token()];
UINFO(9, "pop action " << actionAscii(action) << " from parseTable[" << topTok.ascii()
<< "][" << tok.ascii() << "]\n");
switch (action) {
case RR: // Reduce
reduce();
break;
case SS: // Shift
m_ops.push_back(tok);
break;
case AA: // Accept
break;
default: tok.fileline()->v3error("Syntax error in `ifdef () expression"); return;
}
}
}
public:
// METHODS
V3PreExpr() {}
~V3PreExpr() = default;
void reset(FileLine* flp) {
m_inputs.clear();
m_values.clear();
m_ops.clear();
m_firstFileline = flp;
pushOp(V3PreExprToken{flp, V3PreExprToken::END});
}
void pushInput(const V3PreExprToken& token) {
if (!m_firstFileline) m_firstFileline = token.fileline();
UINFO(9, "pushInput " << token.ascii() << endl);
m_inputs.push_back(token);
}
bool result() {
pushInput(V3PreExprToken{m_firstFileline, V3PreExprToken::END});
parse();
return popValue().value();
}
static void selfTest() VL_MT_DISABLED { selfTestImp(); }
};
#endif // Guard

View File

@ -217,6 +217,7 @@ public: // Used only by V3PreLex.cpp and V3PreProc.cpp
void pushStateDefArg(int level);
void pushStateDefForm();
void pushStateDefValue();
void pushStateExpr();
void pushStateIncFilename();
void scanNewFile(FileLine* filelinep);
void scanBytes(const string& str);

View File

@ -73,6 +73,7 @@ static void appendDefValue(const char* t, size_t l) { LEXP->appendDefValue(t, l)
%x DEFFPAR
%x DEFVAL
%x ENCBASE64
%x EXPR
%x INCMODE
%x PRAGMA
%x PRAGMAERR
@ -375,6 +376,23 @@ bom [\357\273\277]
<DEFCMT><<EOF>> { FL_FWDC; yyerrorf("EOF in '/* ... */' block comment\n");
yyleng=0; return VP_EOF_ERROR; }
/* Preprocessor expression */
<EXPR><<EOF>> { FL_FWDC; linenoInc(); yyerrorf("EOF in unterminated preprocessor expression");
yyleng = 0; return VP_EOF_ERROR; }
<EXPR>"/*" { yy_push_state(CMTMODE); yymore(); }
<EXPR>"//"[^\n\r]* { FL_FWDC; return VP_COMMENT;}
<EXPR>"(" { FL_FWDC; return VP_TEXT; } /* V3PreProc will push another EXPR state to stack */
<EXPR>")" { FL_FWDC; yy_pop_state(); return VP_TEXT; }
<EXPR>"!" { FL_FWDC; return VP_TEXT; }
<EXPR>"&&" { FL_FWDC; return VP_TEXT; }
<EXPR>"||" { FL_FWDC; return VP_TEXT; }
<EXPR>"->" { FL_FWDC; return VP_TEXT; }
<EXPR>"<->" { FL_FWDC; return VP_TEXT; }
<EXPR>{symb} { FL_FWDC; return VP_SYMBOL; }
<EXPR>{crnl} { FL_FWDC; linenoInc(); yytext=(char*)"\n"; yyleng=1; return VP_WHITE; }
<EXPR>{wsn}+ { FL_FWDC; return VP_WHITE; }
<EXPR>. { FL_FWDC; return VP_TEXT; }
/* Define arguments (use of a define) */
<ARGMODE>"/*" { yy_push_state(CMTMODE); yymore(); }
<ARGMODE>"//"[^\n\r]* { FL_FWDC; return VP_COMMENT; }
@ -483,6 +501,11 @@ void V3PreLex::pushStateDefValue() {
m_defValue = "";
}
void V3PreLex::pushStateExpr() {
// Enter preprocessor expression state
yy_push_state(EXPR);
}
void V3PreLex::pushStateIncFilename() {
// Enter include <> filename state
yy_push_state(INCMODE);

View File

@ -26,6 +26,7 @@
#include "V3File.h"
#include "V3Global.h"
#include "V3LanguageWords.h"
#include "V3PreExpr.h"
#include "V3PreLex.h"
#include "V3PreShell.h"
#include "V3String.h"
@ -134,12 +135,15 @@ public:
ps_DEFNAME_IFDEF,
ps_DEFNAME_IFNDEF,
ps_DEFNAME_ELSIF,
ps_DEFFORM,
ps_DEFVALUE,
ps_DEFPAREN,
ps_DEFARG,
ps_INCNAME,
ps_DEFFORM,
ps_DEFPAREN,
ps_DEFVALUE,
ps_ERRORNAME,
ps_EXPR_IFDEF,
ps_EXPR_IFNDEF,
ps_EXPR_ELSIF,
ps_INCNAME,
ps_JOIN,
ps_STRIFY
};
@ -147,8 +151,9 @@ public:
static const char* const states[]
= {"ps_TOP", "ps_DEFNAME_UNDEF", "ps_DEFNAME_DEFINE",
"ps_DEFNAME_IFDEF", "ps_DEFNAME_IFNDEF", "ps_DEFNAME_ELSIF",
"ps_DEFFORM", "ps_DEFVALUE", "ps_DEFPAREN",
"ps_DEFARG", "ps_INCNAME", "ps_ERRORNAME",
"ps_DEFARG", "ps_DEFFORM", "ps_DEFPAREN",
"ps_DEFVALUE", "ps_ERRORNAME", "ps_EXPR_IFDEF",
"ps_EXPR_IFNDEF", "ps_EXPR_ELSIF", "ps_INCNAME",
"ps_JOIN", "ps_STRIFY"};
return states[s];
}
@ -184,6 +189,10 @@ public:
// For `` join
std::stack<string> m_joinStack; ///< Text on lhs of join
// for `ifdef () expressions
V3PreExpr m_exprParser; ///< Parser for () expression
int m_exprParenLevel = 0; ///< Number of ( deep in `ifdef () expression
// For getline()
string m_lineChars; ///< Characters left for next line
@ -290,6 +299,8 @@ V3PreProc* V3PreProc::createPreProc(FileLine* fl) {
return preprocp;
}
void V3PreProc::selfTest() VL_MT_DISABLED { V3PreExpr::selfTest(); }
//*************************************************************************
// Defines
@ -1098,6 +1109,21 @@ int V3PreProcImp::getStateToken() {
goto next_tok;
} else if (tok == VP_TEXT) {
// IE, something like comment between define and symbol
if (yyourleng() == 1 && yyourtext()[0] == '('
&& (state() == ps_DEFNAME_IFDEF || state() == ps_DEFNAME_IFNDEF
|| state() == ps_DEFNAME_ELSIF)) {
UINFO(4, "ifdef() start (\n");
m_lexp->pushStateExpr();
m_exprParser.reset(fileline());
m_exprParenLevel = 1;
switch (state()) {
case ps_DEFNAME_IFDEF: stateChange(ps_EXPR_IFDEF); break;
case ps_DEFNAME_IFNDEF: stateChange(ps_EXPR_IFNDEF); break;
case ps_DEFNAME_ELSIF: stateChange(ps_EXPR_ELSIF); break;
default: v3fatalSrc("bad case");
}
goto next_tok;
}
if (!m_off) {
return tok;
} else {
@ -1111,6 +1137,92 @@ int V3PreProcImp::getStateToken() {
goto next_tok;
}
}
case ps_EXPR_IFDEF: // FALLTHRU
case ps_EXPR_IFNDEF: // FALLTHRU
case ps_EXPR_ELSIF: {
// `ifdef ( *here*
FileLine* const flp = m_lexp->m_tokFilelinep;
if (tok == VP_SYMBOL) {
m_lastSym.assign(yyourtext(), yyourleng());
const bool exists = defExists(m_lastSym);
if (exists) {
string value = defValue(m_lastSym);
if (VString::removeWhitespace(value) == "0") {
flp->v3warn(
PREPROCZERO,
"Preprocessor expression evaluates define with 0: '"
<< m_lastSym << "' with value '" << value
<< "'\n"
"... Suggest change define '"
<< m_lastSym
<< "' to non-zero value if used in preprocessor expression");
}
}
m_exprParser.pushInput(V3PreExprToken{flp, exists});
goto next_tok;
} else if (tok == VP_WHITE) {
goto next_tok;
} else if (tok == VP_TEXT && yyourleng() == 1 && yyourtext()[0] == '(') {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::BRA});
goto next_tok;
} else if (tok == VP_TEXT && yyourleng() == 1 && yyourtext()[0] == ')') {
UASSERT(m_exprParenLevel, "Underflow of ); should have exited ps_EXPR earlier?");
if (--m_exprParenLevel > 0) {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::KET});
goto next_tok;
} else {
// Done with parsing expression
bool enable = m_exprParser.result();
UINFO(4, "ifdef() result=" << enable << endl);
if (state() == ps_EXPR_IFDEF || state() == ps_EXPR_IFNDEF) {
if (state() == ps_EXPR_IFNDEF) enable = !enable;
m_ifdefStack.push(VPreIfEntry{enable, false});
if (!enable) parsingOff();
statePop();
goto next_tok;
} else if (state() == ps_EXPR_ELSIF) {
if (m_ifdefStack.empty()) {
error("`elsif with no matching `if\n");
} else {
// Handle `else portion
const VPreIfEntry lastIf = m_ifdefStack.top();
m_ifdefStack.pop();
if (!lastIf.on()) parsingOn();
// Handle `if portion
enable = !lastIf.everOn() && enable;
UINFO(4, "Elsif " << m_lastSym << (enable ? " ON" : " OFF") << endl);
m_ifdefStack.push(VPreIfEntry{enable, lastIf.everOn()});
if (!enable) parsingOff();
}
statePop();
}
goto next_tok;
}
} else if (tok == VP_TEXT && yyourleng() == 1 && yyourtext()[0] == '!') {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::LNOT});
goto next_tok;
} else if (tok == VP_TEXT && yyourleng() == 2 && 0 == strncmp(yyourtext(), "&&", 2)) {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::LAND});
goto next_tok;
} else if (tok == VP_TEXT && yyourleng() == 2 && 0 == strncmp(yyourtext(), "||", 2)) {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::LOR});
goto next_tok;
} else if (tok == VP_TEXT && yyourleng() == 2 && 0 == strncmp(yyourtext(), "->", 2)) {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::IMP});
goto next_tok;
} else if (tok == VP_TEXT && yyourleng() == 3 && 0 == strncmp(yyourtext(), "<->", 3)) {
m_exprParser.pushInput(V3PreExprToken{flp, V3PreExprToken::EQV});
goto next_tok;
} else {
if (VString::removeWhitespace(string{yyourtext(), yyourleng()}).empty()) {
return tok;
} else {
error(std::string{"Syntax error in `ifdef () expression; unexpected: '"}
+ tokenName(tok) + "'\n");
}
goto next_tok;
}
}
case ps_DEFFORM: {
if (tok == VP_DEFFORM) {
m_formals = m_lexp->m_defValue;

View File

@ -99,6 +99,7 @@ protected:
public:
static V3PreProc* createPreProc(FileLine* fl) VL_MT_DISABLED;
virtual ~V3PreProc() = default; // LCOV_EXCL_LINE // Persistent
static void selfTest() VL_MT_DISABLED;
};
#endif // Guard

View File

@ -176,3 +176,4 @@ void V3PreShell::dumpDefines(std::ostream& os) { V3PreShellImp::s_preprocp->dump
void V3PreShell::candidateDefines(VSpellCheck* spellerp) {
V3PreShellImp::s_preprocp->candidateDefines(spellerp);
}
void V3PreShell::selfTest() { V3PreProc::selfTest(); }

View File

@ -42,6 +42,7 @@ public:
static void undef(const string& name) VL_MT_DISABLED;
static void dumpDefines(std::ostream& os) VL_MT_DISABLED;
static void candidateDefines(VSpellCheck* spellerp) VL_MT_DISABLED;
static void selfTest() VL_MT_DISABLED;
};
#endif // Guard

View File

@ -678,6 +678,7 @@ static void verilate(const string& argString) {
V3ScoreboardBase::selfTest();
V3Partition::selfTest();
V3Partition::selfTestNormalizeCosts();
V3PreShell::selfTest();
V3Broken::selfTest();
}
V3ThreadPool::selfTest();

View File

@ -0,0 +1,19 @@
`begin_keywords "1800-2023"
"ok ( ONE )"
"ok ( ! ONE )"
"ok ( ! ZERO )"
"ok ( ZERO || ZERO || ONE )"
"ok ( ONE && ONE && ONE )"
"ok ( ZERO && ZERO || ONE )"
"ok ( ZERO -> ZERO)"
"ok ( ZERO -> ONE)"
"ok ( ZERO -> ONE)"
"ok ( ZERO -> ONE)"
"ok ( ONE -> ZERO)"
"ok ( ONE -> ONE)"
"ok ( ZERO <-> ZERO)"
"ok ( ZERO <-> ONE)"
"ok ( ONE <-> ZERO)"
"ok ( ONE <-> ONE)"
"ok "
Line: 117

View File

@ -0,0 +1,26 @@
#!/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-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.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
scenarios(vlt => 1);
my $stdout_filename = "$Self->{obj_dir}/$Self->{name}__test.vpp";
compile(
verilator_flags2 => ['-E -P'],
verilator_make_gmake => 0,
make_top_shell => 0,
make_main => 0,
stdout_filename => $stdout_filename,
);
files_identical($stdout_filename, $Self->{golden_filename});
ok(1);
1;

View File

@ -0,0 +1,117 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`begin_keywords "1800-2023"
`define ONE
`undef ZERO
`ifdef ( ONE )
"ok ( ONE )"
`endif
// Test no spaces around ()
`ifdef (ZERO)
`error "( ZERO )"
`endif
`ifndef ( ! ONE )
"ok ( ! ONE )"
`endif
// Test no spaces around ()
`ifndef (!ZERO)
`error "( ! ZERO )"
`endif
`ifdef ( ! ZERO )
"ok ( ! ZERO )"
`endif
`ifdef ( ! ONE )
`error "( ! ONE )"
`endif
`ifdef ( ZERO || ZERO || ONE )
"ok ( ZERO || ZERO || ONE )"
`endif
`ifdef ( ZERO || ZERO || ZERO )
`error "( ZERO || ZERO || ZERO )"
`endif
`ifdef ( ONE && ONE && ONE )
"ok ( ONE && ONE && ONE )"
`endif
`ifdef ( ONE && ONE && ZERO )
`error "( ONE && ONE && ZERO )"
`endif
// Precedence of && is under ||
`ifdef ( ZERO && ZERO || ONE )
"ok ( ZERO && ZERO || ONE )"
`endif
`ifdef ( ONE || ZERO && ZERO )
"ok ( ONE || ZERO && ZERO )"
`endif
`ifdef ZERO
`elsif ( ONE && !( ZERO && ONE ) )
"ok ( ONE && !( ZERO && ONE ) )"
`endif
`ifdef ( ZERO -> ZERO)
"ok ( ZERO -> ZERO)"
`endif
// Text extra newlines
`ifdef ( ZERO
->
ONE)
"ok ( ZERO -> ONE)"
`endif
// Text comments
`ifdef ( ZERO // Zero
-> // Operator
ONE) // One
"ok ( ZERO -> ONE)"
`endif
`ifdef ( /*val*/ ZERO
/*op*/ ->
/*val*/ ONE)
"ok ( ZERO -> ONE)"
`endif
`ifndef ( ONE -> ZERO)
"ok ( ONE -> ZERO)"
`endif
`ifdef ( ONE -> ONE)
"ok ( ONE -> ONE)"
`endif
`ifdef ( ZERO <-> ZERO)
"ok ( ZERO <-> ZERO)"
`endif
`ifndef ( ZERO <-> ONE)
"ok ( ZERO <-> ONE)"
`endif
`ifndef ( ONE <-> ZERO)
"ok ( ONE <-> ZERO)"
`endif
`ifdef ( ONE <-> ONE)
"ok ( ONE <-> ONE)"
`endif
`ifdef (ZERO)
"bad"
`elsif (ZERO)
"bad"
`elsif (ONE)
"ok "
`elsif (ONE)
"bad"
`endif
// Did we end up right?
Line: `__LINE__

View File

@ -0,0 +1,44 @@
%Error: t/t_preproc_ifexpr_bad.v:12:14: `elsif with no matching `if
12 | `elsif ( ONE ) // BAD: elsif without if
| ^
%Error: t/t_preproc_ifexpr_bad.v:13:1: `endif with no matching `if
13 | `endif
| ^~~~~~
%Error: t/t_preproc_ifexpr_bad.v:15:10: Syntax error in `ifdef () expression
15 | `ifdef ( ) // BAD: Missing value
| ^
%Error: t/t_preproc_ifexpr_bad.v:18:17: Syntax error in `ifdef () expression
18 | `ifdef ( && ZERO) // BAD: Expr
| ^
%Error: t/t_preproc_ifexpr_bad.v:21:18: Syntax error in `ifdef () expression
21 | `ifdef ( ZERO && ) // BAD: Expr
| ^
%Error: t/t_preproc_ifexpr_bad.v:24:10: Syntax error in `ifdef () expression; unexpected: 'TEXT'
24 | `ifdef ( 1 ) // BAD: Constant
| ^
%Error: t/t_preproc_ifexpr_bad.v:24:12: Syntax error in `ifdef () expression
24 | `ifdef ( 1 ) // BAD: Constant
| ^
%Error: t/t_preproc_ifexpr_bad.v:27:14: Syntax error in `ifdef () expression; unexpected: 'TEXT'
27 | `ifdef ( ONE & ZERO) // BAD: Operator
| ^
%Error: t/t_preproc_ifexpr_bad.v:30:10: Syntax error in `ifdef () expression; unexpected: 'TEXT'
30 | `ifdef ( % ) // BAD: % is syntax error
| ^
%Error: t/t_preproc_ifexpr_bad.v:30:12: Syntax error in `ifdef () expression
30 | `ifdef ( % ) // BAD: % is syntax error
| ^
%Error: t/t_preproc_ifexpr_bad.v:34:1: Expecting define name. Found: ENDIF
34 | `endif
| ^~~~~~
%Error: t/t_preproc_ifexpr_bad.v:36:1: Expecting define name. Found: IFDEF
36 | `ifdef ( ONE // BAD: Missing paren
| ^~~~~~
%Error: t/t_preproc_ifexpr_bad.v:37:1: Syntax error in `ifdef () expression; unexpected: 'TEXT'
37 | `endif
| ^
%Error: t/t_preproc_ifexpr_bad.v:40:1: EOF in unterminated preprocessor expression
%Error: t/t_preproc_ifexpr_bad.v:33:2: syntax error, unexpected ')'
33 | )
| ^
%Error: Exiting due to

View File

@ -0,0 +1,19 @@
#!/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(linter => 1);
lint(
fails => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -0,0 +1,37 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`begin_keywords "1800-2023"
`define ONE
`undef ZERO
`elsif ( ONE ) // BAD: elsif without if
`endif
`ifdef ( ) // BAD: Missing value
`endif
`ifdef ( && ZERO) // BAD: Expr
`endif
`ifdef ( ZERO && ) // BAD: Expr
`endif
`ifdef ( 1 ) // BAD: Constant
`endif
`ifdef ( ONE & ZERO) // BAD: Operator
`endif
`ifdef ( % ) // BAD: % is syntax error
`endif
`ifdef ) // BAD: ) without (
`endif
`ifdef ( ONE // BAD: Missing paren
`endif

View File

@ -0,0 +1,7 @@
%Warning-PREPROCZERO: t/t_preproc_preproczero_bad.v:11:10: Preprocessor expression evaluates define with 0: 'ZERO' with value '0'
... Suggest change define 'ZERO' to non-zero value if used in preprocessor expression
11 | `ifdef ( ZERO )
| ^~~~
... For warning description see https://verilator.org/warn/PREPROCZERO?v=latest
... Use "/* verilator lint_off PREPROCZERO */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -0,0 +1,26 @@
#!/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-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.
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
scenarios(vlt => 1);
my $stdout_filename = "$Self->{obj_dir}/$Self->{name}__test.vpp";
compile(
verilator_flags2 => ['-E -P'],
verilator_make_gmake => 0,
make_top_shell => 0,
make_main => 0,
stdout_filename => $stdout_filename,
fails => 1,
expect_filename => $Self->{golden_filename},
);
ok(1);
1;

View File

@ -0,0 +1,13 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain, for
// any use, without warranty, 2024 by Wilson Snyder.
// SPDX-License-Identifier: CC0-1.0
`begin_keywords "1800-2023"
`define ZERO 0
`ifdef ( ZERO )
// ...
`endif