| // Copyright 2017-2020 The Verible Authors. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| #include "verilog/preprocessor/verilog_preprocess.h" | |
| #include <algorithm> | |
| #include <map> | |
| #include <vector> | |
| #include "absl/status/status.h" | |
| #include "absl/strings/string_view.h" | |
| #include "common/text/macro_definition.h" | |
| #include "common/text/token_info.h" | |
| #include "common/util/container_util.h" | |
| #include "common/util/file_util.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "verilog/analysis/verilog_analyzer.h" | |
| #include "verilog/analysis/verilog_project.h" | |
| #include "verilog/parser/verilog_lexer.h" | |
| #include "verilog/parser/verilog_token_enum.h" | |
| namespace verilog { | |
| namespace { | |
| using testing::ElementsAre; | |
| using testing::Pair; | |
| using testing::StartsWith; | |
| using verible::container::FindOrNull; | |
| using verible::file::CreateDir; | |
| using verible::file::JoinPath; | |
| using verible::file::testing::ScopedTestFile; | |
| using FileOpener = VerilogPreprocess::FileOpener; | |
| class LexerTester { | |
| public: | |
| LexerTester(absl::string_view text) : lexer_(text) { | |
| for (lexer_.DoNextToken(); !lexer_.GetLastToken().isEOF(); | |
| lexer_.DoNextToken()) { | |
| lexed_sequence_.push_back(lexer_.GetLastToken()); | |
| } | |
| InitTokenStreamView(lexed_sequence_, &stream_view_); | |
| } | |
| verible::TokenStreamView GetTokenStreamView() const { return stream_view_; } | |
| private: | |
| VerilogLexer lexer_; | |
| verible::TokenSequence lexed_sequence_; | |
| verible::TokenStreamView stream_view_; | |
| }; | |
| class PreprocessorTester { | |
| public: | |
| PreprocessorTester(absl::string_view text, | |
| const VerilogPreprocess::Config& config) | |
| : analyzer_(text, "<<inline-file>>", config), | |
| status_(analyzer_.Analyze()) {} | |
| explicit PreprocessorTester(absl::string_view text) | |
| : PreprocessorTester(text, VerilogPreprocess::Config()) {} | |
| const VerilogPreprocessData& PreprocessorData() const { | |
| return analyzer_.PreprocessorData(); | |
| } | |
| const verible::TextStructureView& Data() const { return analyzer_.Data(); } | |
| const absl::Status& Status() const { return status_; } | |
| const VerilogAnalyzer& Analyzer() const { return analyzer_; } | |
| private: | |
| VerilogAnalyzer analyzer_; | |
| const absl::Status status_; | |
| }; | |
| struct FailTest { | |
| absl::string_view input; | |
| int offset; | |
| }; | |
| TEST(VerilogPreprocessTest, InvalidPreprocessorInputs) { | |
| const FailTest test_cases[] = { | |
| {"`define\n", 8}, // unterminated macro definition | |
| {"\n\n`define\n", 10}, // unterminated macro definition | |
| {"`define 789\n", 8}, // expect identifier for macro name | |
| {"`define 789 non-sense\n", 8}, // expect identifier for macro name | |
| {"`define 789 \\\nnon-sense\n", 8}, // expect identifier for macro name | |
| {"`define FOO(\n", 13}, // unterminated parameter list | |
| {"`define FOO(234\n", 12}, // invalid parameter name | |
| {"`define FOO(234)\n", 12}, // invalid parameter name | |
| {"`define FOO(aaa\n", 16}, // unterminated parameter list | |
| {"`define FOO(aaa;\n", 15}, // bad parameter separator | |
| {"`define FOO(aaa bbb\n", 16}, // bad parameter separator | |
| {"`define FOO(aaa bbb)\n", 16}, // bad parameter separator | |
| {"`define FOO(aaa+bbb)\n", 15}, // bad parameter separator | |
| {"`define FOO(aaa.zzz\n", 15}, // bad parameter separator | |
| {"`define FOO(aaa.zzz)\n", 15}, // bad parameter separator | |
| {"`define FOO(aaa,\n", 17}, // unterminated parameter list | |
| {"`define FOO(aaa,)\n", 16}, // missing parameter name | |
| {"`define FOO(,,)\n", 12}, // missing parameter name | |
| {"`define FOO(aaa, 345)\n", 17}, // invalid parameter name | |
| {"`define FOO(aaa=\n", 17}, // unterminated default parameter | |
| {"`define FOO(aaa =\n", 18}, // unterminated default parameter | |
| {"`define FOO(aaa = 9\n", 20}, // expecting ',' or ')' | |
| {"`define FOO(aaa = 9, bbb =\n", 27}, // unterminated parameter list | |
| {"`define FOO(aa = 9, bb = 2\n", 27}, // expecting ',' or ')' | |
| }; | |
| for (const auto& test_case : test_cases) { | |
| PreprocessorTester tester(test_case.input); | |
| EXPECT_FALSE(tester.Status().ok()) | |
| << "Expected preprocess to fail on invalid input: \"" << test_case.input | |
| << "\""; | |
| const auto& rejected_tokens = tester.Analyzer().GetRejectedTokens(); | |
| ASSERT_FALSE(rejected_tokens.empty()) | |
| << "on invalid input: \"" << test_case.input << "\""; | |
| const int rejected_token_offset = | |
| rejected_tokens[0].token_info.left(tester.Analyzer().Data().Contents()); | |
| EXPECT_EQ(rejected_token_offset, test_case.offset) | |
| << "on invalid input: \"" << test_case.input << "\""; | |
| } | |
| } | |
| #define EXPECT_PARSE_OK() \ | |
| do { \ | |
| EXPECT_TRUE(tester.Status().ok()) << "Unexpected analyzer failure."; \ | |
| EXPECT_TRUE(tester.PreprocessorData().errors.empty()); \ | |
| EXPECT_TRUE(tester.Analyzer().GetRejectedTokens().empty()); \ | |
| } while (false) | |
| // Verify that VerilogPreprocess works without any directives. | |
| TEST(VerilogPreprocessTest, WorksWithoutDefinitions) { | |
| absl::string_view test_cases[] = { | |
| "", | |
| "\n", | |
| "module foo;\nendmodule\n", | |
| "module foo(input x, output y);\nendmodule\n", | |
| }; | |
| for (const auto& test_case : test_cases) { | |
| PreprocessorTester tester(test_case); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_TRUE(definitions.empty()); | |
| } | |
| } | |
| TEST(VerilogPreprocessTest, OneMacroDefinitionNoParamsNoValue) { | |
| absl::string_view test_cases[] = { | |
| "`define FOOOO\n", | |
| "`define FOOOO\n", | |
| "module foo;\nendmodule\n" | |
| "`define FOOOO\n", | |
| "`define FOOOO\n" | |
| "module foo;\nendmodule\n", | |
| }; | |
| for (const auto& test_case : test_cases) { | |
| PreprocessorTester tester(test_case); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_))); | |
| auto macro = FindOrNull(definitions, "FOOOO"); | |
| ASSERT_NE(macro, nullptr); | |
| EXPECT_EQ(macro->DefinitionText().text(), ""); | |
| EXPECT_FALSE(macro->IsCallable()); | |
| EXPECT_TRUE(macro->Parameters().empty()); | |
| } | |
| } | |
| TEST(VerilogPreprocessTest, OneMacroDefinitionNoParamsSimpleValue) { | |
| PreprocessorTester tester( | |
| "module foo;\nendmodule\n" | |
| "`define FOOOO \"bar\"\n"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_))); | |
| auto macro = FindOrNull(definitions, "FOOOO"); | |
| ASSERT_NE(macro, nullptr); | |
| EXPECT_EQ(macro->DefinitionText().text(), "\"bar\""); | |
| EXPECT_FALSE(macro->IsCallable()); | |
| EXPECT_TRUE(macro->Parameters().empty()); | |
| } | |
| TEST(VerilogPreprocessTest, OneMacroDefinitionOneParamWithValue) { | |
| PreprocessorTester tester( | |
| "module foo;\nendmodule\n" | |
| "`define FOOOO(x) (x+1)\n"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_))); | |
| auto macro = FindOrNull(definitions, "FOOOO"); | |
| ASSERT_NE(macro, nullptr); | |
| EXPECT_EQ(macro->DefinitionText().text(), "(x+1)"); | |
| EXPECT_TRUE(macro->IsCallable()); | |
| const auto& params = macro->Parameters(); | |
| EXPECT_EQ(params.size(), 1); | |
| const auto& param(params[0]); | |
| EXPECT_EQ(param.name.text(), "x"); | |
| EXPECT_FALSE(param.HasDefaultText()); | |
| } | |
| TEST(VerilogPreprocessTest, OneMacroDefinitionOneParamDefaultWithValue) { | |
| PreprocessorTester tester( | |
| "module foo;\nendmodule\n" | |
| "`define FOOOO(x=22) (x+3)\n"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_THAT(definitions, ElementsAre(Pair("FOOOO", testing::_))); | |
| auto macro = FindOrNull(definitions, "FOOOO"); | |
| ASSERT_NE(macro, nullptr); | |
| EXPECT_EQ(macro->DefinitionText().text(), "(x+3)"); | |
| EXPECT_TRUE(macro->IsCallable()); | |
| const auto& params = macro->Parameters(); | |
| EXPECT_EQ(params.size(), 1); | |
| const auto& param(params[0]); | |
| EXPECT_EQ(param.name.text(), "x"); | |
| EXPECT_TRUE(param.HasDefaultText()); | |
| EXPECT_EQ(param.default_value.text(), "22"); | |
| } | |
| TEST(VerilogPreprocessTest, TwoMacroDefinitions) { | |
| PreprocessorTester tester( | |
| "`define BAAAAR(y, z) (y*z)\n" | |
| "`define FOOOO(x=22) (x+3)\n"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_THAT(definitions, ElementsAre(Pair("BAAAAR", testing::_), | |
| Pair("FOOOO", testing::_))); | |
| { | |
| auto macro = FindOrNull(definitions, "BAAAAR"); | |
| ASSERT_NE(macro, nullptr); | |
| EXPECT_TRUE(macro->IsCallable()); | |
| const auto& params = macro->Parameters(); | |
| EXPECT_EQ(params.size(), 2); | |
| } | |
| { | |
| auto macro = FindOrNull(definitions, "FOOOO"); | |
| ASSERT_NE(macro, nullptr); | |
| EXPECT_TRUE(macro->IsCallable()); | |
| const auto& params = macro->Parameters(); | |
| EXPECT_EQ(params.size(), 1); | |
| } | |
| } | |
| TEST(VerilogPreprocessTest, UndefMacro) { | |
| PreprocessorTester tester( | |
| "`define FOO 42\n" | |
| "`undef FOO"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_EQ(definitions.size(), 0); | |
| } | |
| TEST(VerilogPreprocessTest, UndefNonexistentMacro) { | |
| PreprocessorTester tester("`undef FOO"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_EQ(definitions.size(), 0); | |
| EXPECT_TRUE(tester.PreprocessorData().warnings.empty()); // not a problem. | |
| } | |
| TEST(VerilogPreprocessTest, RedefineMacroWarning) { | |
| PreprocessorTester tester( | |
| "`define FOO 1\n" | |
| "`define FOO 2\n"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_EQ(definitions.size(), 1); | |
| const auto& warnings = tester.PreprocessorData().warnings; | |
| EXPECT_EQ(warnings.size(), 1); | |
| EXPECT_EQ(warnings.front().error_message, "Re-defining macro"); | |
| } | |
| // We might have different modes later, in which we remove the define tokens | |
| // from the stream. Document the current default which registeres all the | |
| // defines, but also does not filter out the define calls. | |
| TEST(VerilogPreprocessTest, DefaultPreprocessorKeepsDefineInStream) { | |
| PreprocessorTester tester( | |
| "`define FOO\n" | |
| "`define BAR(x) (x)\n" | |
| "module x(); endmodule\n"); | |
| EXPECT_PARSE_OK(); | |
| const auto& definitions = tester.PreprocessorData().macro_definitions; | |
| EXPECT_EQ(definitions.size(), 2); | |
| // The original `define tokens are still in the stream | |
| const auto& token_stream = tester.Data().GetTokenStreamView(); | |
| const int count_defines = | |
| std::count_if(token_stream.begin(), token_stream.end(), | |
| [](verible::TokenSequence::const_iterator t) { | |
| return t->token_enum() == verilog_tokentype::PP_define; | |
| }); | |
| EXPECT_EQ(count_defines, 2); | |
| } | |
| struct BranchFailTest { | |
| absl::string_view input; | |
| int offset; | |
| absl::string_view expected_error; | |
| }; | |
| TEST(VerilogPreprocessTest, IncompleteOrUnbalancedIfdef) { | |
| const BranchFailTest test_cases[] = { | |
| {"`endif", 0, "Unmatched `endif"}, | |
| {"`else", 0, "Unmatched `else"}, | |
| {"`elsif FOO", 0, "Unmatched `elsif"}, | |
| {"`ifdef", 6, "unexpected EOF where expecting macro name"}, | |
| {"`ifdef FOO\n`endif\n`endif", 18, "Unmatched `endif"}, | |
| {"`ifdef FOO\n`endif\n`else", 18, "Unmatched `else"}, | |
| {"`ifdef FOO\n`endif\n`elsif BAR", 18, "Unmatched `elsif"}, | |
| {"`ifdef FOO\n`else\n`else", 17, "Duplicate `else"}, | |
| {"`ifdef FOO\n`else\n`elsif BAR", 17, "`elsif after `else"}, | |
| {"`ifdef FOO\n`ifdef BAR`else\n`else", 27, "Duplicate `else"}, | |
| {"`ifdef FOO\n`else\n`ifdef BAR\n`endif", 11, "Unterminated preprocess"}, | |
| {"`ifdef FOO\n`elsif BAR\n", 11, "Unterminated preprocessing"}, | |
| {"`ifdef FOO\n`elsif BAR\n`else\n", 22, "Unterminated preprocessing"}, | |
| }; | |
| for (const BranchFailTest& test : test_cases) { | |
| PreprocessorTester tester( | |
| test.input, VerilogPreprocess::Config({.filter_branches = true})); | |
| EXPECT_FALSE(tester.Status().ok()); | |
| ASSERT_GE(tester.PreprocessorData().errors.size(), 1); | |
| const auto& error = tester.PreprocessorData().errors.front(); | |
| EXPECT_THAT(error.error_message, StartsWith(test.expected_error)); | |
| const int error_token_offset = | |
| error.token_info.left(tester.Analyzer().Data().Contents()); | |
| EXPECT_EQ(error_token_offset, test.offset) << "Input: " << test.input; | |
| } | |
| } | |
| struct RawAndFiltered { | |
| absl::string_view description; | |
| absl::string_view pp_input; | |
| absl::string_view equivalent; | |
| }; | |
| TEST(VerilogPreprocess, FilterPPBranches) { | |
| const RawAndFiltered test_cases[] = { | |
| {"[** Defined macro taking ifdef branch **]", | |
| R"( | |
| `define FOO 1 | |
| `ifdef FOO | |
| module bar(); | |
| `else | |
| module quux(); | |
| `endif | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define FOO 1 | |
| module bar(); | |
| endmodule)"}, | |
| {"[** Undefined macro taking else branch **]", | |
| R"( | |
| `ifdef FOO | |
| module bar(); | |
| `else | |
| module quux(); | |
| `endif | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| module quux(); | |
| endmodule)"}, | |
| {"[** Undefined macro taking else branch. defined value `undef-ed **]", | |
| R"( | |
| `define FOO | |
| `undef FOO | |
| `ifdef FOO | |
| module bar(); | |
| `else | |
| module quux(); | |
| `endif | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define FOO | |
| `undef FOO | |
| module quux(); | |
| endmodule)"}, | |
| {"[** Negative logic: Defined macro taking ifndef-else branch **]", | |
| R"( | |
| `define FOO 1 | |
| `ifndef FOO | |
| module bar(); | |
| `else | |
| module quux(); | |
| `endif | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define FOO 1 | |
| module quux(); | |
| endmodule)"}, | |
| {"[** Negative logic: Undefined macro taking ifndef branch **]", | |
| R"( | |
| `ifndef FOO | |
| module bar(); | |
| `else | |
| module quux(); | |
| `endif | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| module bar(); | |
| endmodule)"}, | |
| {"[** Elsif: choice of first branch **]", | |
| R"( | |
| `define FOO 1 | |
| `ifdef FOO | |
| module foo(); endmodule | |
| `elsif BAR | |
| module bar(); endmodule | |
| `endif)", | |
| // ... equivalent to | |
| R"( | |
| `define FOO 1 | |
| module foo(); endmodule)"}, | |
| {"[** Elsif: choice of elsif branch **]", | |
| R"( | |
| `define BAR 1 | |
| `ifdef FOO | |
| module foo(); endmodule | |
| `elsif BAR | |
| module bar(); endmodule | |
| `endif)", | |
| // ... equivalent to | |
| R"( | |
| `define BAR 1 | |
| module bar(); endmodule)"}, | |
| {"[** Elsif: no branch chosen **]", | |
| R"( | |
| `define BAZ 1 | |
| `ifdef FOO | |
| module foo(); endmodule | |
| `elsif BAR | |
| module bar(); endmodule | |
| `endif)", | |
| // ... equivalent to | |
| R"( | |
| `define BAZ 1 | |
| )"}, | |
| {"[** Elsif: only first (`ifdef) matching branch chosen **]", | |
| R"( | |
| `define FOO 1 | |
| `define BAR 1 | |
| `define BAZ 1 | |
| `ifdef FOO | |
| module foo(); endmodule | |
| `elsif BAR | |
| module bar(); endmodule | |
| `elsif BAZ | |
| module baz(); endmodule | |
| `endif)", | |
| // ... equivalent to | |
| R"( | |
| `define FOO 1 | |
| `define BAR 1 | |
| `define BAZ 1 | |
| module foo(); endmodule | |
| )"}, | |
| {"[** Elsif: only first (`elsif) matching branch chosen **]", | |
| R"( | |
| `define BAR 1 | |
| `define BAZ 1 | |
| `define QUUX 1 | |
| `ifdef FOO | |
| module foo(); endmodule | |
| `elsif BAR | |
| module bar(); endmodule | |
| `elsif BAZ | |
| module baz(); endmodule | |
| `elsif QUUX | |
| module quux(); endmodule | |
| `endif)", | |
| // ... equivalent to | |
| R"( | |
| `define BAR 1 | |
| `define BAZ 1 | |
| `define QUUX 1 | |
| module bar(); endmodule | |
| )"}, | |
| {"[** Nested conditions **]", | |
| R"( | |
| `define BAR 1 | |
| `ifdef FOO | |
| module foo(); endmodule | |
| `ifdef BAR | |
| module foo_bar(); endmodule | |
| `else | |
| module foo_nonbar(); endmodule | |
| `endif | |
| module post_foo(); endmodule | |
| `else | |
| module nonfoo(); endmodule | |
| `ifdef BAR | |
| module nonfoo_bar(); endmodule | |
| `else | |
| module nonfoo_nonbar(); endmodule; | |
| `endif | |
| module post_nonfoo(); endmodule | |
| `endif)", | |
| // ... equivalent to | |
| R"( | |
| `define BAR 1 | |
| module nonfoo(); endmodule | |
| module nonfoo_bar(); endmodule | |
| module post_nonfoo(); endmodule)"}, | |
| {"[** Meta-def: Macro defined in branch controls another branch **]", | |
| R"( | |
| `ifdef FOO | |
| `define BAR 1 | |
| `undef FOOBAR | |
| `else | |
| `define BAZ 1 | |
| `undef FOOQUX | |
| `endif | |
| `ifdef BAR | |
| module bar(); endmodule | |
| `endif | |
| `ifdef BAZ | |
| module baz(); endmodule | |
| `endif)", | |
| // ...equivalent to | |
| R"( | |
| `define BAZ 1 | |
| `undef FOOQUX | |
| module baz(); endmodule | |
| )"}}; | |
| for (const RawAndFiltered& test : test_cases) { | |
| PreprocessorTester with_filter( | |
| test.pp_input, VerilogPreprocess::Config({.filter_branches = true})); | |
| EXPECT_TRUE(with_filter.Status().ok()) | |
| << with_filter.Status() << " " << test.description; | |
| PreprocessorTester equivalent( | |
| test.equivalent, VerilogPreprocess::Config({.filter_branches = false})); | |
| EXPECT_TRUE(equivalent.Status().ok()) | |
| << equivalent.Status() << " " << test.description; | |
| const auto& filtered_stream = with_filter.Data().GetTokenStreamView(); | |
| const auto& equivalent_stream = equivalent.Data().GetTokenStreamView(); | |
| EXPECT_GT(filtered_stream.size(), 0) << test.description; | |
| EXPECT_EQ(filtered_stream.size(), equivalent_stream.size()) | |
| << test.description; | |
| auto filtered_it = filtered_stream.begin(); | |
| auto equivalent_it = equivalent_stream.begin(); | |
| while (filtered_it != filtered_stream.end() && | |
| equivalent_it != equivalent_stream.end()) { | |
| EXPECT_EQ((*filtered_it)->text(), (*equivalent_it)->text()) | |
| << test.description; | |
| EXPECT_EQ((*filtered_it)->token_enum(), (*equivalent_it)->token_enum()) | |
| << test.description; | |
| ++filtered_it; | |
| ++equivalent_it; | |
| } | |
| } | |
| } | |
| TEST(VerilogPreprocessTest, MacroExpansion) { | |
| const RawAndFiltered test_cases[] = { | |
| {"[** Multi-tokens macros being correctly parsed **]", | |
| R"( | |
| `define ASSIGN1 =1 | |
| `define ASSIGN0 =0 | |
| module foo; | |
| wire x`ASSIGN1; | |
| wire y `ASSIGN0; | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define ASSIGN1 =1 | |
| `define ASSIGN0 =0 | |
| module foo; | |
| wire x =1; | |
| wire y =0; | |
| endmodule)"}, | |
| {"[** Multi-tokens macros not empty after undefing **]", | |
| R"( | |
| `define XWIRE wire x | |
| `define YWIRE wire y | |
| module foo; | |
| `XWIRE = 1; | |
| `YWIRE = 0; | |
| endmodule | |
| `undef XWIRE | |
| `undef YWIRE)", | |
| // ...equivalent to | |
| R"( | |
| `define XWIRE wire x | |
| `define YWIRE wire y | |
| module foo; | |
| wire x = 1; | |
| wire y = 0; | |
| endmodule | |
| `undef XWIRE | |
| `undef YWIRE)"}, | |
| {"[** Macros that contain other macro calls, redefining the inner macro " | |
| "**]", | |
| R"( | |
| `define XWIRE wire x | |
| `define YWIRE wire y | |
| `define ASSIGN1XWIRE `XWIRE = 1; | |
| `define ASSIGN0YWIRE `YWIRE = 0; | |
| module foo; | |
| `ASSIGN1XWIRE | |
| `ASSIGN0YWIRE | |
| `define XWIRE wire new_x_wire | |
| `ASSIGN1XWIRE | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define XWIRE wire x | |
| `define YWIRE wire y | |
| `define ASSIGN1XWIRE `XWIRE = 1; | |
| `define ASSIGN0YWIRE `YWIRE = 0; | |
| module foo; | |
| wire x = 1; | |
| wire y = 0; | |
| `define XWIRE wire new_x_wire | |
| wire new_x_wire = 1; | |
| endmodule)"}, | |
| {"[** Macros contatining back to back macro calls **]", | |
| R"( | |
| `define XWIRE wire x | |
| `define YWIRE wire y | |
| `define ASSIGN1 = 1 | |
| `define ASSIGN0 = 0 | |
| `define ASSIGN1XWIRE `XWIRE `ASSIGN1; | |
| `define ASSIGN0YWIRE `YWIRE `ASSIGN0; | |
| module foo; | |
| `ASSIGN1XWIRE | |
| `ASSIGN0YWIRE | |
| `define XWIRE wire new_x_wire | |
| `ASSIGN1XWIRE | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define XWIRE wire x | |
| `define YWIRE wire y | |
| `define ASSIGN1 = 1 | |
| `define ASSIGN0 = 0 | |
| `define ASSIGN1XWIRE `XWIRE `ASSIGN1; | |
| `define ASSIGN0YWIRE `YWIRE `ASSIGN0; | |
| module foo; | |
| wire x = 1; | |
| wire y = 0; | |
| `define XWIRE wire new_x_wire | |
| wire new_x_wire = 1; | |
| endmodule)"}, | |
| {"[** Macros with formal parameters, expanded with both default value, " | |
| "and actual passed value **]", | |
| R"( | |
| `define LSb(n=2) [n-1:0] | |
| module testcase_ppMacro; | |
| localparam int A = 123; | |
| wire a = A`LSb(); | |
| wire b = A`LSb(5); | |
| wire c = A[5-1:0]; | |
| endmodule)", | |
| // ...equivalent to | |
| R"( | |
| `define LSb(n=2) [n-1:0] | |
| module testcase_ppMacro; | |
| localparam int A = 123; | |
| wire a = A[2-1:0]; | |
| wire b = A[5-1:0]; | |
| wire c = A[5-1:0]; | |
| endmodule)"}, | |
| {"[** Actual parameter is another macro call **]", | |
| R"( | |
| `define FOO a | |
| `define A(n) n | |
| `define B(n=x) n ,y | |
| module m; | |
| wire `A(xyz); | |
| wire `B(`FOO); | |
| endmodule | |
| `undef B | |
| `undef A | |
| `undef FOO)", | |
| // ...equivalent to | |
| R"( | |
| `define FOO a | |
| `define A(n) n | |
| `define B(n=x) n ,y | |
| module m; | |
| wire xyz; | |
| wire a ,y; | |
| endmodule | |
| `undef B | |
| `undef A | |
| `undef FOO)"}, | |
| {"[** Multiple parameter macros (From 2017 SV-LRM) **]", | |
| R"( | |
| `define MACRO1(a=5,b="B",c) $display(a,,b,,c); | |
| `define MACRO2(a=5, b, c="C") $display(a,,b,,c); | |
| `define MACRO3(a=5, b=0, c="C") $display(a,,b,,c); | |
| module m; | |
| `MACRO1 ( , 2, 3 ) | |
| `MACRO1 ( 1 , , 3 ) | |
| `MACRO1 ( , 2, ) | |
| `MACRO2 (1, , 3) | |
| `MACRO2 (, 2, ) | |
| `MACRO2 (, 2) | |
| `MACRO3 ( 1 ) | |
| `MACRO3 () | |
| endmodule | |
| `undef MACRO)", | |
| // ...equivalent to | |
| R"( | |
| `define MACRO1(a=5,b="B",c) $display(a,,b,,c); | |
| `define MACRO2(a=5, b, c="C") $display(a,,b,,c); | |
| `define MACRO3(a=5, b=0, c="C") $display(a,,b,,c); | |
| module m; | |
| $display(5,,2,,3); | |
| $display(1,,"B",,3); | |
| $display(5,,2,,); | |
| $display(1,,,,3); | |
| $display(5,,2,,"C"); | |
| $display(5,,2,,"C"); | |
| $display(1,,0,,"C"); | |
| $display(5,,0,,"C"); | |
| endmodule | |
| `undef MACRO)"}, | |
| {"[** Nested callable macros **]", | |
| R"( | |
| `define MACRO1(n) real x=n; | |
| `define MACRO2(m) real y=m; `MACRO1(1) | |
| module foo; | |
| `MACRO1(2) | |
| `MACRO2(3) | |
| endmodule | |
| `undef MACRO1 | |
| `undef MACRO2)", | |
| // ...equivalent to | |
| R"( | |
| `define MACRO1(n) real x=n; | |
| `define MACRO2(m) real y=m; `MACRO1(1) | |
| module foo; | |
| real x=2; | |
| real y=3; real x=1; | |
| endmodule | |
| `undef MACRO1 | |
| `undef MACRO2)"} | |
| }; | |
| for (const auto& test_case : test_cases) { | |
| PreprocessorTester expanded( | |
| test_case.pp_input, VerilogPreprocess::Config({.expand_macros = true})); | |
| EXPECT_TRUE(expanded.Status().ok()) | |
| << expanded.Status() << " " << test_case.description; | |
| PreprocessorTester equivalent( | |
| test_case.equivalent, | |
| VerilogPreprocess::Config({.expand_macros = false})); | |
| EXPECT_TRUE(equivalent.Status().ok()) | |
| << equivalent.Status() << " " << test_case.description; | |
| const auto& expanded_stream = expanded.Data().GetTokenStreamView(); | |
| const auto& equivalent_stream = equivalent.Data().GetTokenStreamView(); | |
| EXPECT_GT(expanded_stream.size(), 0) << test_case.description; | |
| EXPECT_EQ(expanded_stream.size(), equivalent_stream.size()) | |
| << test_case.description; | |
| auto expanded_it = expanded_stream.begin(); | |
| auto equivalent_it = equivalent_stream.begin(); | |
| while (expanded_it != expanded_stream.end() && | |
| equivalent_it != equivalent_stream.end()) { | |
| EXPECT_EQ((*expanded_it)->text(), (*equivalent_it)->text()) | |
| << test_case.description; | |
| EXPECT_EQ((*expanded_it)->token_enum(), (*equivalent_it)->token_enum()) | |
| << test_case.description; | |
| ++expanded_it; | |
| ++equivalent_it; | |
| } | |
| } | |
| } | |
| // TODO(karimtera): This test doesn't use "PreprocessorTester", | |
| // as there isn't a way to tell "VerilogAnalyzer" about external preprocessing | |
| // info. Typically, all tests should use "PreprocessorTester". | |
| TEST(VerilogPreprocessTest, SetExternalDefines) { | |
| // Test case input tokens. | |
| const verible::TokenSequence test_case_tokens = { | |
| {verible::TokenInfo(MacroIdentifier, "`MACRO1")}, | |
| {MacroIdentifier, "`MACRO2"}}; | |
| verible::TokenStreamView test_case_stream_view; | |
| verible::InitTokenStreamView(test_case_tokens, &test_case_stream_view); | |
| VerilogPreprocess::Config test_config({.expand_macros = true}); | |
| VerilogPreprocess preprocessor(test_config); | |
| verilog::TextMacroDefinition macro1("MACRO1", "VALUE1"); | |
| verilog::TextMacroDefinition macro2("MACRO2", "VALUE2"); | |
| verilog::FileList::PreprocessingInfo preprocessing_info; | |
| preprocessing_info.defines.push_back(macro1); | |
| preprocessing_info.defines.push_back(macro2); | |
| preprocessor.setPreprocessingInfo(preprocessing_info); | |
| const auto& pp_data = preprocessor.ScanStream(test_case_stream_view); | |
| EXPECT_THAT(pp_data.preprocessed_token_stream[0]->text(), "VALUE1"); | |
| EXPECT_THAT(pp_data.preprocessed_token_stream[1]->text(), "VALUE2"); | |
| } | |
| // TODO(karimtera): This test doesn't use "PreprocessorTester", | |
| // as there isn't a way to tell "VerilogAnalyzer" about external preprocessing | |
| // info. Typically, all tests should use "PreprocessorTester". | |
| TEST(VerilogPreprocessTest, ExternalDefinesWithUndef) { | |
| // Test case input tokens. | |
| const verible::TokenSequence test_case_tokens = { | |
| {verible::TokenInfo(PP_undef, "`undef")}, | |
| {verible::TokenInfo(PP_Identifier, "MACRO1")}, | |
| {verible::TokenInfo(MacroIdentifier, "`MACRO1")}, | |
| {verible::TokenInfo(MacroIdentifier, "`MACRO2")}}; | |
| verible::TokenStreamView test_case_stream_view; | |
| verible::InitTokenStreamView(test_case_tokens, &test_case_stream_view); | |
| VerilogPreprocess::Config test_config({.expand_macros = true}); | |
| VerilogPreprocess preprocessor(test_config); | |
| verilog::TextMacroDefinition macro1("MACRO1", "VALUE1"); | |
| verilog::TextMacroDefinition macro2("MACRO2", "VALUE2"); | |
| verilog::FileList::PreprocessingInfo preprocessing_info; | |
| preprocessing_info.defines.push_back(macro1); | |
| preprocessing_info.defines.push_back(macro2); | |
| preprocessor.setPreprocessingInfo(preprocessing_info); | |
| const auto& pp_data = preprocessor.ScanStream(test_case_stream_view); | |
| const auto& errors = pp_data.errors; | |
| EXPECT_THAT(errors.size(), 1); | |
| EXPECT_THAT(errors.front().error_message, | |
| StartsWith("Error expanding macro identifier")); | |
| } | |
| TEST(VerilogPreprocessTest, IncludingFileWithAbsolutePath) { | |
| const auto tempdir = testing::TempDir(); | |
| const std::string includes_dir = JoinPath(tempdir, "includes"); | |
| constexpr absl::string_view included_content( | |
| "module included_file(); endmodule"); | |
| const absl::string_view included_filename = "included_file.sv"; | |
| const std::string included_absolute_path = | |
| JoinPath(includes_dir, included_filename); | |
| const std::string src_content = absl::StrCat( | |
| "`include \"", included_absolute_path, "\"\nmodule src(); endmodule\n"); | |
| const std::string equivalent_content = | |
| "module included_file(); endmodule\nmodule src(); endmodule\n"; | |
| FileOpener file_opener = | |
| [included_absolute_path, included_content]( | |
| absl::string_view filename) -> absl::StatusOr<absl::string_view> { | |
| if (filename == included_absolute_path) return included_content; | |
| return absl::NotFoundError(absl::StrCat(filename, " is not found")); | |
| }; | |
| VerilogPreprocess tester(VerilogPreprocess::Config({.include_files = true}), | |
| file_opener); | |
| VerilogPreprocess equivalent( | |
| VerilogPreprocess::Config({.include_files = true})); | |
| LexerTester src_lexer(src_content); | |
| LexerTester equivalent_lexer(equivalent_content); | |
| const auto& tester_pp_data = | |
| tester.ScanStream(src_lexer.GetTokenStreamView()); | |
| const auto& equivalent_pp_data = | |
| equivalent.ScanStream(equivalent_lexer.GetTokenStreamView()); | |
| EXPECT_TRUE(tester_pp_data.errors.empty()); | |
| EXPECT_TRUE(equivalent_pp_data.errors.empty()); | |
| const auto& tester_stream = tester_pp_data.preprocessed_token_stream; | |
| const auto& equivalent_stream = equivalent_pp_data.preprocessed_token_stream; | |
| EXPECT_FALSE(tester_stream.empty()); | |
| EXPECT_EQ(tester_stream.size(), equivalent_stream.size()); | |
| auto tester_it = tester_stream.begin(); | |
| auto equivalent_it = equivalent_stream.begin(); | |
| while (tester_it != tester_stream.end() && | |
| equivalent_it != equivalent_stream.end()) { | |
| EXPECT_EQ((*tester_it)->text(), (*equivalent_it)->text()); | |
| EXPECT_EQ((*tester_it)->token_enum(), (*equivalent_it)->token_enum()); | |
| ++tester_it; | |
| ++equivalent_it; | |
| } | |
| } | |
| TEST(VerilogPreprocessTest, IncludingFileWithRelativePath) { | |
| const auto tempdir = testing::TempDir(); | |
| const std::string includes_dir = JoinPath(tempdir, "includes"); | |
| EXPECT_TRUE(CreateDir(includes_dir).ok()); | |
| constexpr absl::string_view included_content( | |
| "module included_file(); endmodule"); | |
| const absl::string_view included_filename = "included_file.sv"; | |
| const std::string included_absolute_path = | |
| JoinPath(includes_dir, included_filename); | |
| const ScopedTestFile tf(includes_dir, included_content, included_filename); | |
| const std::string src_content = absl::StrCat("`include \"", included_filename, | |
| "\"\nmodule src(); endmodule\n"); | |
| const std::string equivalent_content = | |
| "module included_file(); endmodule\nmodule src(); endmodule\n"; | |
| // TODO(karimtera): allow including files with absolute paths. | |
| // This is a hacky solution for now. | |
| verilog::VerilogProject project(".", {"/", includes_dir}); | |
| FileOpener file_opener = | |
| [&project]( | |
| absl::string_view filename) -> absl::StatusOr<absl::string_view> { | |
| auto result = project.OpenIncludedFile(filename); | |
| if (!result.status().ok()) return result.status(); | |
| return (*result)->GetContent(); | |
| }; | |
| VerilogPreprocess tester(VerilogPreprocess::Config({.include_files = true}), | |
| file_opener); | |
| VerilogPreprocess equivalent( | |
| VerilogPreprocess::Config({.include_files = true})); | |
| LexerTester src_lexer(src_content); | |
| LexerTester equivalent_lexer(equivalent_content); | |
| const auto& tester_pp_data = | |
| tester.ScanStream(src_lexer.GetTokenStreamView()); | |
| const auto& equivalent_pp_data = | |
| equivalent.ScanStream(equivalent_lexer.GetTokenStreamView()); | |
| EXPECT_TRUE(tester_pp_data.errors.empty()); | |
| EXPECT_TRUE(equivalent_pp_data.errors.empty()); | |
| const auto& tester_stream = tester_pp_data.preprocessed_token_stream; | |
| const auto& equivalent_stream = equivalent_pp_data.preprocessed_token_stream; | |
| EXPECT_FALSE(tester_stream.empty()); | |
| EXPECT_EQ(tester_stream.size(), equivalent_stream.size()); | |
| auto tester_it = tester_stream.begin(); | |
| auto equivalent_it = equivalent_stream.begin(); | |
| while (tester_it != tester_stream.end() && | |
| equivalent_it != equivalent_stream.end()) { | |
| EXPECT_EQ((*tester_it)->text(), (*equivalent_it)->text()); | |
| EXPECT_EQ((*tester_it)->token_enum(), (*equivalent_it)->token_enum()); | |
| ++tester_it; | |
| ++equivalent_it; | |
| } | |
| } | |
| TEST(VerilogPreprocessTest, | |
| IncludingFileWithRelativePathWithoutPreprocessingInfo) { | |
| const auto tempdir = testing::TempDir(); | |
| const std::string includes_dir = JoinPath(tempdir, "includes"); | |
| EXPECT_TRUE(CreateDir(includes_dir).ok()); | |
| constexpr absl::string_view included_content( | |
| "module included_file(); endmodule\n"); | |
| const absl::string_view included_filename = "included_file.sv"; | |
| const std::string included_absolute_path = | |
| JoinPath(includes_dir, included_filename); | |
| const ScopedTestFile tf(includes_dir, included_content, included_filename); | |
| const std::string src_content = absl::StrCat("`include \"", included_filename, | |
| "\"\nmodule src(); endmodule\n"); | |
| // TODO(karimtera): allow including files with absolute paths. | |
| // This is a hacky solution for now. | |
| verilog::VerilogProject project(".", {"/"}); | |
| FileOpener file_opener = | |
| [&project]( | |
| absl::string_view filename) -> absl::StatusOr<absl::string_view> { | |
| auto result = project.OpenIncludedFile(filename); | |
| if (!result.status().ok()) return result.status(); | |
| return (*result)->GetContent(); | |
| }; | |
| VerilogPreprocess tester(VerilogPreprocess::Config({.include_files = true}), | |
| file_opener); | |
| LexerTester src_lexer(src_content); | |
| const auto& tester_pp_data = | |
| tester.ScanStream(src_lexer.GetTokenStreamView()); | |
| EXPECT_EQ(tester_pp_data.errors.size(), 1); | |
| const auto& error = tester_pp_data.errors.front(); | |
| EXPECT_THAT(error.error_message, StartsWith("Unable to find")); | |
| } | |
| } // namespace | |
| } // namespace verilog |