/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Claire Xenia 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 verilog_lexer.l). * */ #include "preproc.h" #include "verilog_frontend.h" #include "kernel/log.h" #include #include #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 == '\\') { while ((ch = next_char()) != 0) { if (ch < 33 || ch > 126) { return_char(ch); break; } token += 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; } struct macro_arg_t { macro_arg_t(const std::string &name_, const char *default_value_) : name(name_), has_default(default_value_ != nullptr), default_value(default_value_ ? default_value_ : "") {} std::string name; bool has_default; std::string default_value; }; static bool all_white(const std::string &str) { for (char c : str) if (!isspace(c)) return false; return true; } struct arg_map_t { arg_map_t() {} void add_arg(const std::string &name, const char *default_value) { if (find(name)) { log_error("Duplicate macro arguments with name `%s'.\n", name.c_str()); } name_to_pos[name] = args.size(); args.push_back(macro_arg_t(name, default_value)); } // Find an argument by name; return nullptr if it doesn't exist. If pos is not null, write // the argument's position to it on success. const macro_arg_t *find(const std::string &name, int *pos = nullptr) const { auto it = name_to_pos.find(name); if (it == name_to_pos.end()) return nullptr; if (pos) *pos = it->second; return &args[it->second]; } // Construct the name for the local macro definition we use for the given argument // (something like macro_foobar_arg2). This doesn't include the leading backtick. static std::string str_token(const std::string ¯o_name, int pos) { return stringf("macro_%s_arg%d", macro_name.c_str(), pos); } // Return definitions for the macro arguments (so that substituting in the macro body and // then performing macro expansion will do argument substitution properly). std::vector> get_vals(const std::string ¯o_name, const std::vector &arg_vals) const { std::vector> ret; for (int i = 0; i < GetSize(args); ++ i) { // The SystemVerilog rules are: // // - If the call site specifies an argument and it's not whitespace, use // it. // // - Otherwise, if the argument has a default value, use it. // // - Otherwise, if the call site specified whitespace, use that. // // - Otherwise, error. const std::string *dflt = nullptr; if (args[i].has_default) dflt = &args[i].default_value; const std::string *given = nullptr; if (i < GetSize(arg_vals)) given = &arg_vals[i]; const std::string *val = nullptr; if (given && (! (dflt && all_white(*given)))) val = given; else if (dflt) val = dflt; else if (given) val = given; else log_error("Cannot expand macro `%s by giving only %d argument%s " "(argument %d has no default).\n", macro_name.c_str(), GetSize(arg_vals), (GetSize(arg_vals) == 1 ? "" : "s"), i + 1); assert(val); ret.push_back(std::make_pair(str_token(macro_name, i), * val)); } return ret; } std::vector args; std::map name_to_pos; }; struct define_body_t { define_body_t(const std::string &body, const arg_map_t *args = nullptr) : body(body), has_args(args != nullptr), args(args ? *args : arg_map_t()) {} std::string body; bool has_args; arg_map_t args; }; define_map_t::define_map_t() { add("YOSYS", "1"); } // We must define this destructor here (rather than relying on the default), because we need to // define it somewhere we've got a complete definition of define_body_t. define_map_t::~define_map_t() {} void define_map_t::add(const std::string &name, const std::string &txt, const arg_map_t *args) { defines[name] = std::unique_ptr(new define_body_t(txt, args)); } void define_map_t::add(const std::string &name, const define_body_t &body) { defines[name] = std::unique_ptr(new define_body_t(body)); } void define_map_t::merge(const define_map_t &map) { for (const auto &pr : map.defines) { // These contortions are so that we take a copy of each definition body in // map.defines. defines[pr.first] = std::unique_ptr(new define_body_t(*pr.second)); } } const define_body_t *define_map_t::find(const std::string &name) const { auto it = defines.find(name); return (it == defines.end()) ? nullptr : it->second.get(); } void define_map_t::erase(const std::string &name) { defines.erase(name); } void define_map_t::clear() { defines.clear(); } void define_map_t::log() const { for (auto &it : defines) { const std::string &name = it.first; const define_body_t &body = *it.second; Yosys::log("`define %s%s %s\n", name.c_str(), body.has_args ? "()" : "", body.body.c_str()); } } 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"); } // Read tokens to get one argument (either a macro argument at a callsite or a default argument in a // macro definition). Writes the argument to dest. Returns true if we finished with ')' (the end of // the argument list); false if we finished with ','. static bool read_argument(std::string &dest) { skip_spaces(); std::vector openers; for (;;) { std::string tok = next_token(true); if (tok == ")") { if (openers.empty()) { while (dest.size() && (dest.back() == ' ' || dest.back() == '\t')) dest = dest.substr(0, dest.size() - 1); return true; } if (openers.back() != '(') log_error("Mismatched brackets in macro argument: %c and %c.\n", openers.back(), tok[0]); openers.pop_back(); dest += tok; continue; } if (tok == "]") { char opener = openers.empty() ? '(' : openers.back(); if (opener != '[') log_error("Mismatched brackets in macro argument: %c and %c.\n", opener, tok[0]); openers.pop_back(); dest += tok; continue; } if (tok == "}") { char opener = openers.empty() ? '(' : openers.back(); if (opener != '{') log_error("Mismatched brackets in macro argument: %c and %c.\n", opener, tok[0]); openers.pop_back(); dest += tok; continue; } if (tok == "," && openers.empty()) { return false; } if (tok ==
/*
 *  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.
 *
 *  ---
 *
 *  This file contains an ad-hoc parser for Verilog constants. The Verilog
 *  lexer does only recognize a constant but does not actually split it to its
 *  components. I.e. it just passes the Verilog code for the constant to the
 *  bison parser. The parser then uses the function const2ast() from this file
 *  to create an AST node for the constant.
 *
 */

