diff options
Diffstat (limited to 'passes/memory/memlib.cc')
-rw-r--r-- | passes/memory/memlib.cc | 1101 |
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; +} |