mirror of https://github.com/YosysHQ/yosys.git
1102 lines
32 KiB
C++
1102 lines
32 KiB
C++
|
/*
|
||
|
* yosys -- Yosys Open SYnthesis Suite
|
||
|
*
|
||
|
* Copyright (C) 2021 Marcelina Kościelnicka <mwk@0x04.net>
|
||
|
*
|
||
|
* 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "memlib.h"
|
||
|
|
||
|
#include <ctype.h>
|
||
|
|
||
|
USING_YOSYS_NAMESPACE
|
||
|
|
||
|
using namespace MemLibrary;
|
||
|
|
||
|
PRIVATE_NAMESPACE_BEGIN
|
||
|
|
||
|
typedef dict<std::string, Const> Options;
|
||
|
|
||
|
struct ClockDef {
|
||
|
ClkPolKind kind;
|
||
|
std::string name;
|
||
|
};
|
||
|
|
||
|
struct RawWrTransDef {
|
||
|
WrTransTargetKind target_kind;
|
||
|
std::string target_group;
|
||
|
WrTransKind kind;
|
||
|
};
|
||
|
|
||
|
struct PortWidthDef {
|
||
|
bool tied;
|
||
|
std::vector<int> wr_widths;
|
||
|
std::vector<int> rd_widths;
|
||
|
};
|
||
|
|
||
|
struct SrstDef {
|
||
|
ResetValKind val;
|
||
|
SrstKind kind;
|
||
|
bool block_wr;
|
||
|
};
|
||
|
|
||
|
struct Empty {};
|
||
|
|
||
|
template<typename T> struct Capability {
|
||
|
T val;
|
||
|
Options opts, portopts;
|
||
|
|
||
|
Capability(T val, Options opts, Options portopts) : val(val), opts(opts), portopts(portopts) {}
|
||
|
};
|
||
|
|
||
|
template<typename T> using Caps = std::vector<Capability<T>>;
|
||
|
|
||
|
struct PortGroupDef {
|
||
|
PortKind kind;
|
||
|
dict<std::string, pool<Const>> portopts;
|
||
|
std::vector<std::string> names;
|
||
|
Caps<Empty> forbid;
|
||
|
Caps<ClockDef> clock;
|
||
|
Caps<Empty> clken;
|
||
|
Caps<Empty> wrbe_separate;
|
||
|
Caps<PortWidthDef> width;
|
||
|
Caps<Empty> rden;
|
||
|
Caps<RdWrKind> rdwr;
|
||
|
Caps<ResetValKind> rdinit;
|
||
|
Caps<ResetValKind> rdarst;
|
||
|
Caps<SrstDef> rdsrst;
|
||
|
Caps<std::string> wrprio;
|
||
|
Caps<RawWrTransDef> wrtrans;
|
||
|
Caps<Empty> optional;
|
||
|
Caps<Empty> optional_rw;
|
||
|
};
|
||
|
|
||
|
struct WidthsDef {
|
||
|
std::vector<int> widths;
|
||
|
WidthMode mode;
|
||
|
};
|
||
|
|
||
|
struct ResourceDef {
|
||
|
std::string name;
|
||
|
int count;
|
||
|
};
|
||
|
|
||
|
struct RamDef {
|
||
|
IdString id;
|
||
|
dict<std::string, pool<Const>> opts;
|
||
|
RamKind kind;
|
||
|
Caps<Empty> forbid;
|
||
|
Caps<Empty> prune_rom;
|
||
|
Caps<PortGroupDef> ports;
|
||
|
Caps<int> abits;
|
||
|
Caps<WidthsDef> widths;
|
||
|
Caps<ResourceDef> resource;
|
||
|
Caps<double> cost;
|
||
|
Caps<double> widthscale;
|
||
|
Caps<int> byte;
|
||
|
Caps<MemoryInitKind> init;
|
||
|
Caps<std::string> style;
|
||
|
};
|
||
|
|
||
|
struct Parser {
|
||
|
std::string filename;
|
||
|
std::ifstream infile;
|
||
|
int line_number = 0;
|
||
|
Library &lib;
|
||
|
const pool<std::string> &defines;
|
||
|
pool<std::string> &defines_unused;
|
||
|
std::vector<std::string> tokens;
|
||
|
int token_idx = 0;
|
||
|
bool eof = false;
|
||
|
|
||
|
std::vector<std::pair<std::string, Const>> option_stack;
|
||
|
std::vector<std::pair<std::string, Const>> portoption_stack;
|
||
|
RamDef ram;
|
||
|
PortGroupDef port;
|
||
|
bool active = true;
|
||
|
|
||
|
Parser(std::string filename, Library &lib, const pool<std::string> &defines, pool<std::string> &defines_unused) : filename(filename), lib(lib), defines(defines), defines_unused(defines_unused) {
|
||
|
// Note: this rewrites the filename we're opening, but not
|
||
|
// the one we're storing — this is actually correct, so that
|
||
|
// we keep the original filename for diagnostics.
|
||
|
rewrite_filename(filename);
|
||
|
infile.open(filename);
|
||
|
if (infile.fail()) {
|
||
|
log_error("failed to open %s\n", filename.c_str());
|
||
|
}
|
||
|
parse();
|
||
|
infile.close();
|
||
|
}
|
||
|
|
||
|
std::string peek_token() {
|
||
|
if (eof)
|
||
|
return "";
|
||
|
|
||
|
if (token_idx < GetSize(tokens))
|
||
|
return tokens[token_idx];
|
||
|
|
||
|
tokens.clear();
|
||
|
token_idx = 0;
|
||
|
|
||
|
std::string line;
|
||
|
while (std::getline(infile, line)) {
|
||
|
line_number++;
|
||
|
for (string tok = next_token(line); !tok.empty(); tok = next_token(line)) {
|
||
|
if (tok[0] == '#')
|
||
|
break;
|
||
|
if (tok[tok.size()-1] == ';') {
|
||
|
tokens.push_back(tok.substr(0, tok.size()-1));
|
||
|
tokens.push_back(";");
|
||
|
} else {
|
||
|
tokens.push_back(tok);
|
||
|
}
|
||
|
}
|
||
|
if (!tokens.empty())
|
||
|
return tokens[token_idx];
|
||
|
}
|
||
|
|
||
|
eof = true;
|
||
|
return "";
|
||
|
}
|
||
|
|
||
|
std::string get_token() {
|
||
|
std::string res = peek_token();
|
||
|
if (!eof)
|
||
|
token_idx++;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void eat_token(std::string expected) {
|
||
|
std::string token = get_token();
|
||
|
if (token != expected) {
|
||
|
log_error("%s:%d: expected `%s`, got `%s`.\n", filename.c_str(), line_number, expected.c_str(), token.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
IdString get_id() {
|
||
|
std::string token = get_token();
|
||
|
if (token.empty() || (token[0] != '$' && token[0] != '\\')) {
|
||
|
log_error("%s:%d: expected id string, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
return IdString(token);
|
||
|
}
|
||
|
|
||
|
std::string get_name() {
|
||
|
std::string res = get_token();
|
||
|
bool valid = true;
|
||
|
// Basic sanity check.
|
||
|
if (res.empty() || (!isalpha(res[0]) && res[0] != '_'))
|
||
|
valid = false;
|
||
|
for (char c: res)
|
||
|
if (!isalnum(c) && c != '_')
|
||
|
valid = false;
|
||
|
if (!valid)
|
||
|
log_error("%s:%d: expected name, got `%s`.\n", filename.c_str(), line_number, res.c_str());
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
std::string get_string() {
|
||
|
std::string token = get_token();
|
||
|
if (token.size() < 2 || token[0] != '"' || token[token.size()-1] != '"') {
|
||
|
log_error("%s:%d: expected string, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
return token.substr(1, token.size()-2);
|
||
|
}
|
||
|
|
||
|
bool peek_string() {
|
||
|
std::string token = peek_token();
|
||
|
return !token.empty() && token[0] == '"';
|
||
|
}
|
||
|
|
||
|
int get_int() {
|
||
|
std::string token = get_token();
|
||
|
char *endptr;
|
||
|
long res = strtol(token.c_str(), &endptr, 0);
|
||
|
if (token.empty() || *endptr || res > INT_MAX) {
|
||
|
log_error("%s:%d: expected int, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
double get_double() {
|
||
|
std::string token = get_token();
|
||
|
char *endptr;
|
||
|
double res = strtod(token.c_str(), &endptr);
|
||
|
if (token.empty() || *endptr) {
|
||
|
log_error("%s:%d: expected float, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
bool peek_int() {
|
||
|
std::string token = peek_token();
|
||
|
return !token.empty() && isdigit(token[0]);
|
||
|
}
|
||
|
|
||
|
void get_semi() {
|
||
|
std::string token = get_token();
|
||
|
if (token != ";") {
|
||
|
log_error("%s:%d: expected `;`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Const get_value() {
|
||
|
std::string token = peek_token();
|
||
|
if (!token.empty() && token[0] == '"') {
|
||
|
std::string s = get_string();
|
||
|
return Const(s);
|
||
|
} else {
|
||
|
return Const(get_int());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool enter_ifdef(bool polarity) {
|
||
|
bool res = active;
|
||
|
std::string name = get_name();
|
||
|
defines_unused.erase(name);
|
||
|
if (active) {
|
||
|
if (defines.count(name)) {
|
||
|
active = polarity;
|
||
|
} else {
|
||
|
active = !polarity;
|
||
|
}
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void enter_else(bool save) {
|
||
|
get_token();
|
||
|
active = !active && save;
|
||
|
}
|
||
|
|
||
|
void enter_option() {
|
||
|
std::string name = get_string();
|
||
|
Const val = get_value();
|
||
|
if (active) {
|
||
|
ram.opts[name].insert(val);
|
||
|
}
|
||
|
option_stack.push_back({name, val});
|
||
|
}
|
||
|
|
||
|
void exit_option() {
|
||
|
option_stack.pop_back();
|
||
|
}
|
||
|
|
||
|
Options get_options() {
|
||
|
Options res;
|
||
|
for (auto it: option_stack)
|
||
|
res[it.first] = it.second;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void enter_portoption() {
|
||
|
std::string name = get_string();
|
||
|
Const val = get_value();
|
||
|
if (active) {
|
||
|
port.portopts[name].insert(val);
|
||
|
}
|
||
|
portoption_stack.push_back({name, val});
|
||
|
}
|
||
|
|
||
|
void exit_portoption() {
|
||
|
portoption_stack.pop_back();
|
||
|
}
|
||
|
|
||
|
Options get_portoptions() {
|
||
|
Options res;
|
||
|
for (auto it: portoption_stack)
|
||
|
res[it.first] = it.second;
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
template<typename T> void add_cap(Caps<T> &caps, T val) {
|
||
|
if (active)
|
||
|
caps.push_back(Capability<T>(val, get_options(), get_portoptions()));
|
||
|
}
|
||
|
|
||
|
void parse_port_block() {
|
||
|
if (peek_token() == "{") {
|
||
|
get_token();
|
||
|
while (peek_token() != "}")
|
||
|
parse_port_item();
|
||
|
get_token();
|
||
|
} else {
|
||
|
parse_port_item();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void parse_ram_block() {
|
||
|
if (peek_token() == "{") {
|
||
|
get_token();
|
||
|
while (peek_token() != "}")
|
||
|
parse_ram_item();
|
||
|
get_token();
|
||
|
} else {
|
||
|
parse_ram_item();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void parse_top_block() {
|
||
|
if (peek_token() == "{") {
|
||
|
get_token();
|
||
|
while (peek_token() != "}")
|
||
|
parse_top_item();
|
||
|
get_token();
|
||
|
} else {
|
||
|
parse_top_item();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void parse_port_item() {
|
||
|
std::string token = get_token();
|
||
|
if (token == "ifdef") {
|
||
|
bool save = enter_ifdef(true);
|
||
|
parse_port_block();
|
||
|
if (peek_token() == "else") {
|
||
|
enter_else(save);
|
||
|
parse_port_block();
|
||
|
}
|
||
|
active = save;
|
||
|
} else if (token == "ifndef") {
|
||
|
bool save = enter_ifdef(false);
|
||
|
parse_port_block();
|
||
|
if (peek_token() == "else") {
|
||
|
enter_else(save);
|
||
|
parse_port_block();
|
||
|
}
|
||
|
active = save;
|
||
|
} else if (token == "option") {
|
||
|
enter_option();
|
||
|
parse_port_block();
|
||
|
exit_option();
|
||
|
} else if (token == "portoption") {
|
||
|
enter_portoption();
|
||
|
parse_port_block();
|
||
|
exit_portoption();
|
||
|
} else if (token == "clock") {
|
||
|
if (port.kind == PortKind::Ar) {
|
||
|
log_error("%s:%d: `clock` not allowed in async read port.\n", filename.c_str(), line_number);
|
||
|
}
|
||
|
ClockDef def;
|
||
|
token = get_token();
|
||
|
if (token == "anyedge") {
|
||
|
def.kind = ClkPolKind::Anyedge;
|
||
|
} else if (token == "posedge") {
|
||
|
def.kind = ClkPolKind::Posedge;
|
||
|
} else if (token == "negedge") {
|
||
|
def.kind = ClkPolKind::Negedge;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `posedge`, `negedge`, or `anyedge`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
if (peek_string()) {
|
||
|
def.name = get_string();
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.clock, def);
|
||
|
} else if (token == "clken") {
|
||
|
if (port.kind == PortKind::Ar) {
|
||
|
log_error("%s:%d: `clken` not allowed in async read port.\n", filename.c_str(), line_number);
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.clken, {});
|
||
|
} else if (token == "wrbe_separate") {
|
||
|
if (port.kind == PortKind::Ar || port.kind == PortKind::Sr) {
|
||
|
log_error("%s:%d: `wrbe_separate` not allowed in read port.\n", filename.c_str(), line_number);
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.wrbe_separate, {});
|
||
|
} else if (token == "width") {
|
||
|
PortWidthDef def;
|
||
|
token = peek_token();
|
||
|
bool is_rw = port.kind == PortKind::Srsw || port.kind == PortKind::Arsw;
|
||
|
if (token == "tied") {
|
||
|
get_token();
|
||
|
if (!is_rw)
|
||
|
log_error("%s:%d: `tied` only makes sense for read+write ports.\n", filename.c_str(), line_number);
|
||
|
while (peek_int())
|
||
|
def.wr_widths.push_back(get_int());
|
||
|
def.tied = true;
|
||
|
} else if (token == "mix") {
|
||
|
get_token();
|
||
|
if (!is_rw)
|
||
|
log_error("%s:%d: `mix` only makes sense for read+write ports.\n", filename.c_str(), line_number);
|
||
|
while (peek_int())
|
||
|
def.wr_widths.push_back(get_int());
|
||
|
def.rd_widths = def.wr_widths;
|
||
|
def.tied = false;
|
||
|
} else if (token == "rd") {
|
||
|
get_token();
|
||
|
if (!is_rw)
|
||
|
log_error("%s:%d: `rd` only makes sense for read+write ports.\n", filename.c_str(), line_number);
|
||
|
do {
|
||
|
def.rd_widths.push_back(get_int());
|
||
|
} while (peek_int());
|
||
|
eat_token("wr");
|
||
|
do {
|
||
|
def.wr_widths.push_back(get_int());
|
||
|
} while (peek_int());
|
||
|
def.tied = false;
|
||
|
} else if (token == "wr") {
|
||
|
get_token();
|
||
|
if (!is_rw)
|
||
|
log_error("%s:%d: `wr` only makes sense for read+write ports.\n", filename.c_str(), line_number);
|
||
|
do {
|
||
|
def.wr_widths.push_back(get_int());
|
||
|
} while (peek_int());
|
||
|
eat_token("rd");
|
||
|
do {
|
||
|
def.rd_widths.push_back(get_int());
|
||
|
} while (peek_int());
|
||
|
def.tied = false;
|
||
|
} else {
|
||
|
do {
|
||
|
def.wr_widths.push_back(get_int());
|
||
|
} while (peek_int());
|
||
|
def.tied = true;
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.width, def);
|
||
|
} else if (token == "rden") {
|
||
|
if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
|
||
|
log_error("%s:%d: `rden` only allowed on sync read ports.\n", filename.c_str(), line_number);
|
||
|
get_semi();
|
||
|
add_cap(port.rden, {});
|
||
|
} else if (token == "rdwr") {
|
||
|
if (port.kind != PortKind::Srsw)
|
||
|
log_error("%s:%d: `rdwr` only allowed on sync read+write ports.\n", filename.c_str(), line_number);
|
||
|
RdWrKind kind;
|
||
|
token = get_token();
|
||
|
if (token == "undefined") {
|
||
|
kind = RdWrKind::Undefined;
|
||
|
} else if (token == "no_change") {
|
||
|
kind = RdWrKind::NoChange;
|
||
|
} else if (token == "new") {
|
||
|
kind = RdWrKind::New;
|
||
|
} else if (token == "old") {
|
||
|
kind = RdWrKind::Old;
|
||
|
} else if (token == "new_only") {
|
||
|
kind = RdWrKind::NewOnly;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `undefined`, `new`, `old`, `new_only`, or `no_change`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.rdwr, kind);
|
||
|
} else if (token == "rdinit") {
|
||
|
if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
|
||
|
log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str());
|
||
|
ResetValKind kind;
|
||
|
token = get_token();
|
||
|
if (token == "none") {
|
||
|
kind = ResetValKind::None;
|
||
|
} else if (token == "zero") {
|
||
|
kind = ResetValKind::Zero;
|
||
|
} else if (token == "any") {
|
||
|
kind = ResetValKind::Any;
|
||
|
} else if (token == "no_undef") {
|
||
|
kind = ResetValKind::NoUndef;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `none`, `zero`, `any`, or `no_undef`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.rdinit, kind);
|
||
|
} else if (token == "rdarst") {
|
||
|
if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
|
||
|
log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str());
|
||
|
ResetValKind kind;
|
||
|
token = get_token();
|
||
|
if (token == "none") {
|
||
|
kind = ResetValKind::None;
|
||
|
} else if (token == "zero") {
|
||
|
kind = ResetValKind::Zero;
|
||
|
} else if (token == "any") {
|
||
|
kind = ResetValKind::Any;
|
||
|
} else if (token == "no_undef") {
|
||
|
kind = ResetValKind::NoUndef;
|
||
|
} else if (token == "init") {
|
||
|
kind = ResetValKind::Init;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.rdarst, kind);
|
||
|
} else if (token == "rdsrst") {
|
||
|
if (port.kind != PortKind::Sr && port.kind != PortKind::Srsw)
|
||
|
log_error("%s:%d: `%s` only allowed on sync read ports.\n", filename.c_str(), line_number, token.c_str());
|
||
|
SrstDef def;
|
||
|
token = get_token();
|
||
|
if (token == "none") {
|
||
|
def.val = ResetValKind::None;
|
||
|
} else if (token == "zero") {
|
||
|
def.val = ResetValKind::Zero;
|
||
|
} else if (token == "any") {
|
||
|
def.val = ResetValKind::Any;
|
||
|
} else if (token == "no_undef") {
|
||
|
def.val = ResetValKind::NoUndef;
|
||
|
} else if (token == "init") {
|
||
|
def.val = ResetValKind::Init;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `none`, `zero`, `any`, `no_undef`, or `init`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
if (def.val == ResetValKind::None) {
|
||
|
def.kind = SrstKind::None;
|
||
|
} else {
|
||
|
token = get_token();
|
||
|
if (token == "ungated") {
|
||
|
def.kind = SrstKind::Ungated;
|
||
|
} else if (token == "gated_clken") {
|
||
|
def.kind = SrstKind::GatedClkEn;
|
||
|
} else if (token == "gated_rden") {
|
||
|
def.kind = SrstKind::GatedRdEn;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `ungated`, `gated_clken` or `gated_rden`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
}
|
||
|
def.block_wr = false;
|
||
|
if (peek_token() == "block_wr") {
|
||
|
get_token();
|
||
|
def.block_wr = true;
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.rdsrst, def);
|
||
|
} else if (token == "wrprio") {
|
||
|
if (port.kind == PortKind::Ar || port.kind == PortKind::Sr)
|
||
|
log_error("%s:%d: `wrprio` only allowed on write ports.\n", filename.c_str(), line_number);
|
||
|
do {
|
||
|
add_cap(port.wrprio, get_string());
|
||
|
} while (peek_string());
|
||
|
get_semi();
|
||
|
} else if (token == "wrtrans") {
|
||
|
if (port.kind == PortKind::Ar || port.kind == PortKind::Sr)
|
||
|
log_error("%s:%d: `wrtrans` only allowed on write ports.\n", filename.c_str(), line_number);
|
||
|
token = peek_token();
|
||
|
RawWrTransDef def;
|
||
|
if (token == "all") {
|
||
|
def.target_kind = WrTransTargetKind::All;
|
||
|
get_token();
|
||
|
} else {
|
||
|
def.target_kind = WrTransTargetKind::Group;
|
||
|
def.target_group = get_string();
|
||
|
}
|
||
|
token = get_token();
|
||
|
if (token == "new") {
|
||
|
def.kind = WrTransKind::New;
|
||
|
} else if (token == "old") {
|
||
|
def.kind = WrTransKind::Old;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `new` or `old`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(port.wrtrans, def);
|
||
|
} else if (token == "forbid") {
|
||
|
get_semi();
|
||
|
add_cap(port.forbid, {});
|
||
|
} else if (token == "optional") {
|
||
|
get_semi();
|
||
|
add_cap(port.optional, {});
|
||
|
} else if (token == "optional_rw") {
|
||
|
get_semi();
|
||
|
add_cap(port.optional_rw, {});
|
||
|
} else if (token == "") {
|
||
|
log_error("%s:%d: unexpected EOF while parsing port item.\n", filename.c_str(), line_number);
|
||
|
} else {
|
||
|
log_error("%s:%d: unknown port-level item `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void parse_ram_item() {
|
||
|
std::string token = get_token();
|
||
|
if (token == "ifdef") {
|
||
|
bool save = enter_ifdef(true);
|
||
|
parse_ram_block();
|
||
|
if (peek_token() == "else") {
|
||
|
enter_else(save);
|
||
|
parse_ram_block();
|
||
|
}
|
||
|
active = save;
|
||
|
} else if (token == "ifndef") {
|
||
|
bool save = enter_ifdef(false);
|
||
|
parse_ram_block();
|
||
|
if (peek_token() == "else") {
|
||
|
enter_else(save);
|
||
|
parse_ram_block();
|
||
|
}
|
||
|
active = save;
|
||
|
} else if (token == "option") {
|
||
|
enter_option();
|
||
|
parse_ram_block();
|
||
|
exit_option();
|
||
|
} else if (token == "prune_rom") {
|
||
|
get_semi();
|
||
|
add_cap(ram.prune_rom, {});
|
||
|
} else if (token == "forbid") {
|
||
|
get_semi();
|
||
|
add_cap(ram.forbid, {});
|
||
|
} else if (token == "abits") {
|
||
|
int val = get_int();
|
||
|
if (val < 0)
|
||
|
log_error("%s:%d: abits %d nagative.\n", filename.c_str(), line_number, val);
|
||
|
get_semi();
|
||
|
add_cap(ram.abits, val);
|
||
|
} else if (token == "width") {
|
||
|
WidthsDef def;
|
||
|
int w = get_int();
|
||
|
if (w <= 0)
|
||
|
log_error("%s:%d: width %d not positive.\n", filename.c_str(), line_number, w);
|
||
|
def.widths.push_back(w);
|
||
|
def.mode = WidthMode::Single;
|
||
|
get_semi();
|
||
|
add_cap(ram.widths, def);
|
||
|
} else if (token == "widths") {
|
||
|
WidthsDef def;
|
||
|
int last = 0;
|
||
|
do {
|
||
|
int w = get_int();
|
||
|
if (w <= 0)
|
||
|
log_error("%s:%d: width %d not positive.\n", filename.c_str(), line_number, w);
|
||
|
if (w < last * 2)
|
||
|
log_error("%s:%d: width %d smaller than %d required for progression.\n", filename.c_str(), line_number, w, last * 2);
|
||
|
last = w;
|
||
|
def.widths.push_back(w);
|
||
|
} while(peek_int());
|
||
|
token = get_token();
|
||
|
if (token == "global") {
|
||
|
def.mode = WidthMode::Global;
|
||
|
} else if (token == "per_port") {
|
||
|
def.mode = WidthMode::PerPort;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `global`, or `per_port`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(ram.widths, def);
|
||
|
} else if (token == "resource") {
|
||
|
ResourceDef def;
|
||
|
def.name = get_string();
|
||
|
if (peek_int())
|
||
|
def.count = get_int();
|
||
|
else
|
||
|
def.count = 1;
|
||
|
get_semi();
|
||
|
add_cap(ram.resource, def);
|
||
|
} else if (token == "cost") {
|
||
|
add_cap(ram.cost, get_double());
|
||
|
get_semi();
|
||
|
} else if (token == "widthscale") {
|
||
|
if (peek_int()) {
|
||
|
add_cap(ram.widthscale, get_double());
|
||
|
} else {
|
||
|
add_cap(ram.widthscale, 0.0);
|
||
|
}
|
||
|
get_semi();
|
||
|
} else if (token == "byte") {
|
||
|
int val = get_int();
|
||
|
if (val <= 0)
|
||
|
log_error("%s:%d: byte width %d not positive.\n", filename.c_str(), line_number, val);
|
||
|
add_cap(ram.byte, val);
|
||
|
get_semi();
|
||
|
} else if (token == "init") {
|
||
|
MemoryInitKind kind;
|
||
|
token = get_token();
|
||
|
if (token == "zero") {
|
||
|
kind = MemoryInitKind::Zero;
|
||
|
} else if (token == "any") {
|
||
|
kind = MemoryInitKind::Any;
|
||
|
} else if (token == "no_undef") {
|
||
|
kind = MemoryInitKind::NoUndef;
|
||
|
} else if (token == "none") {
|
||
|
kind = MemoryInitKind::None;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `zero`, `any`, `none`, or `no_undef`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
get_semi();
|
||
|
add_cap(ram.init, kind);
|
||
|
} else if (token == "style") {
|
||
|
do {
|
||
|
std::string val = get_string();
|
||
|
for (auto &c: val)
|
||
|
c = std::tolower(c);
|
||
|
add_cap(ram.style, val);
|
||
|
} while (peek_string());
|
||
|
get_semi();
|
||
|
} else if (token == "port") {
|
||
|
port = PortGroupDef();
|
||
|
token = get_token();
|
||
|
if (token == "ar") {
|
||
|
port.kind = PortKind::Ar;
|
||
|
} else if (token == "sr") {
|
||
|
port.kind = PortKind::Sr;
|
||
|
} else if (token == "sw") {
|
||
|
port.kind = PortKind::Sw;
|
||
|
} else if (token == "arsw") {
|
||
|
port.kind = PortKind::Arsw;
|
||
|
} else if (token == "srsw") {
|
||
|
port.kind = PortKind::Srsw;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `ar`, `sr`, `sw`, `arsw`, or `srsw`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
do {
|
||
|
port.names.push_back(get_string());
|
||
|
} while (peek_string());
|
||
|
parse_port_block();
|
||
|
if (active)
|
||
|
add_cap(ram.ports, port);
|
||
|
} else if (token == "") {
|
||
|
log_error("%s:%d: unexpected EOF while parsing ram item.\n", filename.c_str(), line_number);
|
||
|
} else {
|
||
|
log_error("%s:%d: unknown ram-level item `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void parse_top_item() {
|
||
|
std::string token = get_token();
|
||
|
if (token == "ifdef") {
|
||
|
bool save = enter_ifdef(true);
|
||
|
parse_top_block();
|
||
|
if (peek_token() == "else") {
|
||
|
enter_else(save);
|
||
|
parse_top_block();
|
||
|
}
|
||
|
active = save;
|
||
|
} else if (token == "ifndef") {
|
||
|
bool save = enter_ifdef(false);
|
||
|
parse_top_block();
|
||
|
if (peek_token() == "else") {
|
||
|
enter_else(save);
|
||
|
parse_top_block();
|
||
|
}
|
||
|
active = save;
|
||
|
} else if (token == "ram") {
|
||
|
int orig_line = line_number;
|
||
|
ram = RamDef();
|
||
|
token = get_token();
|
||
|
if (token == "distributed") {
|
||
|
ram.kind = RamKind::Distributed;
|
||
|
} else if (token == "block") {
|
||
|
ram.kind = RamKind::Block;
|
||
|
} else if (token == "huge") {
|
||
|
ram.kind = RamKind::Huge;
|
||
|
} else {
|
||
|
log_error("%s:%d: expected `distributed`, `block`, or `huge`, got `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
ram.id = get_id();
|
||
|
parse_ram_block();
|
||
|
if (active) {
|
||
|
compile_ram(orig_line);
|
||
|
}
|
||
|
} else if (token == "") {
|
||
|
log_error("%s:%d: unexpected EOF while parsing top item.\n", filename.c_str(), line_number);
|
||
|
} else {
|
||
|
log_error("%s:%d: unknown top-level item `%s`.\n", filename.c_str(), line_number, token.c_str());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool opts_ok(const Options &def, const Options &var) {
|
||
|
for (auto &it: def)
|
||
|
if (var.at(it.first) != it.second)
|
||
|
return false;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
template<typename T> const T *find_single_cap(const Caps<T> &caps, const Options &opts, const Options &portopts, const char *name) {
|
||
|
const T *res = nullptr;
|
||
|
for (auto &cap: caps) {
|
||
|
if (!opts_ok(cap.opts, opts))
|
||
|
continue;
|
||
|
if (!opts_ok(cap.portopts, portopts))
|
||
|
continue;
|
||
|
if (res)
|
||
|
log_error("%s:%d: duplicate %s cap.\n", filename.c_str(), line_number, name);
|
||
|
res = &cap.val;
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
std::vector<Options> make_opt_combinations(const dict<std::string, pool<Const>> &opts) {
|
||
|
std::vector<Options> res;
|
||
|
res.push_back(Options());
|
||
|
for (auto &it: opts) {
|
||
|
std::vector<Options> new_res;
|
||
|
for (auto &val: it.second) {
|
||
|
for (Options o: res) {
|
||
|
o[it.first] = val;
|
||
|
new_res.push_back(o);
|
||
|
}
|
||
|
}
|
||
|
res = new_res;
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
void compile_portgroup(Ram &cram, PortGroupDef &pdef, dict<std::string, int> &clk_ids, const dict<std::string, int> &port_ids, int orig_line) {
|
||
|
PortGroup grp;
|
||
|
grp.optional = find_single_cap(pdef.optional, cram.options, Options(), "optional");
|
||
|
grp.optional_rw = find_single_cap(pdef.optional_rw, cram.options, Options(), "optional_rw");
|
||
|
grp.names = pdef.names;
|
||
|
for (auto portopts: make_opt_combinations(pdef.portopts)) {
|
||
|
bool forbidden = false;
|
||
|
for (auto &fdef: ram.forbid) {
|
||
|
if (opts_ok(fdef.opts, cram.options) && opts_ok(fdef.portopts, portopts)) {
|
||
|
forbidden = true;
|
||
|
}
|
||
|
}
|
||
|
if (forbidden)
|
||
|
continue;
|
||
|
PortVariant var;
|
||
|
var.options = portopts;
|
||
|
var.kind = pdef.kind;
|
||
|
if (pdef.kind != PortKind::Ar) {
|
||
|
const ClockDef *cdef = find_single_cap(pdef.clock, cram.options, portopts, "clock");
|
||
|
if (!cdef)
|
||
|
log_error("%s:%d: missing clock capability.\n", filename.c_str(), orig_line);
|
||
|
var.clk_pol = cdef->kind;
|
||
|
if (cdef->name.empty()) {
|
||
|
var.clk_shared = -1;
|
||
|
} else {
|
||
|
auto it = clk_ids.find(cdef->name);
|
||
|
bool anyedge = cdef->kind == ClkPolKind::Anyedge;
|
||
|
if (it == clk_ids.end()) {
|
||
|
clk_ids[cdef->name] = var.clk_shared = GetSize(cram.shared_clocks);
|
||
|
RamClock clk;
|
||
|
clk.name = cdef->name;
|
||
|
clk.anyedge = anyedge;
|
||
|
cram.shared_clocks.push_back(clk);
|
||
|
} else {
|
||
|
var.clk_shared = it->second;
|
||
|
if (cram.shared_clocks[var.clk_shared].anyedge != anyedge) {
|
||
|
log_error("%s:%d: named clock \"%s\" used with both posedge/negedge and anyedge clocks.\n", filename.c_str(), orig_line, cdef->name.c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var.clk_en = find_single_cap(pdef.clken, cram.options, portopts, "clken");
|
||
|
}
|
||
|
const PortWidthDef *wdef = find_single_cap(pdef.width, cram.options, portopts, "width");
|
||
|
if (wdef) {
|
||
|
if (cram.width_mode != WidthMode::PerPort)
|
||
|
log_error("%s:%d: per-port width doesn't make sense for tied dbits.\n", filename.c_str(), orig_line);
|
||
|
compile_widths(var, cram.dbits, *wdef);
|
||
|
} else {
|
||
|
var.width_tied = true;
|
||
|
var.min_wr_wide_log2 = 0;
|
||
|
var.min_rd_wide_log2 = 0;
|
||
|
var.max_wr_wide_log2 = GetSize(cram.dbits) - 1;
|
||
|
var.max_rd_wide_log2 = GetSize(cram.dbits) - 1;
|
||
|
}
|
||
|
if (pdef.kind == PortKind::Srsw || pdef.kind == PortKind::Sr) {
|
||
|
const RdWrKind *rdwr = find_single_cap(pdef.rdwr, cram.options, portopts, "rdwr");
|
||
|
var.rdwr = rdwr ? *rdwr : RdWrKind::Undefined;
|
||
|
var.rd_en = find_single_cap(pdef.rden, cram.options, portopts, "rden");
|
||
|
const ResetValKind *iv = find_single_cap(pdef.rdinit, cram.options, portopts, "rdinit");
|
||
|
var.rdinitval = iv ? *iv : ResetValKind::None;
|
||
|
const ResetValKind *arv = find_single_cap(pdef.rdarst, cram.options, portopts, "rdarst");
|
||
|
var.rdarstval = arv ? *arv : ResetValKind::None;
|
||
|
const SrstDef *srv = find_single_cap(pdef.rdsrst, cram.options, portopts, "rdsrst");
|
||
|
if (srv) {
|
||
|
var.rdsrstval = srv->val;
|
||
|
var.rdsrstmode = srv->kind;
|
||
|
var.rdsrst_block_wr = srv->block_wr;
|
||
|
if (srv->kind == SrstKind::GatedClkEn && !var.clk_en)
|
||
|
log_error("%s:%d: `gated_clken` used without `clken`.\n", filename.c_str(), orig_line);
|
||
|
if (srv->kind == SrstKind::GatedRdEn && !var.rd_en)
|
||
|
log_error("%s:%d: `gated_rden` used without `rden`.\n", filename.c_str(), orig_line);
|
||
|
} else {
|
||
|
var.rdsrstval = ResetValKind::None;
|
||
|
var.rdsrstmode = SrstKind::None;
|
||
|
var.rdsrst_block_wr = false;
|
||
|
}
|
||
|
if (var.rdarstval == ResetValKind::Init || var.rdsrstval == ResetValKind::Init) {
|
||
|
if (var.rdinitval != ResetValKind::Any && var.rdinitval != ResetValKind::NoUndef) {
|
||
|
log_error("%s:%d: reset value `init` has to be paired with `any` or `no_undef` initial value.\n", filename.c_str(), orig_line);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
var.wrbe_separate = find_single_cap(pdef.wrbe_separate, cram.options, portopts, "wrbe_separate");
|
||
|
if (var.wrbe_separate && cram.byte == 0) {
|
||
|
log_error("%s:%d: `wrbe_separate` used without `byte`.\n", filename.c_str(), orig_line);
|
||
|
}
|
||
|
for (auto &def: pdef.wrprio) {
|
||
|
if (!opts_ok(def.opts, cram.options))
|
||
|
continue;
|
||
|
if (!opts_ok(def.portopts, portopts))
|
||
|
continue;
|
||
|
var.wrprio.push_back(port_ids.at(def.val));
|
||
|
}
|
||
|
for (auto &def: pdef.wrtrans) {
|
||
|
if (!opts_ok(def.opts, cram.options))
|
||
|
continue;
|
||
|
if (!opts_ok(def.portopts, portopts))
|
||
|
continue;
|
||
|
WrTransDef tdef;
|
||
|
tdef.target_kind = def.val.target_kind;
|
||
|
if (def.val.target_kind == WrTransTargetKind::Group)
|
||
|
tdef.target_group = port_ids.at(def.val.target_group);
|
||
|
tdef.kind = def.val.kind;
|
||
|
var.wrtrans.push_back(tdef);
|
||
|
}
|
||
|
grp.variants.push_back(var);
|
||
|
}
|
||
|
if (grp.variants.empty()) {
|
||
|
log_error("%s:%d: all port option combinations are forbidden.\n", filename.c_str(), orig_line);
|
||
|
}
|
||
|
cram.port_groups.push_back(grp);
|
||
|
}
|
||
|
|
||
|
void compile_ram(int orig_line) {
|
||
|
if (ram.abits.empty())
|
||
|
log_error("%s:%d: `dims` capability should be specified.\n", filename.c_str(), orig_line);
|
||
|
if (ram.widths.empty())
|
||
|
log_error("%s:%d: `widths` capability should be specified.\n", filename.c_str(), orig_line);
|
||
|
if (ram.ports.empty())
|
||
|
log_error("%s:%d: at least one port group should be specified.\n", filename.c_str(), orig_line);
|
||
|
for (auto opts: make_opt_combinations(ram.opts)) {
|
||
|
bool forbidden = false;
|
||
|
for (auto &fdef: ram.forbid) {
|
||
|
if (opts_ok(fdef.opts, opts)) {
|
||
|
forbidden = true;
|
||
|
}
|
||
|
}
|
||
|
if (forbidden)
|
||
|
continue;
|
||
|
Ram cram;
|
||
|
cram.id = ram.id;
|
||
|
cram.kind = ram.kind;
|
||
|
cram.options = opts;
|
||
|
cram.prune_rom = find_single_cap(ram.prune_rom, opts, Options(), "prune_rom");
|
||
|
const int *abits = find_single_cap(ram.abits, opts, Options(), "abits");
|
||
|
if (!abits)
|
||
|
continue;
|
||
|
cram.abits = *abits;
|
||
|
const WidthsDef *widths = find_single_cap(ram.widths, opts, Options(), "widths");
|
||
|
if (!widths)
|
||
|
continue;
|
||
|
cram.dbits = widths->widths;
|
||
|
cram.width_mode = widths->mode;
|
||
|
const ResourceDef *resource = find_single_cap(ram.resource, opts, Options(), "resource");
|
||
|
if (resource) {
|
||
|
cram.resource_name = resource->name;
|
||
|
cram.resource_count = resource->count;
|
||
|
} else {
|
||
|
cram.resource_count = 1;
|
||
|
}
|
||
|
cram.cost = 0;
|
||
|
for (auto &cap: ram.cost) {
|
||
|
if (opts_ok(cap.opts, opts))
|
||
|
cram.cost += cap.val;
|
||
|
}
|
||
|
const double *widthscale = find_single_cap(ram.widthscale, opts, Options(), "widthscale");
|
||
|
if (widthscale)
|
||
|
cram.widthscale = *widthscale ? *widthscale : cram.cost;
|
||
|
else
|
||
|
cram.widthscale = 0;
|
||
|
const int *byte = find_single_cap(ram.byte, opts, Options(), "byte");
|
||
|
cram.byte = byte ? *byte : 0;
|
||
|
if (GetSize(cram.dbits) - 1 > cram.abits)
|
||
|
log_error("%s:%d: abits %d too small for dbits progression.\n", filename.c_str(), line_number, cram.abits);
|
||
|
validate_byte(widths->widths, cram.byte);
|
||
|
const MemoryInitKind *ik = find_single_cap(ram.init, opts, Options(), "init");
|
||
|
cram.init = ik ? *ik : MemoryInitKind::None;
|
||
|
for (auto &sdef: ram.style)
|
||
|
if (opts_ok(sdef.opts, opts))
|
||
|
cram.style.push_back(sdef.val);
|
||
|
dict<std::string, int> port_ids;
|
||
|
int ctr = 0;
|
||
|
for (auto &pdef: ram.ports) {
|
||
|
if (!opts_ok(pdef.opts, opts))
|
||
|
continue;
|
||
|
for (auto &name: pdef.val.names)
|
||
|
port_ids[name] = ctr;
|
||
|
ctr++;
|
||
|
}
|
||
|
dict<std::string, int> clk_ids;
|
||
|
for (auto &pdef: ram.ports) {
|
||
|
if (!opts_ok(pdef.opts, opts))
|
||
|
continue;
|
||
|
compile_portgroup(cram, pdef.val, clk_ids, port_ids, orig_line);
|
||
|
}
|
||
|
lib.rams.push_back(cram);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void validate_byte(const std::vector<int> &widths, int byte) {
|
||
|
if (byte == 0)
|
||
|
return;
|
||
|
if (byte >= widths.back())
|
||
|
return;
|
||
|
if (widths[0] % byte == 0) {
|
||
|
for (int j = 1; j < GetSize(widths); j++)
|
||
|
if (widths[j] % byte != 0)
|
||
|
log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename.c_str(), line_number, byte);
|
||
|
return;
|
||
|
}
|
||
|
for (int i = 0; i < GetSize(widths); i++) {
|
||
|
if (widths[i] == byte) {
|
||
|
for (int j = i + 1; j < GetSize(widths); j++)
|
||
|
if (widths[j] % byte != 0)
|
||
|
log_error("%s:%d: width progression past byte width %d is not divisible.\n", filename.c_str(), line_number, byte);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
log_error("%s:%d: byte width %d invalid for dbits.\n", filename.c_str(), line_number, byte);
|
||
|
}
|
||
|
|
||
|
void compile_widths(PortVariant &var, const std::vector<int> &widths, const PortWidthDef &width) {
|
||
|
var.width_tied = width.tied;
|
||
|
auto wr_widths = compile_widthdef(widths, width.wr_widths);
|
||
|
var.min_wr_wide_log2 = wr_widths.first;
|
||
|
var.max_wr_wide_log2 = wr_widths.second;
|
||
|
if (width.tied) {
|
||
|
var.min_rd_wide_log2 = wr_widths.first;
|
||
|
var.max_rd_wide_log2 = wr_widths.second;
|
||
|
} else {
|
||
|
auto rd_widths = compile_widthdef(widths, width.rd_widths);
|
||
|
var.min_rd_wide_log2 = rd_widths.first;
|
||
|
var.max_rd_wide_log2 = rd_widths.second;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::pair<int, int> compile_widthdef(const std::vector<int> &dbits, const std::vector<int> &widths) {
|
||
|
if (widths.empty())
|
||
|
return {0, GetSize(dbits) - 1};
|
||
|
for (int i = 0; i < GetSize(dbits); i++) {
|
||
|
if (dbits[i] == widths[0]) {
|
||
|
for (int j = 0; j < GetSize(widths); j++) {
|
||
|
if (i+j >= GetSize(dbits) || dbits[i+j] != widths[j]) {
|
||
|
log_error("%s:%d: port width %d doesn't match dbits progression.\n", filename.c_str(), line_number, widths[j]);
|
||
|
}
|
||
|
}
|
||
|
return {i, i + GetSize(widths) - 1};
|
||
|
}
|
||
|
}
|
||
|
log_error("%s:%d: port width %d invalid for dbits.\n", filename.c_str(), line_number, widths[0]);
|
||
|
}
|
||
|
|
||
|
void parse() {
|
||
|
while (peek_token() != "")
|
||
|
parse_top_item();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
PRIVATE_NAMESPACE_END
|
||
|
|
||
|
Library MemLibrary::parse_library(const std::vector<std::string> &filenames, const pool<std::string> &defines) {
|
||
|
Library res;
|
||
|
pool<std::string> defines_unused = defines;
|
||
|
for (auto &file: filenames) {
|
||
|
Parser(file, res, defines, defines_unused);
|
||
|
}
|
||
|
for (auto def: defines_unused) {
|
||
|
log_warning("define %s not used in the library.\n", def.c_str());
|
||
|
}
|
||
|
return res;
|
||
|
}
|