294 lines
11 KiB
C++
294 lines
11 KiB
C++
// -*- 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
|