/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Clifford Wolf * * 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 #include #include YOSYS_NAMESPACE_BEGIN using namespace VERILOG_FRONTEND; static std::list output_code; static std::list 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 &defines_with_args, std::map &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 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 &pre_defines_map, dict> &global_defines_cache, const std::list &include_dirs) { std::set defines_with_args; std::map defines_map(pre_defines_map); std::vector 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 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(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