* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
- *
+ *
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
- *
+ *
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
SigBit sig_clock;
SigSpec sig_addr, sig_data, sig_en;
bool effective_clkpol;
+ bool make_transp;
+ bool make_outreg;
int mapped_port;
};
struct bram_t {
IdString name;
+ int variant;
+
int groups, abits, dbits, init;
vector<int> ports, wrmode, enable, transp, clocks, clkpol;
+ void dump_config() const
+ {
+ log(" bram %s # variant %d\n", log_id(name), variant);
+ log(" init %d\n", init);
+ log(" abits %d\n", abits);
+ log(" dbits %d\n", dbits);
+ log(" groups %d\n", groups);
+
+ log(" ports "); for (int v : ports) log("%4d", v); log("\n");
+ log(" wrmode"); for (int v : wrmode) log("%4d", v); log("\n");
+ log(" enable"); for (int v : enable) log("%4d", v); log("\n");
+ log(" transp"); for (int v : transp) log("%4d", v); log("\n");
+ log(" clocks"); for (int v : clocks) log("%4d", v); log("\n");
+ log(" clkpol"); for (int v : clkpol) log("%4d", v); log("\n");
+ log(" endbram\n");
+ }
+
+ void check_vectors() const
+ {
+ if (groups != GetSize(ports)) log_error("Bram %s variant %d has %d groups but only %d entries in 'ports'.\n", log_id(name), variant, groups, GetSize(ports));
+ if (groups != GetSize(wrmode)) log_error("Bram %s variant %d has %d groups but only %d entries in 'wrmode'.\n", log_id(name), variant, groups, GetSize(wrmode));
+ if (groups != GetSize(enable)) log_error("Bram %s variant %d has %d groups but only %d entries in 'enable'.\n", log_id(name), variant, groups, GetSize(enable));
+ if (groups != GetSize(transp)) log_error("Bram %s variant %d has %d groups but only %d entries in 'transp'.\n", log_id(name), variant, groups, GetSize(transp));
+ if (groups != GetSize(clocks)) log_error("Bram %s variant %d has %d groups but only %d entries in 'clocks'.\n", log_id(name), variant, groups, GetSize(clocks));
+ if (groups != GetSize(clkpol)) log_error("Bram %s variant %d has %d groups but only %d entries in 'clkpol'.\n", log_id(name), variant, groups, GetSize(clkpol));
+ }
+
vector<portinfo_t> make_portinfos() const
{
vector<portinfo_t> portinfos;
- for (int i = 0; i < groups && i < GetSize(ports); i++)
+ for (int i = 0; i < groups; i++)
for (int j = 0; j < ports[i]; j++) {
portinfo_t pi;
pi.group = i;
pi.index = j;
pi.dupidx = 0;
- pi.wrmode = i < GetSize(wrmode) ? wrmode[i] : 0;
- pi.enable = i < GetSize(enable) ? enable[i] : 0;
- pi.transp = i < GetSize(transp) ? transp[i] : 0;
- pi.clocks = i < GetSize(clocks) ? clocks[i] : 0;
- pi.clkpol = i < GetSize(clkpol) ? clkpol[i] : 0;
+ pi.wrmode = wrmode[i];
+ pi.enable = enable[i];
+ pi.transp = transp[i];
+ pi.clocks = clocks[i];
+ pi.clkpol = clkpol[i];
pi.mapped_port = -1;
+ pi.make_transp = false;
+ pi.make_outreg = false;
+ pi.effective_clkpol = false;
portinfos.push_back(pi);
}
return portinfos;
}
+
+ void find_variant_params(dict<IdString, Const> &variant_params, const bram_t &other) const
+ {
+ log_assert(name == other.name);
+
+ if (groups != other.groups)
+ log_error("Bram %s variants %d and %d have different values for 'groups'.\n", log_id(name), variant, other.variant);
+
+ if (abits != other.abits)
+ variant_params["\\CFG_ABITS"] = abits;
+ if (dbits != other.dbits)
+ variant_params["\\CFG_DBITS"] = dbits;
+ if (init != other.init)
+ variant_params["\\CFG_INIT"] = init;
+
+ for (int i = 0; i < groups; i++)
+ {
+ if (ports[i] != other.ports[i])
+ log_error("Bram %s variants %d and %d have different number of %c-ports.\n", log_id(name), variant, other.variant, 'A'+i);
+ if (wrmode[i] != other.wrmode[i])
+ variant_params[stringf("\\CFG_WRMODE_%c", 'A' + i)] = wrmode[i];
+ if (enable[i] != other.enable[i])
+ variant_params[stringf("\\CFG_ENABLE_%c", 'A' + i)] = enable[i];
+ if (transp[i] != other.transp[i])
+ variant_params[stringf("\\CFG_TRANSP_%c", 'A' + i)] = transp[i];
+ if (clocks[i] != other.clocks[i])
+ variant_params[stringf("\\CFG_CLOCKS_%c", 'A' + i)] = clocks[i];
+ if (clkpol[i] != other.clkpol[i])
+ variant_params[stringf("\\CFG_CLKPOL_%c", 'A' + i)] = clkpol[i];
+ }
+ }
};
struct match_t {
IdString name;
dict<string, int> min_limits, max_limits;
- bool or_next_if_better;
- int shuffle_enable;
+ bool or_next_if_better, make_transp, make_outreg;
+ char shuffle_enable;
};
- dict<IdString, bram_t> brams;
+ dict<IdString, vector<bram_t>> brams;
vector<match_t> matches;
std::ifstream infile;
vector<string> tokens;
+ vector<string> labels;
int linecount;
void syntax_error()
bool next_line()
{
- linecount++;
- tokens.clear();
string line;
while (std::getline(infile, line)) {
- log("> %s\n", line.c_str());
+ tokens.clear();
+ labels.clear();
+ linecount++;
for (string tok = next_token(line); !tok.empty(); tok = next_token(line)) {
+ if (tok[0] == '@') {
+ labels.push_back(tok.substr(1));
+ continue;
+ }
if (tok[0] == '#')
break;
tokens.push_back(tok);
void parse_bram()
{
+ IdString bram_name = RTLIL::escape_id(tokens[1]);
+
if (GetSize(tokens) != 2)
syntax_error();
- bram_t data;
- data.name = RTLIL::escape_id(tokens[1]);
+ vector<vector<string>> lines_nolabels;
+ std::map<string, vector<vector<string>>> lines_labels;
while (next_line())
{
- if (GetSize(tokens) == 1 && tokens[0] == "endbram") {
- brams[data.name] = data;
- return;
- }
+ if (GetSize(tokens) == 1 && tokens[0] == "endbram")
+ break;
+ if (labels.empty())
+ lines_nolabels.push_back(tokens);
+ for (auto lab : labels)
+ lines_labels[lab].push_back(tokens);
+ }
- if (parse_single_int("groups", data.groups))
- continue;
+ std::map<string, vector<vector<string>>> variant_lines;
- if (parse_single_int("abits", data.abits))
- continue;
+ if (lines_labels.empty())
+ variant_lines[""] = lines_nolabels;
+ for (auto &it : lines_labels) {
+ variant_lines[it.first] = lines_nolabels;
+ variant_lines[it.first].insert(variant_lines[it.first].end(), it.second.begin(), it.second.end());
+ }
- if (parse_single_int("dbits", data.dbits))
- continue;
+ for (auto &it : variant_lines)
+ {
+ bram_t data;
+ data.name = bram_name;
+ data.variant = GetSize(brams[data.name]) + 1;
+ data.groups = 0;
+ data.abits = 0;
+ data.dbits = 0;
+ data.init = 0;
+
+ for (auto &line_tokens : it.second)
+ {
+ tokens = line_tokens;
- if (parse_single_int("init", data.init))
- continue;
+ if (parse_single_int("groups", data.groups))
+ continue;
- if (parse_int_vect("ports", data.ports))
- continue;
+ if (parse_single_int("abits", data.abits))
+ continue;
- if (parse_int_vect("wrmode", data.wrmode))
- continue;
+ if (parse_single_int("dbits", data.dbits))
+ continue;
- if (parse_int_vect("enable", data.enable))
- continue;
+ if (parse_single_int("init", data.init))
+ continue;
- if (parse_int_vect("transp", data.transp))
- continue;
+ if (parse_int_vect("ports", data.ports))
+ continue;
- if (parse_int_vect("clocks", data.clocks))
- continue;
+ if (parse_int_vect("wrmode", data.wrmode))
+ continue;
- if (parse_int_vect("clkpol", data.clkpol))
- continue;
+ if (parse_int_vect("enable", data.enable))
+ continue;
- break;
- }
+ if (parse_int_vect("transp", data.transp))
+ continue;
- syntax_error();
+ if (parse_int_vect("clocks", data.clocks))
+ continue;
+
+ if (parse_int_vect("clkpol", data.clkpol))
+ continue;
+
+ syntax_error();
+ }
+
+ data.check_vectors();
+ brams[data.name].push_back(data);
+ }
}
void parse_match()
match_t data;
data.name = RTLIL::escape_id(tokens[1]);
data.or_next_if_better = false;
+ data.make_transp = false;
+ data.make_outreg = false;
data.shuffle_enable = 0;
while (next_line())
{
+ if (!labels.empty())
+ syntax_error();
+
if (GetSize(tokens) == 1 && tokens[0] == "endmatch") {
matches.push_back(data);
- return;
+ break;
}
if (GetSize(tokens) == 3 && tokens[0] == "min") {
continue;
}
- if (GetSize(tokens) == 2 && tokens[0] == "shuffle_enable") {
- data.shuffle_enable = atoi(tokens[1].c_str());
+ if (GetSize(tokens) == 2 && tokens[0] == "shuffle_enable" && GetSize(tokens[1]) == 1 && 'A' <= tokens[1][0] && tokens[1][0] <= 'Z') {
+ data.shuffle_enable = tokens[1][0];
+ continue;
+ }
+
+ if (GetSize(tokens) == 1 && tokens[0] == "make_transp") {
+ data.make_transp = true;
+ continue;
+ }
+
+ if (GetSize(tokens) == 1 && tokens[0] == "make_outreg") {
+ data.make_transp = true;
+ data.make_outreg = true;
continue;
}
continue;
}
- break;
+ syntax_error();
}
-
- syntax_error();
}
void parse(string filename)
{
- if (filename.substr(0, 2) == "+/")
- filename = proc_share_dirname() + filename.substr(1);
-
+ rewrite_filename(filename);
infile.open(filename);
linecount = 0;
while (next_line())
{
- if (tokens[0] == "bram") parse_bram();
- else if (tokens[0] == "match") parse_match();
- else syntax_error();
+ if (!labels.empty())
+ syntax_error();
+
+ if (tokens[0] == "bram") {
+ parse_bram();
+ continue;
+ }
+
+ if (tokens[0] == "match") {
+ parse_match();
+ continue;
+ }
+
+ syntax_error();
}
infile.close();
}
};
-bool replace_cell(Cell *cell, const rules_t::bram_t &bram, const rules_t::match_t &match, dict<string, int> &match_properties, int mode)
+bool replace_cell(Cell *cell, const rules_t &rules, const rules_t::bram_t &bram, const rules_t::match_t &match, dict<string, int> &match_properties, int mode)
{
Module *module = cell->module;
auto portinfos = bram.make_portinfos();
int dup_count = 1;
+ pair<SigBit, bool> make_transp_clk;
+ bool enable_make_transp = false;
+ int make_transp_enbits = 0;
+
dict<int, pair<SigBit, bool>> clock_domains;
dict<int, bool> clock_polarities;
dict<int, bool> read_transp;
if (pi.clkpol > 1)
clkpol_wr_ports.insert(pi.clkpol);
}
- clocks_max = std::max(clocks_max, pi.clocks);
- clkpol_max = std::max(clkpol_max, pi.clkpol);
- transp_max = std::max(transp_max, pi.transp);
+ clocks_max = max(clocks_max, pi.clocks);
+ clkpol_max = max(clkpol_max, pi.clkpol);
+ transp_max = max(transp_max, pi.transp);
}
- log(" Mapping to bram type %s:\n", log_id(bram.name));
+ log(" Mapping to bram type %s (variant %d):\n", log_id(bram.name), bram.variant);
+ // bram.dump_config();
int mem_size = cell->getParam("\\SIZE").as_int();
int mem_abits = cell->getParam("\\ABITS").as_int();
int mem_width = cell->getParam("\\WIDTH").as_int();
// int mem_offset = cell->getParam("\\OFFSET").as_int();
+ bool cell_init = !SigSpec(cell->getParam("\\INIT")).is_fully_undef();
+ vector<Const> initdata;
+
+ if (cell_init) {
+ Const initparam = cell->getParam("\\INIT");
+ initdata.reserve(mem_size);
+ for (int i=0; i < mem_size; i++)
+ initdata.push_back(initparam.extract(mem_width*i, mem_width, State::Sx));
+ }
+
int wr_ports = cell->getParam("\\WR_PORTS").as_int();
auto wr_clken = SigSpec(cell->getParam("\\WR_CLK_ENABLE"));
auto wr_clkpol = SigSpec(cell->getParam("\\WR_CLK_POLARITY"));
rd_clkpol.extend_u0(rd_ports);
rd_transp.extend_u0(rd_ports);
+ SigSpec rd_en = cell->getPort("\\RD_EN");
SigSpec rd_clk = cell->getPort("\\RD_CLK");
SigSpec rd_data = cell->getPort("\\RD_DATA");
SigSpec rd_addr = cell->getPort("\\RD_ADDR");
- if (match.shuffle_enable)
+ if (match.shuffle_enable && bram.dbits >= portinfos.at(match.shuffle_enable - 'A').enable*2 && portinfos.at(match.shuffle_enable - 'A').enable > 0 && wr_ports > 0)
{
- int bucket_size = bram.dbits / match.shuffle_enable;
- log(" Shuffle enable and data bit to accommodate enable buckets of size %d..\n", bucket_size);
+ int bucket_size = bram.dbits / portinfos.at(match.shuffle_enable - 'A').enable;
+ log(" Shuffle bit order to accommodate enable buckets of size %d..\n", bucket_size);
// extract unshuffled data/enable bits
std::vector<SigSpec> new_wr_en(GetSize(old_wr_en));
std::vector<SigSpec> new_wr_data(GetSize(old_wr_data));
std::vector<SigSpec> new_rd_data(GetSize(old_rd_data));
+ std::vector<std::vector<State>> new_initdata;
std::vector<int> shuffle_map;
+ if (cell_init)
+ new_initdata.resize(mem_size);
+
for (auto &it : en_order)
{
auto &bits = bits_wr_en.at(it);
}
for (int j = 0; j < rd_ports; j++)
new_rd_data[j].append(old_rd_data[j][bits[i]]);
+ if (cell_init) {
+ for (int j = 0; j < mem_size; j++)
+ new_initdata[j].push_back(initdata[j][bits[i]]);
+ }
shuffle_map.push_back(bits[i]);
}
}
for (int j = 0; j < rd_ports; j++)
new_rd_data[j].append(State::Sx);
+ if (cell_init) {
+ for (int j = 0; j < mem_size; j++)
+ new_initdata[j].push_back(State::Sx);
+ }
shuffle_map.push_back(-1);
}
}
- log(" Results of enable shuffling:");
+ log(" Results of bit order shuffling:");
for (int v : shuffle_map)
log(" %d", v);
log("\n");
for (int i = 0; i < rd_ports; i++)
rd_data.replace(i*mem_width, new_rd_data[i]);
+
+ if (cell_init) {
+ for (int i = 0; i < mem_size; i++)
+ initdata[i] = Const(new_initdata[i]);
+ }
}
// assign write ports
-
+ pair<SigBit, bool> wr_clkdom;
for (int cell_port_i = 0, bram_port_i = 0; cell_port_i < wr_ports; cell_port_i++)
{
bool clken = wr_clken[cell_port_i] == State::S1;
pair<SigBit, bool> clkdom(clksig, clkpol);
if (!clken)
clkdom = pair<SigBit, bool>(State::S1, false);
-
+ wr_clkdom = clkdom;
log(" Write port #%d is in clock domain %s%s.\n",
cell_port_i, clkdom.second ? "" : "!",
clken ? log_signal(clkdom.first) : "~async~");
for (; bram_port_i < GetSize(portinfos); bram_port_i++)
{
auto &pi = portinfos[bram_port_i];
+ make_transp_enbits = pi.enable;
+ make_transp_clk = clkdom;
if (pi.wrmode != 1)
skip_bram_wport:
sig_en.append(last_en_bit);
}
if (last_en_bit != wr_en[i + cell_port_i*mem_width]) {
- log_dump(last_en_bit, wr_en[i + cell_port_i*mem_width]);
log(" Bram port %c%d has incompatible enable structure.\n", pi.group + 'A', pi.index + 1);
goto skip_bram_wport;
}
mapped_wr_port:;
}
- // houskeeping stuff for growing more read ports and restarting read port assignments
+ // housekeeping stuff for growing more read ports and restarting read port assignments
int grow_read_ports_cursor = -1;
bool try_growing_more_read_ports = false;
pi.sig_addr = SigSpec();
pi.sig_data = SigSpec();
pi.sig_en = SigSpec();
+ pi.make_outreg = false;
+ pi.make_transp = false;
}
new_portinfos.push_back(pi);
if (pi.dupidx == dup_count-1) {
bool transp = rd_transp[cell_port_i] == State::S1;
SigBit clksig = rd_clk[cell_port_i];
+ if (wr_ports == 0)
+ transp = false;
+
pair<SigBit, bool> clkdom(clksig, clkpol);
if (!clken)
clkdom = pair<SigBit, bool>(State::S1, false);
if (clken) {
if (pi.clocks == 0) {
+ if (match.make_outreg) {
+ pi.make_outreg = true;
+ goto skip_bram_rport_clkcheck;
+ }
log(" Bram port %c%d.%d has incompatible clock type.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
goto skip_bram_rport;
}
log(" Bram port %c%d.%d has incompatible clock polarity.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
goto skip_bram_rport;
}
- if (read_transp.count(pi.transp) && read_transp.at(pi.transp) != transp) {
- log(" Bram port %c%d.%d has incompatible read transparancy.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
+ if (rd_en[cell_port_i] != State::S1 && pi.enable == 0) {
+ log(" Bram port %c%d.%d has no read enable input.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
goto skip_bram_rport;
}
+ skip_bram_rport_clkcheck:
+ if (read_transp.count(pi.transp) && read_transp.at(pi.transp) != transp) {
+ if (match.make_transp && wr_ports <= 1) {
+ pi.make_transp = true;
+ if (pi.clocks != 0) {
+ if (wr_ports == 1 && wr_clkdom != clkdom) {
+ log(" Bram port %c%d.%d cannot have soft transparency logic added as read and write clock domains differ.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
+ goto skip_bram_rport;
+ }
+ enable_make_transp = true;
+ }
+ } else {
+ log(" Bram port %c%d.%d has incompatible read transparency.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
+ goto skip_bram_rport;
+ }
+ }
} else {
if (pi.clocks != 0) {
log(" Bram port %c%d.%d has incompatible clock type.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
if (clken) {
clock_domains[pi.clocks] = clkdom;
clock_polarities[pi.clkpol] = clkdom.second;
- read_transp[pi.transp] = transp;
+ if (!pi.make_transp)
+ read_transp[pi.transp] = transp;
pi.sig_clock = clkdom.first;
+ pi.sig_en = rd_en[cell_port_i];
pi.effective_clkpol = clkdom.second;
}
int cells = ((mem_width + bram.dbits - 1) / bram.dbits) * ((mem_size + (1 << bram.abits) - 1) / (1 << bram.abits));
match_properties["efficiency"] = (100 * match_properties["bits"]) / (dup_count * cells * bram.dbits * (1 << bram.abits));
+ match_properties["dcells"] = ((mem_width + bram.dbits - 1) / bram.dbits);
+ match_properties["acells"] = ((mem_size + (1 << bram.abits) - 1) / (1 << bram.abits));
+ match_properties["cells"] = match_properties["dcells"] * match_properties["acells"] * match_properties["dups"];
+
log(" Updated properties: dups=%d waste=%d efficiency=%d\n",
match_properties["dups"], match_properties["waste"], match_properties["efficiency"]);
return true;
}
+ // prepare variant parameters
+
+ dict<IdString, Const> variant_params;
+ for (auto &other_bram : rules.brams.at(bram.name))
+ bram.find_variant_params(variant_params, other_bram);
+
// actually replace that memory cell
dict<SigSpec, pair<SigSpec, SigSpec>> dout_cache;
for (int grid_d = 0; grid_d*bram.dbits < mem_width; grid_d++)
- for (int grid_a = 0; grid_a*(1 << bram.abits) < mem_size; grid_a++)
- for (int dupidx = 0; dupidx < dup_count; dupidx++)
{
- Cell *c = module->addCell(module->uniquify(stringf("%s.%d.%d.%d", cell->name.c_str(), grid_d, grid_a, dupidx)), bram.name);
- log(" Creating %s cell at grid position <%d %d %d>: %s\n", log_id(bram.name), grid_d, grid_a, dupidx, log_id(c));
+ SigSpec mktr_wraddr, mktr_wrdata, mktr_wrdata_q;
+ vector<SigSpec> mktr_wren;
+
+ if (enable_make_transp) {
+ mktr_wraddr = module->addWire(NEW_ID, bram.abits);
+ mktr_wrdata = module->addWire(NEW_ID, bram.dbits);
+ mktr_wrdata_q = module->addWire(NEW_ID, bram.dbits);
+ module->addDff(NEW_ID, make_transp_clk.first, mktr_wrdata, mktr_wrdata_q, make_transp_clk.second);
+ for (int grid_a = 0; grid_a*(1 << bram.abits) < mem_size; grid_a++)
+ mktr_wren.push_back(module->addWire(NEW_ID, make_transp_enbits));
+ }
- for (auto &pi : portinfos)
+ for (int grid_a = 0; grid_a*(1 << bram.abits) < mem_size; grid_a++)
+ for (int dupidx = 0; dupidx < dup_count; dupidx++)
{
- if (pi.dupidx != dupidx)
- continue;
+ Cell *c = module->addCell(module->uniquify(stringf("%s.%d.%d.%d", cell->name.c_str(), grid_d, grid_a, dupidx)), bram.name);
+ log(" Creating %s cell at grid position <%d %d %d>: %s\n", log_id(bram.name), grid_d, grid_a, dupidx, log_id(c));
+
+ for (auto &vp : variant_params)
+ c->setParam(vp.first, vp.second);
+
+ if (cell_init) {
+ int init_offset = grid_a*(1 << bram.abits);
+ int init_shift = grid_d*bram.dbits;
+ int init_size = (1 << bram.abits);
+ Const initparam(State::Sx, init_size*bram.dbits);
+ for (int i = 0; i < init_size; i++) {
+ State padding = State::Sx;
+ for (int j = 0; j < bram.dbits; j++)
+ if (init_offset+i < GetSize(initdata) && init_shift+j < GetSize(initdata[init_offset+i]))
+ initparam[i*bram.dbits+j] = initdata[init_offset+i][init_shift+j];
+ else
+ initparam[i*bram.dbits+j] = padding;
+ }
+ c->setParam("\\INIT", initparam);
+ }
- string prefix = stringf("%c%d", pi.group + 'A', pi.index + 1);
- const char *pf = prefix.c_str();
+ for (auto &pi : portinfos)
+ {
+ if (pi.dupidx != dupidx)
+ continue;
+
+ string prefix = stringf("%c%d", pi.group + 'A', pi.index + 1);
+ const char *pf = prefix.c_str();
+
+ if (pi.clocks && (!c->hasPort(stringf("\\CLK%d", (pi.clocks-1) % clocks_max + 1)) || pi.sig_clock.wire)) {
+ c->setPort(stringf("\\CLK%d", (pi.clocks-1) % clocks_max + 1), pi.sig_clock);
+ if (pi.clkpol > 1 && pi.sig_clock.wire)
+ c->setParam(stringf("\\CLKPOL%d", (pi.clkpol-1) % clkpol_max + 1), clock_polarities.at(pi.clkpol));
+ if (pi.transp > 1 && pi.sig_clock.wire)
+ c->setParam(stringf("\\TRANSP%d", (pi.transp-1) % transp_max + 1), read_transp.at(pi.transp));
+ }
- if (pi.clocks && (!c->hasPort(stringf("\\CLK%d", (pi.clocks-1) % clocks_max + 1)) || pi.sig_clock.wire)) {
- c->setPort(stringf("\\CLK%d", (pi.clocks-1) % clocks_max + 1), pi.sig_clock);
- if (pi.clkpol > 1 && pi.sig_clock.wire)
- c->setParam(stringf("\\CLKPOL%d", (pi.clkpol-1) % clkpol_max + 1), clock_polarities.at(pi.clkpol));
- if (pi.transp > 1 && pi.sig_clock.wire)
- c->setParam(stringf("\\TRANSP%d", (pi.transp-1) % transp_max + 1), read_transp.at(pi.transp));
- }
+ SigSpec addr_ok;
+ if (GetSize(pi.sig_addr) > bram.abits) {
+ SigSpec extra_addr = pi.sig_addr.extract(bram.abits, GetSize(pi.sig_addr) - bram.abits);
+ SigSpec extra_addr_sel = SigSpec(grid_a, GetSize(extra_addr));
+ addr_ok = module->Eq(NEW_ID, extra_addr, extra_addr_sel);
+ }
- SigSpec addr_ok;
- if (GetSize(pi.sig_addr) > bram.abits) {
- SigSpec extra_addr = pi.sig_addr.extract(bram.abits, GetSize(pi.sig_addr) - bram.abits);
- SigSpec extra_addr_sel = SigSpec(grid_a, GetSize(extra_addr));
- addr_ok = module->Eq(NEW_ID, extra_addr, extra_addr_sel);
- }
+ if (pi.enable)
+ {
+ SigSpec sig_en = pi.sig_en;
- if (pi.enable)
- {
- SigSpec sig_en = pi.sig_en;
- sig_en.extend_u0((grid_d+1) * pi.enable);
- sig_en = sig_en.extract(grid_d * pi.enable, pi.enable);
+ if (pi.wrmode == 1) {
+ sig_en.extend_u0((grid_d+1) * pi.enable);
+ sig_en = sig_en.extract(grid_d * pi.enable, pi.enable);
+ }
- if (!addr_ok.empty())
- sig_en = module->Mux(NEW_ID, SigSpec(0, GetSize(sig_en)), sig_en, addr_ok);
+ if (!addr_ok.empty())
+ sig_en = module->Mux(NEW_ID, SigSpec(0, GetSize(sig_en)), sig_en, addr_ok);
- c->setPort(stringf("\\%sEN", pf), sig_en);
- }
+ c->setPort(stringf("\\%sEN", pf), sig_en);
- SigSpec sig_data = pi.sig_data;
- sig_data.extend_u0((grid_d+1) * bram.dbits);
- sig_data = sig_data.extract(grid_d * bram.dbits, bram.dbits);
+ if (pi.wrmode == 1 && enable_make_transp)
+ module->connect(mktr_wren[grid_a], sig_en);
+ }
- if (pi.wrmode == 1) {
- c->setPort(stringf("\\%sDATA", pf), sig_data);
- } else {
- SigSpec bram_dout = module->addWire(NEW_ID, bram.dbits);
- c->setPort(stringf("\\%sDATA", pf), bram_dout);
+ SigSpec sig_addr = pi.sig_addr;
+ sig_addr.extend_u0(bram.abits);
+ c->setPort(stringf("\\%sADDR", pf), sig_addr);
+
+ if (pi.wrmode == 1 && enable_make_transp && grid_a == 0)
+ module->connect(mktr_wraddr, sig_addr);
+
+ SigSpec sig_data = pi.sig_data;
+ sig_data.extend_u0((grid_d+1) * bram.dbits);
+ sig_data = sig_data.extract(grid_d * bram.dbits, bram.dbits);
+
+ if (pi.wrmode == 1) {
+ c->setPort(stringf("\\%sDATA", pf), sig_data);
+ if (enable_make_transp && grid_a == 0)
+ module->connect(mktr_wrdata, sig_data);
+ } else {
+ SigSpec bram_dout = module->addWire(NEW_ID, bram.dbits);
+ c->setPort(stringf("\\%sDATA", pf), bram_dout);
+ if (pi.make_outreg && pi.make_transp) {
+ log(" Moving output register to address for transparent port %c%d.%d.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
+ SigSpec sig_addr_q = module->addWire(NEW_ID, bram.abits);
+ module->addDff(NEW_ID, pi.sig_clock, sig_addr, sig_addr_q, pi.effective_clkpol);
+ c->setPort(stringf("\\%sADDR", pf), sig_addr_q);
+ } else if (pi.make_outreg) {
+ SigSpec bram_dout_q = module->addWire(NEW_ID, bram.dbits);
+ if (!pi.sig_en.empty())
+ bram_dout = module->Mux(NEW_ID, bram_dout_q, bram_dout, pi.sig_en);
+ module->addDff(NEW_ID, pi.sig_clock, bram_dout, bram_dout_q, pi.effective_clkpol);
+ bram_dout = bram_dout_q;
+ } else if (pi.make_transp) {
+ log(" Adding extra logic for transparent port %c%d.%d.\n", pi.group + 'A', pi.index + 1, pi.dupidx + 1);
+
+ SigSpec transp_en_d = module->Mux(NEW_ID, SigSpec(0, make_transp_enbits),
+ mktr_wren[grid_a], module->Eq(NEW_ID, mktr_wraddr, sig_addr));
+
+ SigSpec transp_en_q = module->addWire(NEW_ID, make_transp_enbits);
+ module->addDff(NEW_ID, make_transp_clk.first, transp_en_d, transp_en_q, make_transp_clk.second);
+
+ for (int i = 0; i < make_transp_enbits; i++) {
+ int en_width = bram.dbits / make_transp_enbits;
+ SigSpec orig_bram_dout = bram_dout.extract(i * en_width, en_width);
+ SigSpec bypass_dout = mktr_wrdata_q.extract(i * en_width, en_width);
+ bram_dout.replace(i * en_width, module->Mux(NEW_ID, orig_bram_dout, bypass_dout, transp_en_q[i]));
+ }
+ }
- for (int i = bram.dbits-1; i >= 0; i--)
- if (sig_data[i].wire == nullptr) {
- sig_data.remove(i);
- bram_dout.remove(i);
+ for (int i = bram.dbits-1; i >= 0; i--)
+ if (sig_data[i].wire == nullptr) {
+ sig_data.remove(i);
+ bram_dout.remove(i);
+ }
+
+ SigSpec addr_ok_q = addr_ok;
+ if ((pi.clocks || pi.make_outreg) && !addr_ok.empty()) {
+ addr_ok_q = module->addWire(NEW_ID);
+ if (!pi.sig_en.empty())
+ addr_ok = module->Mux(NEW_ID, addr_ok_q, addr_ok, pi.sig_en);
+ module->addDff(NEW_ID, pi.sig_clock, addr_ok, addr_ok_q, pi.effective_clkpol);
}
- SigSpec addr_ok_q = addr_ok;
- if (pi.clocks && !addr_ok.empty()) {
- addr_ok_q = module->addWire(NEW_ID);
- module->addDff(NEW_ID, pi.sig_clock, addr_ok, addr_ok_q, pi.effective_clkpol);
+ dout_cache[sig_data].first.append(addr_ok_q);
+ dout_cache[sig_data].second.append(bram_dout);
}
-
- dout_cache[sig_data].first.append(addr_ok_q);
- dout_cache[sig_data].second.append(bram_dout);
}
-
- SigSpec sig_addr = pi.sig_addr;
- sig_addr.extend_u0(bram.abits);
- c->setPort(stringf("\\%sADDR", pf), sig_addr);
}
}
{
log("Processing %s.%s:\n", log_id(cell->module), log_id(cell));
+ bool cell_init = !SigSpec(cell->getParam("\\INIT")).is_fully_undef();
+
dict<string, int> match_properties;
match_properties["words"] = cell->getParam("\\SIZE").as_int();
match_properties["abits"] = cell->getParam("\\ABITS").as_int();
log(" %s=%d", it.first.c_str(), it.second);
log("\n");
- pool<IdString> failed_brams;
- dict<int, int> best_rule_cache;
+ pool<pair<IdString, int>> failed_brams;
+ dict<pair<int, int>, tuple<int, int, int>> best_rule_cache;
for (int i = 0; i < GetSize(rules.matches); i++)
{
+ auto &match = rules.matches.at(i);
+
if (!rules.brams.count(rules.matches[i].name))
log_error("No bram description for resource %s found!\n", log_id(rules.matches[i].name));
- auto &match = rules.matches.at(i);
- auto &bram = rules.brams.at(match.name);
-
- if (match.name.in(failed_brams))
- continue;
-
- int avail_rd_ports = 0;
- int avail_wr_ports = 0;
- for (int j = 0; j < bram.groups; j++) {
- if (GetSize(bram.wrmode) < j || bram.wrmode.at(j) == 0)
- avail_rd_ports += GetSize(bram.ports) < j ? bram.ports.at(j) : 0;
- if (GetSize(bram.wrmode) < j || bram.wrmode.at(j) != 0)
- avail_wr_ports += GetSize(bram.ports) < j ? bram.ports.at(j) : 0;
- }
+ for (int vi = 0; vi < GetSize(rules.brams.at(match.name)); vi++)
+ {
+ auto &bram = rules.brams.at(match.name).at(vi);
+ bool or_next_if_better = match.or_next_if_better || vi+1 < GetSize(rules.brams.at(match.name));
- log(" Checking rule #%d for bram type %s:\n", i, log_id(match.name));
- log(" Bram geometry: abits=%d dbits=%d wports=%d rports=%d\n", bram.abits, bram.dbits, avail_wr_ports, avail_rd_ports);
+ if (failed_brams.count(pair<IdString, int>(bram.name, bram.variant)))
+ continue;
- int dups = avail_rd_ports ? (match_properties["rports"] + avail_rd_ports - 1) / avail_rd_ports : 1;
- match_properties["dups"] = dups;
+ int avail_rd_ports = 0;
+ int avail_wr_ports = 0;
+ for (int j = 0; j < bram.groups; j++) {
+ if (GetSize(bram.wrmode) < j || bram.wrmode.at(j) == 0)
+ avail_rd_ports += GetSize(bram.ports) < j ? bram.ports.at(j) : 0;
+ if (GetSize(bram.wrmode) < j || bram.wrmode.at(j) != 0)
+ avail_wr_ports += GetSize(bram.ports) < j ? bram.ports.at(j) : 0;
+ }
- log(" Estimated number of duplicates for more read ports: dups=%d\n", match_properties["dups"]);
+ log(" Checking rule #%d for bram type %s (variant %d):\n", i+1, log_id(bram.name), bram.variant);
+ log(" Bram geometry: abits=%d dbits=%d wports=%d rports=%d\n", bram.abits, bram.dbits, avail_wr_ports, avail_rd_ports);
- int aover = match_properties["words"] % (1 << bram.abits);
- int awaste = aover ? (1 << bram.abits) - aover : 0;
- match_properties["awaste"] = awaste;
+ int dups = avail_rd_ports ? (match_properties["rports"] + avail_rd_ports - 1) / avail_rd_ports : 1;
+ match_properties["dups"] = dups;
- int dover = match_properties["dbits"] % bram.dbits;
- int dwaste = dover ? bram.dbits - dover : 0;
- match_properties["dwaste"] = dwaste;
+ log(" Estimated number of duplicates for more read ports: dups=%d\n", match_properties["dups"]);
- int bwaste = awaste * bram.dbits + dwaste * (1 << bram.abits) - awaste * dwaste;
- match_properties["bwaste"] = bwaste;
+ int aover = match_properties["words"] % (1 << bram.abits);
+ int awaste = aover ? (1 << bram.abits) - aover : 0;
+ match_properties["awaste"] = awaste;
- int waste = match_properties["dups"] * bwaste;
- match_properties["waste"] = waste;
+ int dover = match_properties["dbits"] % bram.dbits;
+ int dwaste = dover ? bram.dbits - dover : 0;
+ match_properties["dwaste"] = dwaste;
- int cells = ((match_properties["dbits"] + bram.dbits - 1) / bram.dbits) * ((match_properties["words"] + (1 << bram.abits) - 1) / (1 << bram.abits));
- int efficiency = (100 * match_properties["bits"]) / (dups * cells * bram.dbits * (1 << bram.abits));
- match_properties["efficiency"] = efficiency;
+ int bwaste = awaste * bram.dbits + dwaste * (1 << bram.abits) - awaste * dwaste;
+ match_properties["bwaste"] = bwaste;
- log(" Metrics for %s: awaste=%d dwaste=%d bwaste=%d waste=%d efficiency=%d\n",
- log_id(match.name), awaste, dwaste, bwaste, waste, efficiency);
+ int waste = match_properties["dups"] * bwaste;
+ match_properties["waste"] = waste;
- for (auto it : match.min_limits) {
- if (it.first == "waste" || it.first == "dups")
- continue;
- if (!match_properties.count(it.first))
- log_error("Unknown property '%s' in match rule for bram type %s.\n",
- it.first.c_str(), log_id(match.name));
- if (match_properties[it.first] >= it.second)
- continue;
- log(" Rule #%d for bram type %s rejected: requirement 'min %s %d' not met.\n",
- i, log_id(match.name), it.first.c_str(), it.second);
- goto next_match_rule;
- }
- for (auto it : match.max_limits) {
- if (!match_properties.count(it.first))
- log_error("Unknown property '%s' in match rule for bram type %s.\n",
- it.first.c_str(), log_id(match.name));
- if (match_properties[it.first] <= it.second)
- continue;
- log(" Rule #%d for bram type %s rejected: requirement 'max %s %d' not met.\n",
- i, log_id(match.name), it.first.c_str(), it.second);
- goto next_match_rule;
- }
+ int cells = ((match_properties["dbits"] + bram.dbits - 1) / bram.dbits) * ((match_properties["words"] + (1 << bram.abits) - 1) / (1 << bram.abits));
+ int efficiency = (100 * match_properties["bits"]) / (dups * cells * bram.dbits * (1 << bram.abits));
+ match_properties["efficiency"] = efficiency;
- log(" Rule #%d for bram type %s accepted.\n", i, log_id(match.name));
+ log(" Metrics for %s: awaste=%d dwaste=%d bwaste=%d waste=%d efficiency=%d\n",
+ log_id(match.name), awaste, dwaste, bwaste, waste, efficiency);
- if (match.or_next_if_better || !best_rule_cache.empty())
- {
- if (match.or_next_if_better && i+1 == GetSize(rules.matches))
- log_error("Found 'or_next_if_better' in last match rule.\n");
-
- if (!replace_cell(cell, bram, match, match_properties, 1)) {
- log(" Mapping to bram type %s failed.\n", log_id(match.name));
- failed_brams.insert(match.name);
+ if (cell_init && bram.init == 0) {
+ log(" Rule #%d for bram type %s (variant %d) rejected: cannot be initialized.\n",
+ i+1, log_id(bram.name), bram.variant);
goto next_match_rule;
}
- log(" Storing for later selection.\n");
- best_rule_cache[i] = match_properties["efficiency"];
+ for (auto it : match.min_limits) {
+ if (it.first == "waste" || it.first == "dups" || it.first == "acells" || it.first == "dcells" || it.first == "cells")
+ continue;
+ if (!match_properties.count(it.first))
+ log_error("Unknown property '%s' in match rule for bram type %s.\n",
+ it.first.c_str(), log_id(match.name));
+ if (match_properties[it.first] >= it.second)
+ continue;
+ log(" Rule #%d for bram type %s (variant %d) rejected: requirement 'min %s %d' not met.\n",
+ i+1, log_id(bram.name), bram.variant, it.first.c_str(), it.second);
+ goto next_match_rule;
+ }
- if (match.or_next_if_better)
+ for (auto it : match.max_limits) {
+ if (it.first == "acells" || it.first == "dcells" || it.first == "cells")
+ continue;
+ if (!match_properties.count(it.first))
+ log_error("Unknown property '%s' in match rule for bram type %s.\n",
+ it.first.c_str(), log_id(match.name));
+ if (match_properties[it.first] <= it.second)
+ continue;
+ log(" Rule #%d for bram type %s (variant %d) rejected: requirement 'max %s %d' not met.\n",
+ i+1, log_id(bram.name), bram.variant, it.first.c_str(), it.second);
goto next_match_rule;
+ }
- next_match_rule:
- if (match.or_next_if_better || best_rule_cache.empty())
- continue;
+ log(" Rule #%d for bram type %s (variant %d) accepted.\n", i+1, log_id(bram.name), bram.variant);
- log(" Selecting best of %d rules:\n", GetSize(best_rule_cache));
- int best_rule = best_rule_cache.begin()->first;
+ if (or_next_if_better || !best_rule_cache.empty())
+ {
+ if (or_next_if_better && i+1 == GetSize(rules.matches) && vi+1 == GetSize(rules.brams.at(match.name)))
+ log_error("Found 'or_next_if_better' in last match rule.\n");
- for (auto &it : best_rule_cache) {
- if (it.second > best_rule_cache[best_rule])
- best_rule = it.first;
- log(" Efficiency for rule %d: %d\n", it.first, it.second);
- }
+ if (!replace_cell(cell, rules, bram, match, match_properties, 1)) {
+ log(" Mapping to bram type %s failed.\n", log_id(match.name));
+ failed_brams.insert(pair<IdString, int>(bram.name, bram.variant));
+ goto next_match_rule;
+ }
- log(" Selected rule %d with efficiency %d.\n", best_rule, best_rule_cache[best_rule]);
- best_rule_cache.clear();
+ log(" Storing for later selection.\n");
+ best_rule_cache[pair<int, int>(i, vi)] = tuple<int, int, int>(match_properties["efficiency"], -match_properties["cells"], -match_properties["acells"]);
- if (!replace_cell(cell, rules.brams.at(rules.matches.at(best_rule).name), rules.matches.at(best_rule), match_properties, 2))
- log_error("Mapping to bram type %s after pre-selection failed.\n", log_id(rules.matches.at(best_rule).name));
- return;
- }
+ next_match_rule:
+ if (or_next_if_better || best_rule_cache.empty())
+ continue;
+
+ log(" Selecting best of %d rules:\n", GetSize(best_rule_cache));
+ pair<int, int> best_rule = best_rule_cache.begin()->first;
+
+ for (auto &it : best_rule_cache) {
+ if (it.second > best_rule_cache[best_rule])
+ best_rule = it.first;
+ log(" Efficiency for rule %d.%d: efficiency=%d, cells=%d, acells=%d\n", it.first.first+1, it.first.second+1,
+ std::get<0>(it.second), -std::get<1>(it.second), -std::get<2>(it.second));
+ }
+
+ log(" Selected rule %d.%d with efficiency %d.\n", best_rule.first+1, best_rule.second+1, std::get<0>(best_rule_cache[best_rule]));
+ best_rule_cache.clear();
+
+ auto &best_bram = rules.brams.at(rules.matches.at(best_rule.first).name).at(best_rule.second);
+ if (!replace_cell(cell, rules, best_bram, rules.matches.at(best_rule.first), match_properties, 2))
+ log_error("Mapping to bram type %s (variant %d) after pre-selection failed.\n", log_id(best_bram.name), best_bram.variant);
+ return;
+ }
- if (!replace_cell(cell, bram, match, match_properties, 0)) {
- log(" Mapping to bram type %s failed.\n", log_id(match.name));
- failed_brams.insert(match.name);
- goto next_match_rule;
+ if (!replace_cell(cell, rules, bram, match, match_properties, 0)) {
+ log(" Mapping to bram type %s failed.\n", log_id(match.name));
+ failed_brams.insert(pair<IdString, int>(bram.name, bram.variant));
+ goto next_match_rule;
+ }
+ return;
}
- return;
}
log(" No acceptable bram resources found.\n");
struct MemoryBramPass : public Pass {
MemoryBramPass() : Pass("memory_bram", "map memories to block rams") { }
- virtual void help()
+ void help() YS_OVERRIDE
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log("rules. A block ram description looks like this:\n");
log("\n");
log(" bram RAMB1024X32 # name of BRAM cell\n");
- // log(" init 1 # set to '1' if BRAM can be initialized\n");
+ log(" init 1 # set to '1' if BRAM can be initialized\n");
log(" abits 10 # number of address bits\n");
log(" dbits 32 # number of data bits\n");
log(" groups 2 # number of port groups\n");
log(" ports 1 1 # number of ports in each group\n");
log(" wrmode 1 0 # set to '1' if this groups is write ports\n");
- log(" enable 4 0 # number of enable bits (for write ports)\n");
- log(" transp 0 2 # transparatent (for read ports)\n");
+ log(" enable 4 1 # number of enable bits\n");
+ log(" transp 0 2 # transparent (for read ports)\n");
log(" clocks 1 2 # clock configuration\n");
log(" clkpol 2 2 # clock polarity configuration\n");
log(" endbram\n");
log("and a value greater than 1 means configurable. All groups with the same value\n");
log("greater than 1 share the same configuration bit.\n");
log("\n");
+ log("Using the same bram name in different bram blocks will create different variants\n");
+ log("of the bram. Verilog configuration parameters for the bram are created as needed.\n");
+ log("\n");
+ log("It is also possible to create variants by repeating statements in the bram block\n");
+ log("and appending '@<label>' to the individual statements.\n");
+ log("\n");
log("A match rule looks like this:\n");
log("\n");
log(" match RAMB1024X32\n");
log("It is possible to match against the following values with min/max rules:\n");
log("\n");
log(" words ........ number of words in memory in design\n");
- log(" abits ........ number of adress bits on memory in design\n");
+ log(" abits ........ number of address bits on memory in design\n");
log(" dbits ........ number of data bits on memory in design\n");
log(" wports ....... number of write ports on memory in design\n");
log(" rports ....... number of read ports on memory in design\n");
log(" waste ........ total number of unused bram bits (bwaste*dups)\n");
log(" efficiency ... total percentage of used and non-duplicated bits\n");
log("\n");
- log("The interface for the created bram instances is dervived from the bram\n");
+ log(" acells ....... number of cells in 'address-direction'\n");
+ log(" dcells ....... number of cells in 'data-direction'\n");
+ log(" cells ........ total number of cells (acells*dcells*dups)\n");
+ log("\n");
+ log("The interface for the created bram instances is derived from the bram\n");
log("description. Use 'techmap' to convert the created bram instances into\n");
log("instances of the actual bram cells of your target architecture.\n");
log("\n");
log("has a higher efficiency than the next match (and the one after that if\n");
log("the next also has 'or_next_if_better' set, and so forth).\n");
log("\n");
- log("A match containing the command 'shuffle_enable <N>' will re-organize\n");
- log("the data bits to accommodate bram ports with <N> enable bits before\n");
- log("mapping to the bram.\n");
+ log("A match containing the command 'make_transp' will add external circuitry\n");
+ log("to simulate 'transparent read', if necessary.\n");
+ log("\n");
+ log("A match containing the command 'make_outreg' will add external flip-flops\n");
+ log("to implement synchronous read ports, if necessary.\n");
+ log("\n");
+ log("A match containing the command 'shuffle_enable A' will re-organize\n");
+ log("the data bits to accommodate the enable pattern of port A.\n");
log("\n");
}
- virtual void execute(vector<string> args, Design *design)
+ void execute(vector<string> args, Design *design) YS_OVERRIDE
{
rules_t rules;
- log_header("Executing MEMORY_BRAM pass (mapping $mem cells to block memories).\n");
+ log_header(design, "Executing MEMORY_BRAM pass (mapping $mem cells to block memories).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {