aboutsummaryrefslogtreecommitdiffstats
path: root/passes/memory/memlib.cc
diff options
context:
space:
mode:
Diffstat (limited to 'passes/memory/memlib.cc')
-rw-r--r--passes/memory/memlib.cc1101
1 files changed, 1101 insertions, 0 deletions
diff --git a/passes/memory/memlib.cc b/passes/memory/memlib.cc
new file mode 100644
index 000000000..8a7adc9ac
--- /dev/null
+++ b/passes/memory/memlib.cc
@@ -0,0 +1,1101 @@
+/*
+ * 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;
+}