#include "verilog_frontend.h"
#include "kernel/log.h"
#include <string.h>
#include <math.h>

YOSYS_NAMESPACE_BEGIN

using namespace AST;

// divide an arbitrary length decimal number by two and return the rest
static int my_decimal_div_by_two(std::vector<uint8_t> &digits)
{
	int carry = 0;
	for (size_t i = 0; i < digits.size(); i++) {
		if (digits[i] >= 10)
			log_error("Invalid use of [a-fxz?] in decimal constant at %s:%d.\n",
				current_filename.c_str(), get_line_num());
		digits[i] += carry * 10;
		carry = digits[i] % 2;
		digits[i] /= 2;
	}
	while (!digits.empty() && !digits.front())
		digits.erase(digits.begin());
	return carry;
}

// find the number of significant bits in a binary number (not including the sign bit)
static int my_ilog2(int x)
{
	int ret = 0;
	while (x != 0 && x != -1) {
		x = x >> 1;
		ret++;
	}
	return ret;
}

// parse a binary, decimal, hexadecimal or octal number with support for special bits ('x', 'z' and '?')
static void my_strtobin(std::vector<RTLIL::State> &data, const char *str, int len_in_bits, int base, char case_type)
{
	// all digits in string (MSB at index 0)
	std::vector<uint8_t> digits;

	while (*str) {
		if ('0' <= *str && *str <= '9')
			digits.push_back(*str - '0');
		else if ('a' <= *str && *str <= 'f')
			digits.push_back(10 + *str - 'a');
		else if ('A' <= *str && *str <= 'F')
			digits.push_back(10 + *str - 'A');
		else if (*str == 'x' || *str == 'X')
			digits.push_back(0xf0);
		else if (*str == 'z' || *str == 'Z')
			digits.push_back(0xf1);
		else if (*str == '?')
			digits.push_back(0xf2);
		str++;
	}

	if (base == 10 && GetSize(digits) == 1 && digits.front() >= 0xf0)
		base = 2;

	data.clear();

	if (base == 10) {
		while (!digits.empty())
			data.push_back(my_decimal_div_by_two(digits) ? RTLIL::S1 : RTLIL::S0);
	} else {
		int bits_per_digit = my_ilog2(base-1);
		for (auto it = digits.rbegin(), e = digits.rend(); it != e; it++) {
			if (*it > (base-1) && *it < 0xf0)
				log_error("Digit larger than %d used in in base-%d constant at %s:%d.\n",
					base-1, base, current_filename.c_str(), get_line_num());
			for (int i = 0; i < bits_per_digit; i++) {
				int bitmask = 1 << i;
				if (*it == 0xf0)
					data.push_back(case_type == 'x' ? RTLIL::Sa : RTLIL::Sx);
				else if (*it == 0xf1)
					data.push_back(case_type == 'x' || case_type == 'z' ? RTLIL::Sa : RTLIL::Sz);
				else if (*it == 0xf2)
					data.push_back(RTLIL::Sa);
				else
					data.push_back((*it & bitmask) ? RTLIL::S1 : RTLIL::S0);
			}
		}
	}

	int len = GetSize(data);
	RTLIL::State msb = data.back();

	if (len_in_bits < 0) {
		if (len < 32)
			data.resize(32, msb == RTLIL::S0 || msb == RTLIL::S1 ? RTLIL::S0 : msb);
		return;
	}

	for (len = len - 1; len >= 0; len--)
		if (data[len] == RTLIL::S1)
			break;
	if (msb == RTLIL::S0 || msb == RTLIL::S1) {
		len += 1;
		data.resize(len_in_bits, RTLIL::S0);
	} else {
		len += 2;
		data.resize(len_in_bits, msb);
	}

	if (len > len_in_bits)
		log_warning("Literal has a width of %d bit, but value requires %d bit. (%s:%d)\n",
			len_in_bits, len, current_filename.c_str(), get_line_num());
}

