OpenFPGA/yosys/frontends/verilog/preproc.cc

548 lines
13 KiB
C++

/*
* 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');
// 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);
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