| /* | |
| * yosys -- Yosys Open SYnthesis Suite | |
| * | |
| * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at> | |
| * | |
| * Permission to use, copy, modify, and/or distribute this software for any | |
| * purpose with or without fee is hereby granted, provided that the above | |
| * copyright notice and this permission notice appear in all copies. | |
| * | |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
| * | |
| * --- | |
| * | |
| * The Verilog frontend. | |
| * | |
| * This frontend is using the AST frontend library (see frontends/ast/). | |
| * Thus this frontend does not generate RTLIL code directly but creates an | |
| * AST directly from the Verilog parse tree and then passes this AST to | |
| * the AST frontend library. | |
| * | |
| * --- | |
| * | |
| * Ad-hoc implementation of a Verilog preprocessor. The directives `define, | |
| * `include, `ifdef, `ifndef, `else and `endif are handled here. All other | |
| * directives are handled by the lexer (see lexer.l). | |
| * | |
| */ | |
| #include "verilog_frontend.h" | |
| #include "kernel/log.h" | |
| #include <stdarg.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| YOSYS_NAMESPACE_BEGIN | |
| using namespace VERILOG_FRONTEND; | |
| static std::list<std::string> output_code; | |
| static std::list<std::string> input_buffer; | |
| static size_t input_buffer_charp; | |
| static void return_char(char ch) | |
| { | |
| if (input_buffer_charp == 0) | |
| input_buffer.push_front(std::string() + ch); | |
| else | |
| input_buffer.front()[--input_buffer_charp] = ch; | |
| } | |
| static void insert_input(std::string str) | |
| { | |
| if (input_buffer_charp != 0) { | |
| input_buffer.front() = input_buffer.front().substr(input_buffer_charp); | |
| input_buffer_charp = 0; | |
| } | |
| input_buffer.push_front(str); | |
| } | |
| static char next_char() | |
| { | |
| if (input_buffer.empty()) | |
| return 0; | |
| log_assert(input_buffer_charp <= input_buffer.front().size()); | |
| if (input_buffer_charp == input_buffer.front().size()) { | |
| input_buffer_charp = 0; | |
| input_buffer.pop_front(); | |
| return next_char(); | |
| } | |
| char ch = input_buffer.front()[input_buffer_charp++]; | |
| return ch == '\r' ? next_char() : ch; | |
| } | |
| static std::string skip_spaces() | |
| { | |
| std::string spaces; | |
| while (1) { | |
| char ch = next_char(); | |
| if (ch == 0) | |
| break; | |
| if (ch != ' ' && ch != '\t') { | |
| return_char(ch); | |
| break; | |
| } | |
| spaces += ch; | |
| } | |
| return spaces; | |
| } | |
| static std::string next_token(bool pass_newline = false) | |
| { | |
| std::string token; | |
| char ch = next_char(); | |
| if (ch == 0) | |
| return token; | |
| token += ch; | |
| if (ch == '\n') { | |
| if (pass_newline) { | |
| output_code.push_back(token); | |
| return ""; | |
| } | |
| return token; | |
| } | |
| if (ch == ' ' || ch == '\t') | |
| { | |
| while ((ch = next_char()) != 0) { | |
| if (ch != ' ' && ch != '\t') { | |
| return_char(ch); | |
| break; | |
| } | |
| token += ch; | |
| } | |
| } | |
| else if (ch == '"') | |
| { | |
| while ((ch = next_char()) != 0) { | |
| token += ch; | |
| if (ch == '"') | |
| break; | |
| if (ch == '\\') { | |
| if ((ch = next_char()) != 0) | |
| token += ch; | |
| } | |
| } | |
| if (token == "\"\"" && (ch = next_char()) != 0) { | |
| if (ch == '"') | |
| token += ch; | |
| else | |
| return_char(ch); | |
| } | |
| } | |
| else if (ch == '/') | |
| { | |
| if ((ch = next_char()) != 0) { | |
| if (ch == '/') { | |
| token += '*'; | |
| char last_ch = 0; | |
| while ((ch = next_char()) != 0) { | |
| if (ch == '\n') { | |
| return_char(ch); | |
| break; | |
| } | |
| if (last_ch != '*' || ch != '/') { | |
| token += ch; | |
| last_ch = ch; | |
| } | |
| } | |
| token += " */"; | |
| } | |
| else if (ch == '*') { | |
| token += '*'; | |
| int newline_count = 0; | |
| char last_ch = 0; | |
| while ((ch = next_char()) != 0) { | |
| if (ch == '\n') { | |
| newline_count++; | |
| token += ' '; | |
| } else | |
| token += ch; | |
| if (last_ch == '*' && ch == '/') | |
| break; | |
| last_ch = ch; | |
| } | |
| while (newline_count-- > 0) | |
| return_char('\n'); | |
| } | |
| else | |
| return_char(ch); | |
| } | |
| } | |
| else | |
| { | |
| const char *ok = "abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ$0123456789"; | |
| if (ch == '`' || strchr(ok, ch) != NULL) | |
| { | |
| char first = ch; | |
| ch = next_char(); | |
| if (first == '`' && (ch == '"' || ch == '`')) { | |
| token += ch; | |
| } else do { | |
| if (strchr(ok, ch) == NULL) { | |
| return_char(ch); | |
| break; | |
| } | |
| token += ch; | |
| } while ((ch = next_char()) != 0); | |
| } | |
| } | |
| return token; | |
| } | |
| static void input_file(std::istream &f, std::string filename) | |
| { | |
| char buffer[513]; | |
| int rc; | |
| insert_input(""); | |
| auto it = input_buffer.begin(); | |
| input_buffer.insert(it, "`file_push \"" + filename + "\"\n"); | |
| while ((rc = readsome(f, buffer, sizeof(buffer)-1)) > 0) { | |
| buffer[rc] = 0; | |
| input_buffer.insert(it, buffer); | |
| } | |
| input_buffer.insert(it, "\n`file_pop\n"); | |
| } | |
| static bool try_expand_macro(std::set<std::string> &defines_with_args, | |
| std::map<std::string, std::string> &defines_map, | |
| std::string &tok | |
| ) | |
| { | |
| if (tok == "`\"") { | |
| std::string literal("\""); | |
| // Expand string literal | |
| while (!input_buffer.empty()) { | |
| std::string ntok = next_token(); | |
| if (ntok == "`\"") { | |
| insert_input(literal+"\""); | |
| return true; | |
| } else if (!try_expand_macro(defines_with_args, defines_map, ntok)) { | |
| literal += ntok; | |
| } | |
| } | |
| return false; // error - unmatched `" | |
| } else if (tok.size() > 1 && tok[0] == '`' && defines_map.count(tok.substr(1)) > 0) { | |
| std::string name = tok.substr(1); | |
| // printf("expand: >>%s<< -> >>%s<<\n", name.c_str(), defines_map[name].c_str()); | |
| std::string skipped_spaces = skip_spaces(); | |
| tok = next_token(false); | |
| if (tok == "(" && defines_with_args.count(name) > 0) { | |
| int level = 1; | |
| std::vector<std::string> args; | |
| args.push_back(std::string()); | |
| while (1) | |
| { | |
| skip_spaces(); | |
| tok = next_token(true); | |
| if (tok == ")" || tok == "}" || tok == "]") | |
| level--; | |
| if (level == 0) | |
| break; | |
| if (level == 1 && tok == ",") | |
| args.push_back(std::string()); | |
| else | |
| args.back() += tok; | |
| if (tok == "(" || tok == "{" || tok == "[") | |
| level++; | |
| } | |
| for (int i = 0; i < GetSize(args); i++) | |
| defines_map[stringf("macro_%s_arg%d", name.c_str(), i+1)] = args[i]; | |
| } else { | |
| insert_input(tok); | |
| insert_input(skipped_spaces); | |
| } | |
| insert_input(defines_map[name]); | |
| return true; | |
| } else if (tok == "``") { | |
| // Swallow `` in macro expansion | |
| return true; | |
| } else return false; | |
| } | |
| std::string frontend_verilog_preproc(std::istream &f, std::string filename, const std::map<std::string, std::string> &pre_defines_map, | |
| dict<std::string, std::pair<std::string, bool>> &global_defines_cache, const std::list<std::string> &include_dirs) | |
| { | |
| std::set<std::string> defines_with_args; | |
| std::map<std::string, std::string> defines_map(pre_defines_map); | |
| std::vector<std::string> filename_stack; | |
| int ifdef_fail_level = 0; | |
| bool in_elseif = false; | |
| output_code.clear(); | |
| input_buffer.clear(); | |
| input_buffer_charp = 0; | |
| input_file(f, filename); | |
| defines_map["YOSYS"] = "1"; | |
| defines_map[formal_mode ? "FORMAL" : "SYNTHESIS"] = "1"; | |
| for (auto &it : pre_defines_map) | |
| defines_map[it.first] = it.second; | |
| for (auto &it : global_defines_cache) { | |
| if (it.second.second) | |
| defines_with_args.insert(it.first); | |
| defines_map[it.first] = it.second.first; | |
| } | |
| while (!input_buffer.empty()) | |
| { | |
| std::string tok = next_token(); | |
| // printf("token: >>%s<<\n", tok != "\n" ? tok.c_str() : "NEWLINE"); | |
| if (tok == "`endif") { | |
| if (ifdef_fail_level > 0) | |
| ifdef_fail_level--; | |
| if (ifdef_fail_level == 0) | |
| in_elseif = false; | |
| continue; | |
| } | |
| if (tok == "`else") { | |
| if (ifdef_fail_level == 0) | |
| ifdef_fail_level = 1; | |
| else if (ifdef_fail_level == 1 && !in_elseif) | |
| ifdef_fail_level = 0; | |
| continue; | |
| } | |
| if (tok == "`elsif") { | |
| skip_spaces(); | |
| std::string name = next_token(true); | |
| if (ifdef_fail_level == 0) | |
| ifdef_fail_level = 1, in_elseif = true; | |
| else if (ifdef_fail_level == 1 && defines_map.count(name) != 0) | |
| ifdef_fail_level = 0, in_elseif = true; | |
| continue; | |
| } | |
| if (tok == "`ifdef") { | |
| skip_spaces(); | |
| std::string name = next_token(true); | |
| if (ifdef_fail_level > 0 || defines_map.count(name) == 0) | |
| ifdef_fail_level++; | |
| continue; | |
| } | |
| if (tok == "`ifndef") { | |
| skip_spaces(); | |
| std::string name = next_token(true); | |
| if (ifdef_fail_level > 0 || defines_map.count(name) != 0) | |
| ifdef_fail_level++; | |
| continue; | |
| } | |
| if (ifdef_fail_level > 0) { | |
| if (tok == "\n") | |
| output_code.push_back(tok); | |
| continue; | |
| } | |
| if (tok == "`include") { | |
| skip_spaces(); | |
| std::string fn = next_token(true); | |
| while (try_expand_macro(defines_with_args, defines_map, fn)) { | |
| fn = next_token(); | |
| } | |
| while (1) { | |
| size_t pos = fn.find('"'); | |
| if (pos == std::string::npos) | |
| break; | |
| if (pos == 0) | |
| fn = fn.substr(1); | |
| else | |
| fn = fn.substr(0, pos) + fn.substr(pos+1); | |
| } | |
| std::ifstream ff; | |
| ff.clear(); | |
| std::string fixed_fn = fn; | |
| ff.open(fixed_fn.c_str()); | |
| bool filename_path_sep_found; | |
| bool fn_relative; | |
| #ifdef _WIN32 | |
| // Both forward and backslash are acceptable separators on Windows. | |
| filename_path_sep_found = (filename.find_first_of("/\\") != std::string::npos); | |
| // Easier just to invert the check for an absolute path (e.g. C:\ or C:/) | |
| fn_relative = !(fn[1] == ':' && (fn[2] == '/' || fn[2] == '\\')); | |
| #else | |
| filename_path_sep_found = (filename.find('/') != std::string::npos); | |
| fn_relative = (fn[0] != '/'); | |
| #endif | |
| if (ff.fail() && fn.size() > 0 && fn_relative && filename_path_sep_found) { | |
| // if the include file was not found, it is not given with an absolute path, and the | |
| // currently read file is given with a path, then try again relative to its directory | |
| ff.clear(); | |
| #ifdef _WIN32 | |
| fixed_fn = filename.substr(0, filename.find_last_of("/\\")+1) + fn; | |
| #else | |
| fixed_fn = filename.substr(0, filename.rfind('/')+1) + fn; | |
| #endif | |
| ff.open(fixed_fn); | |
| } | |
| if (ff.fail() && fn.size() > 0 && fn_relative) { | |
| // if the include file was not found and it is not given with an absolute path, then | |
| // search it in the include path | |
| for (auto incdir : include_dirs) { | |
| ff.clear(); | |
| fixed_fn = incdir + '/' + fn; | |
| ff.open(fixed_fn); | |
| if (!ff.fail()) break; | |
| } | |
| } | |
| if (ff.fail()) { | |
| output_code.push_back("`file_notfound " + fn); | |
| } else { | |
| input_file(ff, fixed_fn); | |
| yosys_input_files.insert(fixed_fn); | |
| } | |
| continue; | |
| } | |
| if (tok == "`file_push") { | |
| skip_spaces(); | |
| std::string fn = next_token(true); | |
| if (!fn.empty() && fn.front() == '"' && fn.back() == '"') | |
| fn = fn.substr(1, fn.size()-2); | |
| output_code.push_back(tok + " \"" + fn + "\""); | |
| filename_stack.push_back(filename); | |
| filename = fn; | |
| continue; | |
| } | |
| if (tok == "`file_pop") { | |
| output_code.push_back(tok); | |
| filename = filename_stack.back(); | |
| filename_stack.pop_back(); | |
| continue; | |
| } | |
| if (tok == "`define") { | |
| std::string name, value; | |
| std::map<std::string, int> args; | |
| skip_spaces(); | |
| name = next_token(true); | |
| bool here_doc_mode = false; | |
| int newline_count = 0; | |
| int state = 0; | |
| if (skip_spaces() != "") | |
| state = 3; | |
| while (!tok.empty()) { | |
| tok = next_token(); | |
| if (tok == "\"\"\"") { | |
| here_doc_mode = !here_doc_mode; | |
| continue; | |
| } | |
| if (state == 0 && tok == "(") { | |
| state = 1; | |
| skip_spaces(); | |
| } else | |
| if (state == 1) { | |
| if (tok == ")") | |
| state = 2; | |
| else if (tok != ",") { | |
| int arg_idx = args.size()+1; | |
| args[tok] = arg_idx; | |
| } | |
| skip_spaces(); | |
| } else { | |
| if (state != 2) | |
| state = 3; | |
| if (tok == "\n") { | |
| if (here_doc_mode) { | |
| value += " "; | |
| newline_count++; | |
| } else { | |
| return_char('\n'); | |
| break; | |
| } | |
| } else | |
| if (tok == "\\") { | |
| char ch = next_char(); | |
| if (ch == '\n') { | |
| value += " "; | |
| newline_count++; | |
| } else { | |
| value += std::string("\\"); | |
| return_char(ch); | |
| } | |
| } else | |
| if (args.count(tok) > 0) | |
| value += stringf("`macro_%s_arg%d", name.c_str(), args.at(tok)); | |
| else | |
| value += tok; | |
| } | |
| } | |
| while (newline_count-- > 0) | |
| return_char('\n'); | |
| if (strchr("abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ$0123456789", name[0])) { | |
| // printf("define: >>%s<< -> >>%s<<\n", name.c_str(), value.c_str()); | |
| defines_map[name] = value; | |
| if (state == 2) | |
| defines_with_args.insert(name); | |
| else | |
| defines_with_args.erase(name); | |
| global_defines_cache[name] = std::pair<std::string, bool>(value, state == 2); | |
| } else { | |
| log_file_error(filename, 0, "Invalid name for macro definition: >>%s<<.\n", name.c_str()); | |
| } | |
| continue; | |
| } | |
| if (tok == "`undef") { | |
| std::string name; | |
| skip_spaces(); | |
| name = next_token(true); | |
| // printf("undef: >>%s<<\n", name.c_str()); | |
| defines_map.erase(name); | |
| defines_with_args.erase(name); | |
| global_defines_cache.erase(name); | |
| continue; | |
| } | |
| if (tok == "`timescale") { | |
| skip_spaces(); | |
| while (!tok.empty() && tok != "\n") | |
| tok = next_token(true); | |
| if (tok == "\n") | |
| return_char('\n'); | |
| continue; | |
| } | |
| if (tok == "`resetall") { | |
| defines_map.clear(); | |
| defines_with_args.clear(); | |
| global_defines_cache.clear(); | |
| continue; | |
| } | |
| if (try_expand_macro(defines_with_args, defines_map, tok)) | |
| continue; | |
| output_code.push_back(tok); | |
| } | |
| std::string output; | |
| for (auto &str : output_code) | |
| output += str; | |
| output_code.clear(); | |
| input_buffer.clear(); | |
| input_buffer_charp = 0; | |
| return output; | |
| } | |
| YOSYS_NAMESPACE_END |