// convert the Verilog code for a constant to an AST node
AstNode *VERILOG_FRONTEND::const2ast(std::string code, char case_type, bool warn_z)
{
	if (warn_z) {
		AstNode *ret = const2ast(code, case_type);
		if (std::find(ret->bits.begin(), ret->bits.end(), RTLIL::State::Sz) != ret->bits.end())
			log_warning("Yosys has only limited support for tri-state logic at the moment. (%s:%d)\n",
				current_filename.c_str(), get_line_num());
		return ret;
	}

	const char *str = code.c_str();

	// Strings
	if (*str == '"') {
		int len = strlen(str) - 2;
		std::vector<RTLIL::State> data;
		data.reserve(len * 8);
		for (int i = 0; i < len; i++) {
			unsigned char ch = str[len - i];
			for (int j = 0; j < 8; j++) {
				data.push_back((ch & 1) ? RTLIL::S1 : RTLIL::S0);
				ch = ch >> 1;
			}
		}
		AstNode *ast = AstNode::mkconst_bits(data, false);
		ast->str = code;
		return ast;
	}

	for (size_t i = 0; i < code.size(); i++)
		if (code[i] == '_' || code[i] == ' ' || code[i] == '\t' || code[i] == '\r' || code[i] == '\n')
			code.erase(code.begin()+(i--));
	str = code.c_str();

	char *endptr;
	long len_in_bits = strtol(str, &endptr, 10);

	// Simple base-10 integer
	if (*endptr == 0) {
		std::vector<RTLIL::State> data;
		my_strtobin(data, str, -1, 10, case_type);
		if (data.back() == RTLIL::S1)
			data.push_back(RTLIL::S0);
		return AstNode::mkconst_bits(data, true);
	}

	// unsized constant
	if (str == endptr)
		len_in_bits = -1;

	// The "<bits>'s?[bodhBODH]<digits>" syntax
	if (*endptr == '\'')
	{
		std::vector<RTLIL::State> data;
		bool is_signed = false;
		if (*(endptr+1) == 's') {
			is_signed = true;
			endptr++;
		}
		switch (*(endptr+1))
		{
		case 'b':
		case 'B':
			my_strtobin(data, endptr+2, len_in_bits, 2, case_type);
			break;
		case 'o':
		case 'O':
			my_strtobin(data, endptr+2, len_in_bits, 8, case_type);
			break;
		case 'd':
		case 'D':
			my_strtobin(data, endptr+2, len_in_bits, 10, case_type);
			break;
		case 'h':
		case 'H':
			my_strtobin(data, endptr+2, len_in_bits, 16, case_type);
			break;
		default:
			return NULL;
		}
		if (len_in_bits < 0) {
			if (is_signed && data.back() == RTLIL::S1)
				data.push_back(RTLIL::S0);
		}
		return AstNode::mkconst_bits(data, is_signed);
	}

	return NULL;
}

YOSYS_NAMESPACE_END