mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-15 03:35:40 +00:00
1168 lines
32 KiB
C++
1168 lines
32 KiB
C++
/*
|
|
* yosys -- Yosys Open SYnthesis Suite
|
|
*
|
|
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
|
|
*
|
|
* 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.
|
|
*
|
|
* ---
|
|
*
|
|
* A handwritten recursive-descent parser for the RTLIL text representation.
|
|
*
|
|
*/
|
|
|
|
#include "kernel/register.h"
|
|
#include "kernel/log.h"
|
|
#include "kernel/rtlil.h"
|
|
#include "kernel/utils.h"
|
|
#include "kernel/twine.h"
|
|
#include <charconv>
|
|
#include <deque>
|
|
#include <optional>
|
|
|
|
YOSYS_NAMESPACE_BEGIN
|
|
|
|
struct RTLILFrontendWorker {
|
|
// Forbid constants of more than 1 Gb.
|
|
// This will help us not explode on malicious RTLIL.
|
|
static constexpr int MAX_CONST_WIDTH = 1024 * 1024 * 1024;
|
|
|
|
std::istream *f = nullptr;
|
|
RTLIL::Design *design;
|
|
bool flag_nooverwrite = false;
|
|
bool flag_overwrite = false;
|
|
bool flag_lib = false;
|
|
bool flag_legalize = false;
|
|
|
|
int line_num;
|
|
std::string line_buf;
|
|
// Substring of line_buf. Always newline-terminated, thus never empty.
|
|
std::string_view line;
|
|
|
|
RTLIL::Module *current_module;
|
|
dict<RTLIL::IdString, RTLIL::Const> attrbuf;
|
|
|
|
TwineRef pending_src = Twine::Null;
|
|
std::vector<std::vector<RTLIL::SwitchRule*>*> switch_stack;
|
|
std::vector<RTLIL::CaseRule*> case_stack;
|
|
|
|
dict<size_t, TwineRef> twine_remap;
|
|
std::vector<TwineRef> twine_parser_holds;
|
|
|
|
struct TwineDesc {
|
|
enum Kind { Leaf, Suffix, Concat } kind;
|
|
std::string text;
|
|
size_t parent = 0;
|
|
std::vector<size_t> children;
|
|
bool materializing = false;
|
|
};
|
|
dict<size_t, TwineDesc> twine_descs;
|
|
|
|
template <typename... Args>
|
|
[[noreturn]]
|
|
void error(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
|
|
{
|
|
log_error("Parser error in line %d: %s\n", line_num, fmt.format(args...));
|
|
}
|
|
|
|
template <typename... Args>
|
|
void warning(FmtString<TypeIdentity<Args>...> fmt, const Args &... args)
|
|
{
|
|
log_warning("In line %d: %s\n", line_num, fmt.format(args...));
|
|
}
|
|
|
|
// May return an empty line if the stream is not good().
|
|
void advance_to_next_nonempty_line()
|
|
{
|
|
if (!f->good()) {
|
|
line = "\n";
|
|
return;
|
|
}
|
|
while (true) {
|
|
std::getline(*f, line_buf);
|
|
line_num++;
|
|
if (line_buf.empty() || line_buf[line_buf.size() - 1] != '\n')
|
|
line_buf += '\n';
|
|
line = line_buf;
|
|
consume_whitespace_and_comments();
|
|
if (line[0] != '\n' || !f->good())
|
|
break;
|
|
}
|
|
}
|
|
|
|
void consume_whitespace_and_comments()
|
|
{
|
|
while (true) {
|
|
switch (line[0]) {
|
|
case ' ':
|
|
case '\t':
|
|
line = line.substr(1);
|
|
break;
|
|
case '#':
|
|
line = "\n";
|
|
return;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool try_parse_keyword(std::string_view keyword)
|
|
{
|
|
int keyword_size = keyword.size();
|
|
if (keyword != line.substr(0, keyword_size))
|
|
return false;
|
|
// This index is safe because `line` is always newline-terminated
|
|
// and `keyword` never contains a newline.
|
|
char ch = line[keyword_size];
|
|
if (ch >= 'a' && ch <= 'z')
|
|
return false;
|
|
line = line.substr(keyword_size);
|
|
consume_whitespace_and_comments();
|
|
return true;
|
|
}
|
|
|
|
std::string error_token()
|
|
{
|
|
std::string result;
|
|
for (char ch : line) {
|
|
if (ch == '\n' || ch == ' ' || ch == '\t')
|
|
break;
|
|
result += ch;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void expect_keyword(std::string_view keyword)
|
|
{
|
|
if (!try_parse_keyword(keyword))
|
|
error("Expected token `%s', got `%s'.", keyword, error_token());
|
|
}
|
|
|
|
bool try_parse_char(char ch)
|
|
{
|
|
if (line[0] != ch)
|
|
return false;
|
|
line = line.substr(1);
|
|
consume_whitespace_and_comments();
|
|
return true;
|
|
}
|
|
|
|
void expect_char(char ch)
|
|
{
|
|
if (!try_parse_char(ch))
|
|
error("Expected `%c', got `%s'.", ch, error_token());
|
|
}
|
|
|
|
bool try_parse_eol()
|
|
{
|
|
if (line[0] != '\n')
|
|
return false;
|
|
advance_to_next_nonempty_line();
|
|
return true;
|
|
}
|
|
|
|
void expect_eol()
|
|
{
|
|
if (!try_parse_eol())
|
|
error("Expected EOL, got `%s'.", error_token());
|
|
}
|
|
|
|
std::optional<std::string> try_parse_id()
|
|
{
|
|
char ch = line[0];
|
|
if (ch != '\\' && ch != '$')
|
|
return std::nullopt;
|
|
int idx = 1;
|
|
while (true) {
|
|
ch = line[idx];
|
|
if (ch <= ' ' && (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'))
|
|
break;
|
|
++idx;
|
|
}
|
|
std::string result(line.substr(0, idx));
|
|
line = line.substr(idx);
|
|
consume_whitespace_and_comments();
|
|
return result;
|
|
}
|
|
|
|
std::string parse_id()
|
|
{
|
|
std::optional<std::string> id = try_parse_id();
|
|
if (!id.has_value())
|
|
error("Expected ID, got `%s'.", error_token());
|
|
return std::move(*id);
|
|
}
|
|
|
|
long long parse_integer()
|
|
{
|
|
long long result = parse_integer_alone();
|
|
consume_whitespace_and_comments();
|
|
return result;
|
|
}
|
|
|
|
long long parse_integer_alone()
|
|
{
|
|
int idx = 0;
|
|
if (line[idx] == '-')
|
|
++idx;
|
|
while (true) {
|
|
char ch = line[idx];
|
|
if (ch < '0' || ch > '9')
|
|
break;
|
|
++idx;
|
|
}
|
|
long long result;
|
|
if (std::from_chars(line.data(), line.data() + idx, result, 10).ec != std::errc{})
|
|
error("Invalid integer `%s'.", error_token());
|
|
line = line.substr(idx);
|
|
return result;
|
|
}
|
|
|
|
std::string parse_string()
|
|
{
|
|
if (line[0] != '\"')
|
|
error("Expected string, got `%s'.", error_token());
|
|
std::string str;
|
|
int idx = 1;
|
|
while (true) {
|
|
int start_idx = idx;
|
|
char ch;
|
|
while (true) {
|
|
ch = line[idx];
|
|
if (ch == '"' || ch == '\n' || ch == '\\' || ch == 0)
|
|
break;
|
|
++idx;
|
|
}
|
|
str.append(line.data() + start_idx, line.data() + idx);
|
|
++idx;
|
|
if (ch == '"')
|
|
break;
|
|
if (ch == 0)
|
|
error("Null byte in string literal: `%s'.", line);
|
|
if (ch == '\n')
|
|
error("Unterminated string literal: `%s'.", line);
|
|
ch = line[idx++];
|
|
if (ch == 'n') {
|
|
ch = '\n';
|
|
} else if (ch == 't') {
|
|
ch = '\t';
|
|
} else if (ch >= '0' && ch <= '7') {
|
|
int v = ch - '0';
|
|
char next_ch = line[idx + 1];
|
|
if (next_ch >= '0' && next_ch <= '7') {
|
|
++idx;
|
|
v = v*8 + (next_ch - '0');
|
|
next_ch = line[idx + 1];
|
|
if (next_ch >= '0' && next_ch <= '7') {
|
|
++idx;
|
|
v = v*8 + (next_ch - '0');
|
|
}
|
|
}
|
|
ch = v;
|
|
}
|
|
str += ch;
|
|
}
|
|
line = line.substr(idx);
|
|
consume_whitespace_and_comments();
|
|
return str;
|
|
}
|
|
|
|
RTLIL::Const parse_const()
|
|
{
|
|
if (line[0] == '"')
|
|
return RTLIL::Const(parse_string());
|
|
|
|
bool negative_value = line[0] == '-';
|
|
long long width = parse_integer_alone();
|
|
// Can't test value<0 here because we need to stop parsing after '-0'
|
|
if (negative_value || line[0] != '\'') {
|
|
if (width < INT_MIN || width > INT_MAX)
|
|
error("Integer %lld out of range before `%s'.", width, error_token());
|
|
consume_whitespace_and_comments();
|
|
return RTLIL::Const(width);
|
|
}
|
|
|
|
int idx = 1;
|
|
bool is_signed = line[1] == 's';
|
|
if (is_signed)
|
|
++idx;
|
|
|
|
std::vector<RTLIL::State> bits;
|
|
if (width > MAX_CONST_WIDTH)
|
|
error("Constant width %lld out of range before `%s`.", width, error_token());
|
|
bits.reserve(width);
|
|
int start_idx = idx;
|
|
while (true) {
|
|
RTLIL::State bit;
|
|
switch (line[idx]) {
|
|
case '0': bit = RTLIL::S0; break;
|
|
case '1': bit = RTLIL::S1; break;
|
|
case 'x': bit = RTLIL::Sx; break;
|
|
case 'z': bit = RTLIL::Sz; break;
|
|
case 'm': bit = RTLIL::Sm; break;
|
|
case '-': bit = RTLIL::Sa; break;
|
|
default: goto done;
|
|
}
|
|
bits.push_back(bit);
|
|
++idx;
|
|
}
|
|
done:
|
|
if (start_idx < idx)
|
|
std::reverse(bits.begin(), bits.end());
|
|
|
|
if (GetSize(bits) > width)
|
|
bits.resize(width);
|
|
else if (GetSize(bits) < width) {
|
|
RTLIL::State extbit = RTLIL::Sx;
|
|
if (!bits.empty()) {
|
|
extbit = bits.back();
|
|
if (extbit == RTLIL::S1)
|
|
extbit = RTLIL::S0;
|
|
}
|
|
bits.resize(width, extbit);
|
|
}
|
|
|
|
RTLIL::Const val(std::move(bits));
|
|
if (is_signed)
|
|
val.flags |= RTLIL::CONST_FLAG_SIGNED;
|
|
line = line.substr(idx);
|
|
consume_whitespace_and_comments();
|
|
return val;
|
|
}
|
|
|
|
RTLIL::Wire *legalize_wire(RTLIL::IdString id)
|
|
{
|
|
int wires_size = current_module->wires_size();
|
|
if (wires_size == 0)
|
|
error("No wires found for legalization");
|
|
int hash = hash_ops<RTLIL::IdString>::hash(id).yield();
|
|
RTLIL::Wire *wire = current_module->wire_at(abs(hash % wires_size));
|
|
log("Legalizing wire `%s' to `%s'.\n", log_id(id), design->twines.unescaped_str(wire->name.ref()));
|
|
return wire;
|
|
}
|
|
|
|
RTLIL::SigSpec parse_sigspec()
|
|
{
|
|
RTLIL::SigSpec sig;
|
|
|
|
if (try_parse_char('{')) {
|
|
std::vector<SigSpec> parts;
|
|
while (!try_parse_char('}'))
|
|
parts.push_back(parse_sigspec());
|
|
for (auto it = parts.rbegin(); it != parts.rend(); ++it)
|
|
sig.append(std::move(*it));
|
|
} else if (std::optional<TwineRef> handle = try_parse_twine_handle()) {
|
|
TwineRef ref = *handle;
|
|
RTLIL::Wire *wire = current_module->wire(ref);
|
|
if (wire == nullptr) {
|
|
if (flag_legalize)
|
|
wire = legalize_wire(RTLIL::IdString(design->twines.str(ref)));
|
|
else
|
|
error("Wire %s not found.", design->twines.str(ref).c_str());
|
|
}
|
|
sig = RTLIL::SigSpec(wire);
|
|
} else {
|
|
// We could add a special path for parsing IdStrings that must already exist,
|
|
// as here.
|
|
// We don't need to addref/release in this case.
|
|
std::optional<RTLIL::IdString> id = try_parse_id();
|
|
if (id.has_value()) {
|
|
std::string s = id->str();
|
|
bool pub = !s.empty() && s[0] == '\\';
|
|
TwineRef ref = twine_tag(design->twines.find(Twine{pub ? s.substr(1) : s}), pub);
|
|
RTLIL::Wire *wire = current_module->wire(ref);
|
|
if (wire == nullptr) {
|
|
if (flag_legalize)
|
|
wire = legalize_wire(*id);
|
|
else {
|
|
for (auto wire : current_module->wires())
|
|
design->twines.dump(wire->meta_->name);
|
|
error("Wire `%s' not found.", *id);
|
|
}
|
|
}
|
|
sig = RTLIL::SigSpec(wire);
|
|
} else {
|
|
sig = RTLIL::SigSpec(parse_const());
|
|
}
|
|
}
|
|
|
|
while (try_parse_char('[')) {
|
|
int left = parse_integer();
|
|
if (left >= sig.size() || left < 0) {
|
|
if (flag_legalize) {
|
|
int legalized;
|
|
if (sig.size() == 0)
|
|
legalized = 0;
|
|
else
|
|
legalized = std::max(0, std::min(left, sig.size() - 1));
|
|
log("Legalizing bit index %d to %d.\n", left, legalized);
|
|
left = legalized;
|
|
} else {
|
|
error("bit index %d out of range", left);
|
|
}
|
|
}
|
|
if (try_parse_char(':')) {
|
|
int right = parse_integer();
|
|
if (right < 0) {
|
|
if (flag_legalize) {
|
|
log("Legalizing bit index %d to %d.\n", right, 0);
|
|
right = 0;
|
|
} else
|
|
error("bit index %d out of range", right);
|
|
}
|
|
if (left < right) {
|
|
if (flag_legalize) {
|
|
log("Legalizing bit index %d to %d.\n", left, right);
|
|
left = right;
|
|
} else
|
|
error("invalid slice [%d:%d]", left, right);
|
|
}
|
|
if (flag_legalize && left >= sig.size())
|
|
log("Legalizing slice %d:%d by igoring it\n", left, right);
|
|
else
|
|
sig = sig.extract(right, left - right + 1);
|
|
} else {
|
|
if (flag_legalize && left >= sig.size())
|
|
log("Legalizing slice %d by igoring it\n", left);
|
|
else
|
|
sig = sig.extract(left);
|
|
}
|
|
expect_char(']');
|
|
}
|
|
|
|
return sig;
|
|
}
|
|
|
|
void parse_module()
|
|
{
|
|
TwineRef module_name = parse_twine();
|
|
expect_eol();
|
|
|
|
bool delete_current_module = false;
|
|
if (design->has(module_name)) {
|
|
RTLIL::Module *existing_mod = design->module(module_name);
|
|
if (!flag_overwrite && (flag_lib || (attrbuf.count(ID::blackbox) && attrbuf.at(ID::blackbox).as_bool()))) {
|
|
log("Ignoring blackbox re-definition of module %s.\n", module_name);
|
|
delete_current_module = true;
|
|
} else if (!flag_nooverwrite && !flag_overwrite && !existing_mod->get_bool_attribute(ID::blackbox)) {
|
|
error("RTLIL error: redefinition of module %s.", module_name);
|
|
} else if (flag_nooverwrite) {
|
|
log("Ignoring re-definition of module %s.\n", module_name);
|
|
delete_current_module = true;
|
|
} else {
|
|
log("Replacing existing%s module %s.\n", existing_mod->get_bool_attribute(ID::blackbox) ? " blackbox" : "", module_name);
|
|
design->remove(existing_mod);
|
|
}
|
|
}
|
|
|
|
current_module = new RTLIL::Module;
|
|
current_module->design = design;
|
|
current_module->meta_->name = module_name;
|
|
if (delete_current_module) {
|
|
attrbuf.erase(ID::src);
|
|
pending_src = Twine::Null;
|
|
current_module->attributes = std::move(attrbuf);
|
|
} else {
|
|
design->add(current_module);
|
|
current_module->absorb_attrs(std::move(attrbuf));
|
|
flush_src(current_module);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (try_parse_keyword("attribute")) {
|
|
parse_attribute();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("parameter")) {
|
|
parse_parameter();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("connect")) {
|
|
parse_connect();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("wire")) {
|
|
parse_wire();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("cell")) {
|
|
parse_cell();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("memory")) {
|
|
parse_memory();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("process")) {
|
|
parse_process();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("end")) {
|
|
expect_eol();
|
|
break;
|
|
}
|
|
error("Unexpected token in module body: %s", error_token());
|
|
}
|
|
|
|
if (attrbuf.size() != 0)
|
|
error("dangling attribute");
|
|
current_module->fixup_ports();
|
|
if (delete_current_module)
|
|
delete current_module;
|
|
else if (flag_lib)
|
|
current_module->makeblackbox();
|
|
current_module = nullptr;
|
|
}
|
|
|
|
void parse_attribute()
|
|
{
|
|
RTLIL::IdString id = parse_id();
|
|
RTLIL::Const c = parse_const();
|
|
if (id == RTLIL::ID::src && (c.flags & RTLIL::CONST_FLAG_STRING)) {
|
|
std::string raw = c.decode_string();
|
|
if (!raw.empty() && raw[0] == '@') {
|
|
size_t file_id = 0;
|
|
auto [ptr, ec] = std::from_chars(raw.data() + 1, raw.data() + raw.size(), file_id);
|
|
if (ec != std::errc() || ptr != raw.data() + raw.size())
|
|
error("Malformed src twine reference %s at line %d", raw.c_str(), line_num);
|
|
pending_src = resolve_file_twine(file_id);
|
|
expect_eol();
|
|
return;
|
|
}
|
|
if (raw.find('|') != std::string::npos) {
|
|
log_warning("line %d: src attribute %s contains '|' separators. "
|
|
"That convention is Yosys-internal; the producing tool "
|
|
"should emit a single path:line.col per attribute and "
|
|
"let Yosys merge through the twine pool.\n",
|
|
line_num, raw.c_str());
|
|
}
|
|
}
|
|
attrbuf.insert({std::move(id), std::move(c)});
|
|
expect_eol();
|
|
}
|
|
|
|
// Apply a pending "@N" src reference to the object just built from attrbuf.
|
|
void flush_src(RTLIL::AttrObject *obj)
|
|
{
|
|
if (pending_src != Twine::Null) {
|
|
design->set_src_attribute(obj, pending_src);
|
|
pending_src = Twine::Null;
|
|
}
|
|
}
|
|
|
|
TwineRef resolve_file_twine(size_t id, bool is_public = false)
|
|
{
|
|
TwineRef base;
|
|
if (id < STATIC_TWINE_END) {
|
|
base = TwineRef(id);
|
|
} else {
|
|
auto it = twine_remap.find(id);
|
|
base = (it == twine_remap.end()) ? materialize_file_twine(id) : it->second;
|
|
}
|
|
return twine_tag(base, is_public);
|
|
}
|
|
|
|
// Tolerates nodes listed out of dependency order
|
|
TwineRef materialize_file_twine(size_t id)
|
|
{
|
|
if (id < STATIC_TWINE_END)
|
|
return TwineRef(id);
|
|
auto rit = twine_remap.find(id);
|
|
if (rit != twine_remap.end())
|
|
return rit->second;
|
|
auto dit = twine_descs.find(id);
|
|
if (dit == twine_descs.end())
|
|
error("Unknown twine reference @%zu at line %d", id, line_num);
|
|
TwineDesc &desc = dit->second;
|
|
if (desc.materializing)
|
|
error("Cyclic twine reference @%zu at line %d", id, line_num);
|
|
desc.materializing = true;
|
|
TwineRef ref;
|
|
switch (desc.kind) {
|
|
case TwineDesc::Leaf:
|
|
ref = design->twines.add(Twine{desc.text});
|
|
break;
|
|
case TwineDesc::Suffix:
|
|
ref = design->twines.add(Twine{Twine::Suffix{
|
|
materialize_file_twine(desc.parent), desc.text}});
|
|
break;
|
|
case TwineDesc::Concat: {
|
|
std::vector<TwineRef> children;
|
|
children.reserve(desc.children.size());
|
|
for (size_t c : desc.children)
|
|
children.push_back(materialize_file_twine(c));
|
|
ref = design->twines.add(Twine{children});
|
|
break;
|
|
}
|
|
}
|
|
desc.materializing = false;
|
|
twine_remap[id] = ref;
|
|
return ref;
|
|
}
|
|
|
|
// Parse a "$pub@N"/"$priv@N" twine handle into a resolved, retagged ref
|
|
std::optional<TwineRef> try_parse_twine_handle()
|
|
{
|
|
bool is_public;
|
|
if (line.substr(0, 5) == "$pub@")
|
|
is_public = true, line = line.substr(5);
|
|
else if (line.substr(0, 6) == "$priv@")
|
|
is_public = false, line = line.substr(6);
|
|
else
|
|
return std::nullopt;
|
|
return resolve_file_twine(parse_integer(), is_public);
|
|
}
|
|
|
|
// A twine-typed token at a definition site: a $pub@/$priv@ reference into
|
|
// the twines table, or an escaped identifier interned into the pool.
|
|
std::optional<TwineRef> try_parse_twine()
|
|
{
|
|
if (std::optional<TwineRef> handle = try_parse_twine_handle())
|
|
return handle;
|
|
std::optional<std::string> id = try_parse_id();
|
|
if (!id)
|
|
return std::nullopt;
|
|
return design->twines.add(std::move(*id));
|
|
}
|
|
|
|
TwineRef parse_twine()
|
|
{
|
|
std::optional<TwineRef> t = try_parse_twine();
|
|
if (!t)
|
|
error("Expected twine reference or ID, got `%s'.", error_token());
|
|
return *t;
|
|
}
|
|
|
|
// Parse a `twines` ... `end` block into per-file node descriptors, then
|
|
// intern them all into design->twines. The destination pool may already
|
|
// hold twines (multi-file load); it dedups by content. Static ids are
|
|
// universal and never appear in the block.
|
|
void parse_twines()
|
|
{
|
|
expect_eol();
|
|
while (true) {
|
|
if (try_parse_keyword("end"))
|
|
break;
|
|
if (try_parse_keyword("leaf")) {
|
|
size_t file_id = parse_integer();
|
|
TwineDesc &desc = twine_descs[file_id];
|
|
desc.kind = TwineDesc::Leaf;
|
|
desc.text = parse_string();
|
|
expect_eol();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("suffix")) {
|
|
size_t file_id = parse_integer();
|
|
TwineDesc &desc = twine_descs[file_id];
|
|
desc.kind = TwineDesc::Suffix;
|
|
desc.parent = parse_integer();
|
|
desc.text = parse_string();
|
|
expect_eol();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("concat")) {
|
|
size_t file_id = parse_integer();
|
|
TwineDesc &desc = twine_descs[file_id];
|
|
desc.kind = TwineDesc::Concat;
|
|
while (!try_parse_eol())
|
|
desc.children.push_back(parse_integer());
|
|
continue;
|
|
}
|
|
error("Expected `leaf`, `suffix` or `concat` inside twines block, got `%s'.",
|
|
error_token());
|
|
}
|
|
std::vector<size_t> ordered_ids;
|
|
ordered_ids.reserve(twine_descs.size());
|
|
for (auto &it : twine_descs)
|
|
ordered_ids.push_back(it.first);
|
|
std::sort(ordered_ids.begin(), ordered_ids.end());
|
|
for (size_t id : ordered_ids)
|
|
materialize_file_twine(id);
|
|
twine_descs.clear();
|
|
expect_eol();
|
|
}
|
|
|
|
void parse_parameter()
|
|
{
|
|
RTLIL::IdString id = parse_id();
|
|
current_module->avail_parameters(id);
|
|
if (try_parse_eol())
|
|
return;
|
|
RTLIL::Const c = parse_const();
|
|
current_module->parameter_default_values.insert({std::move(id), std::move(c)});
|
|
expect_eol();
|
|
}
|
|
|
|
void parse_wire()
|
|
{
|
|
RTLIL::Wire *wire;
|
|
int width = 1;
|
|
int start_offset = 0;
|
|
int port_id = 0;
|
|
bool port_input = false;
|
|
bool port_output = false;
|
|
bool upto = false;
|
|
bool is_signed = false;
|
|
|
|
while (true)
|
|
{
|
|
std::optional<TwineRef> name = try_parse_twine();
|
|
if (name) {
|
|
TwineRef wire_name = *name;
|
|
if (current_module->wire(wire_name) != nullptr) {
|
|
if (flag_legalize) {
|
|
log("Legalizing redefinition of wire %s.\n", design->twines.str(wire_name).c_str());
|
|
pool<RTLIL::Wire*> wires = {current_module->wire(wire_name)};
|
|
current_module->remove(wires);
|
|
} else
|
|
error("RTLIL error: redefinition of wire %s.", design->twines.str(wire_name).c_str());
|
|
}
|
|
wire = current_module->addWire(wire_name);
|
|
break;
|
|
}
|
|
if (try_parse_keyword("width"))
|
|
width = parse_integer();
|
|
else if (try_parse_keyword("upto"))
|
|
upto = true;
|
|
else if (try_parse_keyword("signed"))
|
|
is_signed = true;
|
|
else if (try_parse_keyword("offset"))
|
|
start_offset = parse_integer();
|
|
else if (try_parse_keyword("input")) {
|
|
port_id = parse_integer();
|
|
port_input = true;
|
|
} else if (try_parse_keyword("output")) {
|
|
port_id = parse_integer();
|
|
port_output = true;
|
|
} else if (try_parse_keyword("inout")) {
|
|
port_id = parse_integer();
|
|
port_input = true;
|
|
port_output = true;
|
|
} else if (try_parse_eol())
|
|
error("Missing wire ID");
|
|
else
|
|
error("Unexpected wire option: %s", error_token());
|
|
}
|
|
|
|
wire->absorb_attrs(std::move(attrbuf));
|
|
flush_src(wire);
|
|
wire->width = width;
|
|
wire->upto = upto;
|
|
wire->start_offset = start_offset;
|
|
wire->is_signed = is_signed;
|
|
wire->port_id = port_id;
|
|
wire->port_input = port_input;
|
|
wire->port_output = port_output;
|
|
expect_eol();
|
|
}
|
|
|
|
void parse_memory()
|
|
{
|
|
RTLIL::Memory *memory = new RTLIL::Memory;
|
|
memory->module = current_module;
|
|
memory->absorb_attrs(std::move(attrbuf));
|
|
flush_src(memory);
|
|
|
|
int width = 1;
|
|
int start_offset = 0;
|
|
int size = 0;
|
|
TwineRef mem_name = Twine::Null;
|
|
while (true)
|
|
{
|
|
std::optional<TwineRef> name = try_parse_twine();
|
|
if (name.has_value()) {
|
|
mem_name = *name;
|
|
if (current_module->memories.count(mem_name) != 0) {
|
|
if (flag_legalize) {
|
|
log("Legalizing redefinition of memory %s.\n", design->twines.str(mem_name).c_str());
|
|
current_module->remove(current_module->memories.at(mem_name));
|
|
} else
|
|
error("RTLIL error: redefinition of memory %s.", design->twines.str(mem_name).c_str());
|
|
}
|
|
if (memory->meta_ == nullptr)
|
|
memory->meta_ = design->alloc_obj_meta();
|
|
memory->meta_->name = mem_name;
|
|
break;
|
|
}
|
|
if (try_parse_keyword("width"))
|
|
width = parse_integer();
|
|
else if (try_parse_keyword("size"))
|
|
size = parse_integer();
|
|
else if (try_parse_keyword("offset"))
|
|
start_offset = parse_integer();
|
|
else if (try_parse_eol())
|
|
error("Missing memory ID");
|
|
else
|
|
error("Unexpected memory option: %s", error_token());
|
|
}
|
|
memory->width = width;
|
|
memory->start_offset = start_offset;
|
|
memory->size = size;
|
|
current_module->memories.insert({mem_name, memory});
|
|
expect_eol();
|
|
}
|
|
|
|
void legalize_width_parameter(RTLIL::Cell *cell, TwineRef port_name)
|
|
{
|
|
std::string width_param_name = design->twines.str(port_name) + "_WIDTH";
|
|
if (cell->parameters.count(RTLIL::IdString(width_param_name)) == 0)
|
|
return;
|
|
RTLIL::Const ¶m = cell->parameters.at(RTLIL::IdString(width_param_name));
|
|
if (param.as_int() != 0)
|
|
return;
|
|
cell->parameters[RTLIL::IdString(width_param_name)] = RTLIL::Const(cell->getPort(port_name).size());
|
|
}
|
|
|
|
void parse_cell()
|
|
{
|
|
TwineRef cell_type_ref = parse_twine();
|
|
TwineRef cell_name_ref = parse_twine();
|
|
expect_eol();
|
|
|
|
if (current_module->cell(cell_name_ref) != nullptr) {
|
|
if (flag_legalize) {
|
|
std::string base = design->twines.str(cell_name_ref);
|
|
std::string new_name_str;
|
|
int suffix = 1;
|
|
do {
|
|
new_name_str = base + "_" + std::to_string(suffix);
|
|
cell_name_ref = design->twines.add(std::string(new_name_str));
|
|
++suffix;
|
|
} while (current_module->cell(cell_name_ref) != nullptr);
|
|
log("Legalizing redefinition of cell %s by renaming to %s.\n", base.c_str(), new_name_str.c_str());
|
|
} else
|
|
error("RTLIL error: redefinition of cell %s.", design->twines.str(cell_name_ref).c_str());
|
|
}
|
|
RTLIL::Cell *cell = current_module->addCell(cell_name_ref, cell_type_ref);
|
|
cell->absorb_attrs(std::move(attrbuf));
|
|
flush_src(cell);
|
|
|
|
while (true)
|
|
{
|
|
if (try_parse_keyword("parameter")) {
|
|
bool is_signed = false;
|
|
bool is_real = false;
|
|
bool is_unsized = false;
|
|
if (try_parse_keyword("signed")) {
|
|
is_signed = true;
|
|
} else if (try_parse_keyword("real")) {
|
|
is_real = true;
|
|
} else if (try_parse_keyword("unsized")) {
|
|
is_unsized = true;
|
|
}
|
|
RTLIL::IdString param_name = parse_id();
|
|
RTLIL::Const val = parse_const();
|
|
if (is_signed)
|
|
val.flags |= RTLIL::CONST_FLAG_SIGNED;
|
|
if (is_real)
|
|
val.flags |= RTLIL::CONST_FLAG_REAL;
|
|
if (is_unsized)
|
|
val.flags |= RTLIL::CONST_FLAG_UNSIZED;
|
|
cell->parameters.insert({std::move(param_name), std::move(val)});
|
|
expect_eol();
|
|
} else if (try_parse_keyword("connect")) {
|
|
TwineRef port_name = parse_twine();
|
|
if (cell->hasPort(port_name)) {
|
|
if (flag_legalize)
|
|
log("Legalizing redefinition of cell port %s.", design->twines.str(port_name).c_str());
|
|
else
|
|
error("RTLIL error: redefinition of cell port %s.", design->twines.str(port_name).c_str());
|
|
}
|
|
cell->setPort(port_name, parse_sigspec());
|
|
if (flag_legalize)
|
|
legalize_width_parameter(cell, port_name);
|
|
expect_eol();
|
|
} else if (try_parse_keyword("end")) {
|
|
expect_eol();
|
|
break;
|
|
} else {
|
|
error("Unexpected token in cell body: %s", error_token());
|
|
}
|
|
}
|
|
}
|
|
|
|
void parse_connect()
|
|
{
|
|
if (attrbuf.size() != 0)
|
|
error("dangling attribute");
|
|
RTLIL::SigSpec s1 = parse_sigspec();
|
|
RTLIL::SigSpec s2 = parse_sigspec();
|
|
if (flag_legalize) {
|
|
int min_size = std::min(s1.size(), s2.size());
|
|
s1 = s1.extract(0, min_size);
|
|
s2 = s2.extract(0, min_size);
|
|
}
|
|
current_module->connect(std::move(s1), std::move(s2));
|
|
expect_eol();
|
|
}
|
|
|
|
void parse_case_body(RTLIL::CaseRule *current_case)
|
|
{
|
|
while (true)
|
|
{
|
|
if (try_parse_keyword("attribute"))
|
|
parse_attribute();
|
|
else if (try_parse_keyword("switch"))
|
|
parse_switch();
|
|
else if (try_parse_keyword("assign")) {
|
|
if (attrbuf.size() != 0)
|
|
error("dangling attribute");
|
|
// See https://github.com/YosysHQ/yosys/pull/4765 for discussion on this
|
|
// warning
|
|
if (!switch_stack.back()->empty())
|
|
warning("case rule assign statements after switch statements may cause unexpected behaviour. "
|
|
"The assign statement is reordered to come before all switch statements.");
|
|
RTLIL::SigSpec s1 = parse_sigspec();
|
|
RTLIL::SigSpec s2 = parse_sigspec();
|
|
current_case->actions.push_back({std::move(s1), std::move(s2), Twine::Null});
|
|
expect_eol();
|
|
} else
|
|
return;
|
|
}
|
|
}
|
|
|
|
void parse_switch()
|
|
{
|
|
RTLIL::SwitchRule *rule = new RTLIL::SwitchRule;
|
|
rule->module = current_module;
|
|
rule->signal = parse_sigspec();
|
|
rule->absorb_attrs(std::move(attrbuf));
|
|
flush_src(rule);
|
|
switch_stack.back()->push_back(rule);
|
|
expect_eol();
|
|
|
|
while (true) {
|
|
if (try_parse_keyword("attribute")) {
|
|
parse_attribute();
|
|
continue;
|
|
}
|
|
|
|
if (try_parse_keyword("end")) {
|
|
expect_eol();
|
|
break;
|
|
}
|
|
|
|
expect_keyword("case");
|
|
RTLIL::CaseRule *case_rule = new RTLIL::CaseRule;
|
|
case_rule->module = current_module;
|
|
case_rule->absorb_attrs(std::move(attrbuf));
|
|
flush_src(case_rule);
|
|
rule->cases.push_back(case_rule);
|
|
switch_stack.push_back(&case_rule->switches);
|
|
case_stack.push_back(case_rule);
|
|
|
|
if (!try_parse_eol()) {
|
|
while (true) {
|
|
case_rule->compare.push_back(parse_sigspec());
|
|
if (try_parse_eol())
|
|
break;
|
|
expect_char(',');
|
|
}
|
|
}
|
|
|
|
parse_case_body(case_rule);
|
|
|
|
switch_stack.pop_back();
|
|
case_stack.pop_back();
|
|
}
|
|
}
|
|
|
|
void parse_process()
|
|
{
|
|
TwineRef proc_name = parse_twine();
|
|
expect_eol();
|
|
|
|
if (current_module->processes.count(proc_name) != 0) {
|
|
if (flag_legalize) {
|
|
log("Legalizing redefinition of process %s.\n", design->twines.str(proc_name).c_str());
|
|
current_module->remove(current_module->processes.at(proc_name));
|
|
} else
|
|
error("RTLIL error: redefinition of process %s.", design->twines.str(proc_name).c_str());
|
|
}
|
|
RTLIL::Process *proc = current_module->addProcess(proc_name);
|
|
proc->absorb_attrs(std::move(attrbuf));
|
|
flush_src(proc);
|
|
|
|
switch_stack.clear();
|
|
switch_stack.push_back(&proc->root_case.switches);
|
|
case_stack.clear();
|
|
case_stack.push_back(&proc->root_case);
|
|
|
|
parse_case_body(&proc->root_case);
|
|
|
|
while (try_parse_keyword("sync"))
|
|
{
|
|
RTLIL::SyncRule *rule = new RTLIL::SyncRule;
|
|
|
|
if (try_parse_keyword("low")) rule->type = RTLIL::ST0;
|
|
else if (try_parse_keyword("high")) rule->type = RTLIL::ST1;
|
|
else if (try_parse_keyword("posedge")) rule->type = RTLIL::STp;
|
|
else if (try_parse_keyword("negedge")) rule->type = RTLIL::STn;
|
|
else if (try_parse_keyword("edge")) rule->type = RTLIL::STe;
|
|
else if (try_parse_keyword("always")) rule->type = RTLIL::STa;
|
|
else if (try_parse_keyword("global")) rule->type = RTLIL::STg;
|
|
else if (try_parse_keyword("init")) rule->type = RTLIL::STi;
|
|
else error("Unexpected sync type: %s", error_token());
|
|
|
|
if (rule->type != RTLIL::STa && rule->type != RTLIL::STg && rule->type != RTLIL::STi)
|
|
rule->signal = parse_sigspec();
|
|
proc->syncs.push_back(rule);
|
|
expect_eol();
|
|
|
|
bool attributes_in_update_list = false;
|
|
while (true)
|
|
{
|
|
if (try_parse_keyword("update")) {
|
|
RTLIL::SigSpec s1 = parse_sigspec();
|
|
RTLIL::SigSpec s2 = parse_sigspec();
|
|
rule->actions.push_back({std::move(s1), std::move(s2), Twine::Null});
|
|
expect_eol();
|
|
continue;
|
|
}
|
|
|
|
if (try_parse_keyword("attribute")) {
|
|
attributes_in_update_list = true;
|
|
parse_attribute();
|
|
continue;
|
|
}
|
|
|
|
if (!try_parse_keyword("memwr"))
|
|
break;
|
|
|
|
RTLIL::MemWriteAction act;
|
|
act.module = current_module;
|
|
design->absorb_attrs(&act, std::move(attrbuf));
|
|
flush_src(&act);
|
|
act.memid = parse_id();
|
|
act.address = parse_sigspec();
|
|
act.data = parse_sigspec();
|
|
act.enable = parse_sigspec();
|
|
act.priority_mask = parse_const();
|
|
rule->mem_write_actions.push_back(act);
|
|
act.meta_ = nullptr;
|
|
expect_eol();
|
|
}
|
|
// The old parser allowed dangling attributes before a "sync" to carry through
|
|
// the "sync", so we will too, for now.
|
|
if (attributes_in_update_list && attrbuf.size() > 0)
|
|
error("dangling attribute");
|
|
}
|
|
|
|
expect_keyword("end");
|
|
expect_eol();
|
|
}
|
|
|
|
RTLILFrontendWorker(RTLIL::Design *design) : design(design) {}
|
|
|
|
void parse(std::istream *f)
|
|
{
|
|
this->f = f;
|
|
line_num = 0;
|
|
advance_to_next_nonempty_line();
|
|
while (f->good())
|
|
{
|
|
if (try_parse_keyword("attribute")) {
|
|
parse_attribute();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("module")) {
|
|
parse_module();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("autoidx")) {
|
|
autoidx = std::max<int>(autoidx, parse_integer());
|
|
expect_eol();
|
|
continue;
|
|
}
|
|
if (try_parse_keyword("twines")) {
|
|
parse_twines();
|
|
continue;
|
|
}
|
|
error("Unexpected token: %s", error_token());
|
|
}
|
|
if (attrbuf.size() != 0)
|
|
error("dangling attribute");
|
|
|
|
twine_parser_holds.clear();
|
|
twine_remap.clear();
|
|
}
|
|
};
|
|
|
|
struct RTLILFrontend : public Frontend {
|
|
RTLILFrontend() : Frontend("rtlil", "read modules from RTLIL file") { }
|
|
void help() override
|
|
{
|
|
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
|
log("\n");
|
|
log(" read_rtlil [filename]\n");
|
|
log("\n");
|
|
log("Load modules from an RTLIL file to the current design. (RTLIL is a text\n");
|
|
log("representation of a design in yosys's internal format.)\n");
|
|
log("\n");
|
|
log(" -nooverwrite\n");
|
|
log(" ignore re-definitions of modules. (the default behavior is to\n");
|
|
log(" create an error message if the existing module is not a blackbox\n");
|
|
log(" module, and overwrite the existing module if it is a blackbox module.)\n");
|
|
log("\n");
|
|
log(" -overwrite\n");
|
|
log(" overwrite existing modules with the same name\n");
|
|
log("\n");
|
|
log(" -lib\n");
|
|
log(" only create empty blackbox modules\n");
|
|
log("\n");
|
|
log(" -legalize\n");
|
|
log(" prevent semantic errors (e.g. reference to unknown wire, redefinition of wire/cell)\n");
|
|
log(" by deterministically rewriting the input into something valid. Useful when using\n");
|
|
log(" fuzzing to generate random but valid RTLIL.\n");
|
|
log("\n");
|
|
}
|
|
void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
|
|
{
|
|
RTLILFrontendWorker worker(design);
|
|
|
|
log_header(design, "Executing RTLIL frontend.\n");
|
|
|
|
size_t argidx;
|
|
for (argidx = 1; argidx < args.size(); argidx++) {
|
|
std::string arg = args[argidx];
|
|
if (arg == "-nooverwrite") {
|
|
worker.flag_nooverwrite = true;
|
|
worker.flag_overwrite = false;
|
|
continue;
|
|
}
|
|
if (arg == "-overwrite") {
|
|
worker.flag_nooverwrite = false;
|
|
worker.flag_overwrite = true;
|
|
continue;
|
|
}
|
|
if (arg == "-lib") {
|
|
worker.flag_lib = true;
|
|
continue;
|
|
}
|
|
if (arg == "-legalize") {
|
|
worker.flag_legalize = true;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
extra_args(f, filename, args, argidx);
|
|
|
|
log("Input filename: %s\n", filename);
|
|
|
|
worker.parse(f);
|
|
}
|
|
} RTLILFrontend;
|
|
|
|
YOSYS_NAMESPACE_END
|