diff --git a/Changes b/Changes index cc43a2354..fd5001c94 100644 --- a/Changes +++ b/Changes @@ -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:** diff --git a/docs/guide/languages.rst b/docs/guide/languages.rst index 8a6c2362e..9f32a8a03 100644 --- a/docs/guide/languages.rst +++ b/docs/guide/languages.rst @@ -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 ------------------- diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index e38b8bfe5..57d9fa012 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -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 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 458e87631..0848e263b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -132,6 +132,7 @@ set(HEADERS V3PartitionGraph.h V3PchAstMT.h V3PchAstNoMT.h + V3PreExpr.h V3PreLex.h V3PreProc.h V3PreShell.h diff --git a/src/V3Error.h b/src/V3Error.h index 4873bd193..d81261484 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -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 { diff --git a/src/V3PreExpr.h b/src/V3PreExpr.h new file mode 100644 index 000000000..18d69cb12 --- /dev/null +++ b/src/V3PreExpr.h @@ -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 + +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 m_inputs; // Input stack + std::deque m_values; // Value stack + std::deque 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 diff --git a/src/V3PreLex.h b/src/V3PreLex.h index 64e49ffcc..7d11a9e3f 100644 --- a/src/V3PreLex.h +++ b/src/V3PreLex.h @@ -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); diff --git a/src/V3PreLex.l b/src/V3PreLex.l index 63e5989c2..fa605d72a 100644 --- a/src/V3PreLex.l +++ b/src/V3PreLex.l @@ -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] <> { FL_FWDC; yyerrorf("EOF in '/* ... */' block comment\n"); yyleng=0; return VP_EOF_ERROR; } + /* Preprocessor expression */ +<> { FL_FWDC; linenoInc(); yyerrorf("EOF in unterminated preprocessor expression"); + yyleng = 0; return VP_EOF_ERROR; } +"/*" { yy_push_state(CMTMODE); yymore(); } +"//"[^\n\r]* { FL_FWDC; return VP_COMMENT;} +"(" { FL_FWDC; return VP_TEXT; } /* V3PreProc will push another EXPR state to stack */ +")" { FL_FWDC; yy_pop_state(); return VP_TEXT; } +"!" { FL_FWDC; return VP_TEXT; } +"&&" { FL_FWDC; return VP_TEXT; } +"||" { FL_FWDC; return VP_TEXT; } +"->" { FL_FWDC; return VP_TEXT; } +"<->" { FL_FWDC; return VP_TEXT; } +{symb} { FL_FWDC; return VP_SYMBOL; } +{crnl} { FL_FWDC; linenoInc(); yytext=(char*)"\n"; yyleng=1; return VP_WHITE; } +{wsn}+ { FL_FWDC; return VP_WHITE; } +. { FL_FWDC; return VP_TEXT; } + /* Define arguments (use of a define) */ "/*" { yy_push_state(CMTMODE); yymore(); } "//"[^\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); diff --git a/src/V3PreProc.cpp b/src/V3PreProc.cpp index a12d88e9f..0c7d68a92 100644 --- a/src/V3PreProc.cpp +++ b/src/V3PreProc.cpp @@ -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 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; diff --git a/src/V3PreProc.h b/src/V3PreProc.h index e57be8787..f623c0237 100644 --- a/src/V3PreProc.h +++ b/src/V3PreProc.h @@ -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 diff --git a/src/V3PreShell.cpp b/src/V3PreShell.cpp index fba2ea6ed..6a1372b59 100644 --- a/src/V3PreShell.cpp +++ b/src/V3PreShell.cpp @@ -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(); } diff --git a/src/V3PreShell.h b/src/V3PreShell.h index 4ebb97077..f34b65801 100644 --- a/src/V3PreShell.h +++ b/src/V3PreShell.h @@ -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 diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 1388a27b8..c3c7cb289 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -678,6 +678,7 @@ static void verilate(const string& argString) { V3ScoreboardBase::selfTest(); V3Partition::selfTest(); V3Partition::selfTestNormalizeCosts(); + V3PreShell::selfTest(); V3Broken::selfTest(); } V3ThreadPool::selfTest(); diff --git a/test_regress/t/t_preproc_ifexpr.out b/test_regress/t/t_preproc_ifexpr.out new file mode 100644 index 000000000..152f0a061 --- /dev/null +++ b/test_regress/t/t_preproc_ifexpr.out @@ -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 diff --git a/test_regress/t/t_preproc_ifexpr.pl b/test_regress/t/t_preproc_ifexpr.pl new file mode 100755 index 000000000..37bb50fe5 --- /dev/null +++ b/test_regress/t/t_preproc_ifexpr.pl @@ -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; diff --git a/test_regress/t/t_preproc_ifexpr.v b/test_regress/t/t_preproc_ifexpr.v new file mode 100644 index 000000000..d56963d40 --- /dev/null +++ b/test_regress/t/t_preproc_ifexpr.v @@ -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__ diff --git a/test_regress/t/t_preproc_ifexpr_bad.out b/test_regress/t/t_preproc_ifexpr_bad.out new file mode 100644 index 000000000..8192b636c --- /dev/null +++ b/test_regress/t/t_preproc_ifexpr_bad.out @@ -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 diff --git a/test_regress/t/t_preproc_ifexpr_bad.pl b/test_regress/t/t_preproc_ifexpr_bad.pl new file mode 100755 index 000000000..a60503a1f --- /dev/null +++ b/test_regress/t/t_preproc_ifexpr_bad.pl @@ -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; diff --git a/test_regress/t/t_preproc_ifexpr_bad.v b/test_regress/t/t_preproc_ifexpr_bad.v new file mode 100644 index 000000000..208a78528 --- /dev/null +++ b/test_regress/t/t_preproc_ifexpr_bad.v @@ -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 diff --git a/test_regress/t/t_preproc_preproczero_bad.out b/test_regress/t/t_preproc_preproczero_bad.out new file mode 100644 index 000000000..029f34613 --- /dev/null +++ b/test_regress/t/t_preproc_preproczero_bad.out @@ -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 diff --git a/test_regress/t/t_preproc_preproczero_bad.pl b/test_regress/t/t_preproc_preproczero_bad.pl new file mode 100755 index 000000000..abcaaca95 --- /dev/null +++ b/test_regress/t/t_preproc_preproczero_bad.pl @@ -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; diff --git a/test_regress/t/t_preproc_preproczero_bad.v b/test_regress/t/t_preproc_preproczero_bad.v new file mode 100644 index 000000000..b6af66740 --- /dev/null +++ b/test_regress/t/t_preproc_preproczero_bad.v @@ -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