aboutsummaryrefslogtreecommitdiffstats
path: root/backends
diff options
context:
space:
mode:
Diffstat (limited to 'backends')
-rw-r--r--backends/aiger/aiger.cc4
-rw-r--r--backends/aiger/xaiger.cc45
-rw-r--r--backends/blif/blif.cc112
-rw-r--r--backends/btor/.gitignore1
-rw-r--r--backends/btor/btor.cc248
-rwxr-xr-x[-rw-r--r--]backends/btor/test_cells.sh4
-rw-r--r--backends/cxxrtl/cxxrtl.h243
-rw-r--r--backends/cxxrtl/cxxrtl_backend.cc2029
-rw-r--r--backends/cxxrtl/cxxrtl_capi.cc31
-rw-r--r--backends/cxxrtl/cxxrtl_capi.h136
-rw-r--r--backends/cxxrtl/cxxrtl_vcd.h51
-rw-r--r--backends/edif/edif.cc10
-rw-r--r--backends/firrtl/firrtl.cc704
-rw-r--r--backends/ilang/Makefile.inc3
-rw-r--r--backends/intersynth/intersynth.cc8
-rw-r--r--backends/json/json.cc44
-rw-r--r--backends/protobuf/Makefile.inc2
-rw-r--r--backends/protobuf/protobuf.cc2
-rw-r--r--backends/rtlil/Makefile.inc3
-rw-r--r--backends/rtlil/rtlil_backend.cc (renamed from backends/ilang/ilang_backend.cc)116
-rw-r--r--backends/rtlil/rtlil_backend.h (renamed from backends/ilang/ilang_backend.h)10
-rw-r--r--backends/simplec/simplec.cc2
-rw-r--r--backends/smt2/smt2.cc498
-rw-r--r--backends/smt2/smtbmc.py54
-rw-r--r--backends/smt2/smtio.py36
-rw-r--r--backends/smv/.gitignore1
-rw-r--r--backends/smv/smv.cc25
-rw-r--r--backends/spice/spice.cc24
-rw-r--r--backends/table/table.cc2
-rw-r--r--backends/verilog/verilog_backend.cc1287
30 files changed, 3693 insertions, 2042 deletions
diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc
index 81a3f483b..35935b847 100644
--- a/backends/aiger/aiger.cc
+++ b/backends/aiger/aiger.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -111,7 +111,7 @@ struct AigerWriter
// promote public wires
for (auto wire : module->wires())
- if (wire->name[0] == '\\')
+ if (wire->name.isPublic())
sigmap.add(wire);
// promote input wires
diff --git a/backends/aiger/xaiger.cc b/backends/aiger/xaiger.cc
index ef0103c17..e223f185e 100644
--- a/backends/aiger/xaiger.cc
+++ b/backends/aiger/xaiger.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* 2019 Eddie Hung <eddie@fpgeh.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
@@ -146,7 +146,7 @@ struct XAigerWriter
// promote public wires
for (auto wire : module->wires())
- if (wire->name[0] == '\\')
+ if (wire->name.isPublic())
sigmap.add(wire);
// promote input wires
@@ -156,7 +156,7 @@ struct XAigerWriter
// promote keep wires
for (auto wire : module->wires())
- if (wire->get_bool_attribute(ID::keep) || wire->get_bool_attribute(ID::abc9_keep))
+ if (wire->get_bool_attribute(ID::keep))
sigmap.add(wire);
for (auto wire : module->wires()) {
@@ -177,11 +177,10 @@ struct XAigerWriter
undriven_bits.insert(bit);
unused_bits.insert(bit);
- bool keep = wire->get_bool_attribute(ID::abc9_keep);
- if (wire->port_input || keep)
+ if (wire->port_input)
input_bits.insert(bit);
- keep = keep || wire->get_bool_attribute(ID::keep);
+ bool keep = wire->get_bool_attribute(ID::keep);
if (wire->port_output || keep) {
if (bit != wirebit)
alias_map[wirebit] = bit;
@@ -262,26 +261,27 @@ struct XAigerWriter
if (!timing.count(inst_module->name))
timing.setup_module(inst_module);
- auto &t = timing.at(inst_module->name).arrival;
- for (const auto &conn : cell->connections()) {
- auto port_wire = inst_module->wire(conn.first);
- if (!port_wire->port_output)
+
+ for (auto &i : timing.at(inst_module->name).arrival) {
+ if (!cell->hasPort(i.first.name))
continue;
- for (int i = 0; i < GetSize(conn.second); i++) {
- auto d = t.at(TimingInfo::NameBit(conn.first,i), 0);
- if (d == 0)
- continue;
+ auto port_wire = inst_module->wire(i.first.name);
+ log_assert(port_wire->port_output);
+
+ auto d = i.second.first;
+ if (d == 0)
+ continue;
+ auto offset = i.first.offset;
#ifndef NDEBUG
- if (ys_debug(1)) {
- static std::set<std::tuple<IdString,IdString,int>> seen;
- if (seen.emplace(inst_module->name, conn.first, i).second) log("%s.%s[%d] abc9_arrival = %d\n",
- log_id(cell->type), log_id(conn.first), i, d);
- }
-#endif
- arrival_times[conn.second[i]] = d;
+ if (ys_debug(1)) {
+ static pool<std::pair<IdString,TimingInfo::NameBit>> seen;
+ if (seen.emplace(inst_module->name, i.first).second) log("%s.%s[%d] abc9_arrival = %d\n",
+ log_id(cell->type), log_id(i.first.name), offset, d);
}
+#endif
+ arrival_times[cell->getPort(i.first.name)[offset]] = d;
}
if (abc9_flop)
@@ -432,7 +432,8 @@ struct XAigerWriter
// that has been padded to its full width
if (bit == State::Sx)
continue;
- log_assert(!aig_map.count(bit));
+ if (aig_map.count(bit))
+ log_error("Visited AIG node more than once; this could be a combinatorial loop that has not been broken\n");
aig_map[bit] = 2*aig_m;
}
diff --git a/backends/blif/blif.cc b/backends/blif/blif.cc
index 780a16320..ba29d9090 100644
--- a/backends/blif/blif.cc
+++ b/backends/blif/blif.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -86,20 +86,18 @@ struct BlifDumper
}
}
- vector<shared_str> cstr_buf;
pool<SigBit> cstr_bits_seen;
- const char *cstr(RTLIL::IdString id)
+ const std::string str(RTLIL::IdString id)
{
std::string str = RTLIL::unescape_id(id);
for (size_t i = 0; i < str.size(); i++)
if (str[i] == '#' || str[i] == '=' || str[i] == '<' || str[i] == '>')
str[i] = '?';
- cstr_buf.push_back(str);
- return cstr_buf.back().c_str();
+ return str;
}
- const char *cstr(RTLIL::SigBit sig)
+ const std::string str(RTLIL::SigBit sig)
{
cstr_bits_seen.insert(sig);
@@ -117,11 +115,10 @@ struct BlifDumper
if (sig.wire->width != 1)
str += stringf("[%d]", sig.wire->upto ? sig.wire->start_offset+sig.wire->width-sig.offset-1 : sig.wire->start_offset+sig.offset);
- cstr_buf.push_back(str);
- return cstr_buf.back().c_str();
+ return str;
}
- const char *cstr_init(RTLIL::SigBit sig)
+ const std::string str_init(RTLIL::SigBit sig)
{
sigmap.apply(sig);
@@ -130,8 +127,7 @@ struct BlifDumper
string str = stringf(" %d", init_bits.at(sig));
- cstr_buf.push_back(str);
- return cstr_buf.back().c_str();
+ return str;
}
const char *subckt_or_gate(std::string cell_type)
@@ -168,7 +164,7 @@ struct BlifDumper
void dump()
{
f << stringf("\n");
- f << stringf(".model %s\n", cstr(module->name));
+ f << stringf(".model %s\n", str(module->name).c_str());
std::map<int, RTLIL::Wire*> inputs, outputs;
@@ -183,7 +179,7 @@ struct BlifDumper
for (auto &it : inputs) {
RTLIL::Wire *wire = it.second;
for (int i = 0; i < wire->width; i++)
- f << stringf(" %s", cstr(RTLIL::SigSpec(wire, i)));
+ f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str());
}
f << stringf("\n");
@@ -191,7 +187,7 @@ struct BlifDumper
for (auto &it : outputs) {
RTLIL::Wire *wire = it.second;
for (int i = 0; i < wire->width; i++)
- f << stringf(" %s", cstr(RTLIL::SigSpec(wire, i)));
+ f << stringf(" %s", str(RTLIL::SigSpec(wire, i)).c_str());
}
f << stringf("\n");
@@ -233,131 +229,131 @@ struct BlifDumper
if (config->unbuf_types.count(cell->type)) {
auto portnames = config->unbuf_types.at(cell->type);
f << stringf(".names %s %s\n1 1\n",
- cstr(cell->getPort(portnames.first)), cstr(cell->getPort(portnames.second)));
+ str(cell->getPort(portnames.first)).c_str(), str(cell->getPort(portnames.second)).c_str());
continue;
}
if (!config->icells_mode && cell->type == ID($_NOT_)) {
f << stringf(".names %s %s\n0 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_AND_)) {
f << stringf(".names %s %s %s\n11 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_OR_)) {
f << stringf(".names %s %s %s\n1- 1\n-1 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_XOR_)) {
f << stringf(".names %s %s %s\n10 1\n01 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_NAND_)) {
f << stringf(".names %s %s %s\n0- 1\n-0 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_NOR_)) {
f << stringf(".names %s %s %s\n00 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_XNOR_)) {
f << stringf(".names %s %s %s\n11 1\n00 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_ANDNOT_)) {
f << stringf(".names %s %s %s\n10 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_ORNOT_)) {
f << stringf(".names %s %s %s\n1- 1\n-0 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_AOI3_)) {
f << stringf(".names %s %s %s %s\n-00 1\n0-0 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_OAI3_)) {
f << stringf(".names %s %s %s %s\n00- 1\n--0 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)), cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(), str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_AOI4_)) {
f << stringf(".names %s %s %s %s %s\n-0-0 1\n-00- 1\n0--0 1\n0-0- 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)),
- cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(),
+ str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_OAI4_)) {
f << stringf(".names %s %s %s %s %s\n00-- 1\n--00 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)),
- cstr(cell->getPort(ID::C)), cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(),
+ str(cell->getPort(ID::C)).c_str(), str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_MUX_)) {
f << stringf(".names %s %s %s %s\n1-0 1\n-11 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)),
- cstr(cell->getPort(ID::S)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(),
+ str(cell->getPort(ID::S)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_NMUX_)) {
f << stringf(".names %s %s %s %s\n0-0 1\n-01 1\n",
- cstr(cell->getPort(ID::A)), cstr(cell->getPort(ID::B)),
- cstr(cell->getPort(ID::S)), cstr(cell->getPort(ID::Y)));
+ str(cell->getPort(ID::A)).c_str(), str(cell->getPort(ID::B)).c_str(),
+ str(cell->getPort(ID::S)).c_str(), str(cell->getPort(ID::Y)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_FF_)) {
- f << stringf(".latch %s %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)),
- cstr_init(cell->getPort(ID::Q)));
+ f << stringf(".latch %s %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
+ str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DFF_N_)) {
- f << stringf(".latch %s %s fe %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)),
- cstr(cell->getPort(ID::C)), cstr_init(cell->getPort(ID::Q)));
+ f << stringf(".latch %s %s fe %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
+ str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DFF_P_)) {
- f << stringf(".latch %s %s re %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)),
- cstr(cell->getPort(ID::C)), cstr_init(cell->getPort(ID::Q)));
+ f << stringf(".latch %s %s re %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
+ str(cell->getPort(ID::C)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DLATCH_N_)) {
- f << stringf(".latch %s %s al %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)),
- cstr(cell->getPort(ID::E)), cstr_init(cell->getPort(ID::Q)));
+ f << stringf(".latch %s %s al %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
+ str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
if (!config->icells_mode && cell->type == ID($_DLATCH_P_)) {
- f << stringf(".latch %s %s ah %s%s\n", cstr(cell->getPort(ID::D)), cstr(cell->getPort(ID::Q)),
- cstr(cell->getPort(ID::E)), cstr_init(cell->getPort(ID::Q)));
+ f << stringf(".latch %s %s ah %s%s\n", str(cell->getPort(ID::D)).c_str(), str(cell->getPort(ID::Q)).c_str(),
+ str(cell->getPort(ID::E)).c_str(), str_init(cell->getPort(ID::Q)).c_str());
goto internal_cell;
}
@@ -367,10 +363,10 @@ struct BlifDumper
auto width = cell->parameters.at(ID::WIDTH).as_int();
log_assert(inputs.size() == width);
for (int i = width-1; i >= 0; i--)
- f << stringf(" %s", cstr(inputs.extract(i, 1)));
+ f << stringf(" %s", str(inputs.extract(i, 1)).c_str());
auto &output = cell->getPort(ID::Y);
log_assert(output.size() == 1);
- f << stringf(" %s", cstr(output));
+ f << stringf(" %s", str(output).c_str());
f << stringf("\n");
RTLIL::SigSpec mask = cell->parameters.at(ID::LUT);
for (int i = 0; i < (1 << width); i++)
@@ -393,10 +389,10 @@ struct BlifDumper
table.push_back(State::S0);
log_assert(inputs.size() == width);
for (int i = 0; i < width; i++)
- f << stringf(" %s", cstr(inputs.extract(i, 1)));
+ f << stringf(" %s", str(inputs.extract(i, 1)).c_str());
auto &output = cell->getPort(ID::Y);
log_assert(output.size() == 1);
- f << stringf(" %s", cstr(output));
+ f << stringf(" %s", str(output).c_str());
f << stringf("\n");
for (int i = 0; i < depth; i++) {
for (int j = 0; j < width; j++) {
@@ -411,11 +407,11 @@ struct BlifDumper
goto internal_cell;
}
- f << stringf(".%s %s", subckt_or_gate(cell->type.str()), cstr(cell->type));
+ f << stringf(".%s %s", subckt_or_gate(cell->type.str()), str(cell->type).c_str());
for (auto &conn : cell->connections())
{
if (conn.second.size() == 1) {
- f << stringf(" %s=%s", cstr(conn.first), cstr(conn.second[0]));
+ f << stringf(" %s=%s", str(conn.first).c_str(), str(conn.second[0]).c_str());
continue;
}
@@ -424,20 +420,20 @@ struct BlifDumper
if (w == nullptr) {
for (int i = 0; i < GetSize(conn.second); i++)
- f << stringf(" %s[%d]=%s", cstr(conn.first), i, cstr(conn.second[i]));
+ f << stringf(" %s[%d]=%s", str(conn.first).c_str(), i, str(conn.second[i]).c_str());
} else {
for (int i = 0; i < std::min(GetSize(conn.second), GetSize(w)); i++) {
SigBit sig(w, i);
- f << stringf(" %s[%d]=%s", cstr(conn.first), sig.wire->upto ?
+ f << stringf(" %s[%d]=%s", str(conn.first).c_str(), sig.wire->upto ?
sig.wire->start_offset+sig.wire->width-sig.offset-1 :
- sig.wire->start_offset+sig.offset, cstr(conn.second[i]));
+ sig.wire->start_offset+sig.offset, str(conn.second[i]).c_str());
}
}
}
f << stringf("\n");
if (config->cname_mode)
- f << stringf(".cname %s\n", cstr(cell->name));
+ f << stringf(".cname %s\n", str(cell->name).c_str());
if (config->attr_mode)
dump_params(".attr", cell->attributes);
if (config->param_mode)
@@ -446,7 +442,7 @@ struct BlifDumper
if (0) {
internal_cell:
if (config->iname_mode)
- f << stringf(".cname %s\n", cstr(cell->name));
+ f << stringf(".cname %s\n", str(cell->name).c_str());
if (config->iattr_mode)
dump_params(".attr", cell->attributes);
}
@@ -462,12 +458,12 @@ struct BlifDumper
continue;
if (config->conn_mode)
- f << stringf(".conn %s %s\n", cstr(rhs_bit), cstr(lhs_bit));
+ f << stringf(".conn %s %s\n", str(rhs_bit).c_str(), str(lhs_bit).c_str());
else if (!config->buf_type.empty())
f << stringf(".%s %s %s=%s %s=%s\n", subckt_or_gate(config->buf_type), config->buf_type.c_str(),
- config->buf_in.c_str(), cstr(rhs_bit), config->buf_out.c_str(), cstr(lhs_bit));
+ config->buf_in.c_str(), str(rhs_bit).c_str(), config->buf_out.c_str(), str(lhs_bit).c_str());
else
- f << stringf(".names %s %s\n1 1\n", cstr(rhs_bit), cstr(lhs_bit));
+ f << stringf(".names %s %s\n1 1\n", str(rhs_bit).c_str(), str(lhs_bit).c_str());
}
f << stringf(".end\n");
diff --git a/backends/btor/.gitignore b/backends/btor/.gitignore
new file mode 100644
index 000000000..d23d492d7
--- /dev/null
+++ b/backends/btor/.gitignore
@@ -0,0 +1 @@
+/test_cells.tmp/
diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc
index e5da6c1e7..d62cc4c3d 100644
--- a/backends/btor/btor.cc
+++ b/backends/btor/btor.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -18,7 +18,7 @@
*/
// [[CITE]] Btor2 , BtorMC and Boolector 3.0
-// Aina Niemetz, Mathias Preiner, Clifford Wolf, Armin Biere
+// Aina Niemetz, Mathias Preiner, C. Wolf, Armin Biere
// Computer Aided Verification - 30th International Conference, CAV 2018
// https://cs.stanford.edu/people/niemetz/publication/2018/niemetzpreinerwolfbiere-cav18/
@@ -27,6 +27,7 @@
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/log.h"
+#include "kernel/mem.h"
#include <string>
USING_YOSYS_NAMESPACE
@@ -68,12 +69,15 @@ struct BtorWorker
// ff inputs that need to be evaluated (<nid>, <ff_cell>)
vector<pair<int, Cell*>> ff_todo;
+ vector<pair<int, Mem*>> mem_todo;
pool<Cell*> cell_recursion_guard;
vector<int> bad_properties;
dict<SigBit, bool> initbits;
pool<Wire*> statewires;
pool<string> srcsymbols;
+ vector<Mem> memories;
+ dict<Cell*, Mem*> mem_cells;
string indent, info_filename;
vector<string> info_lines;
@@ -205,9 +209,8 @@ struct BtorWorker
if (cell->type.in(ID($xnor), ID($_XNOR_))) btor_op = "xnor";
log_assert(!btor_op.empty());
- int width = GetSize(cell->getPort(ID::Y));
- width = std::max(width, GetSize(cell->getPort(ID::A)));
- width = std::max(width, GetSize(cell->getPort(ID::B)));
+ int width_ay = std::max(GetSize(cell->getPort(ID::A)), GetSize(cell->getPort(ID::Y)));
+ int width = std::max(width_ay, GetSize(cell->getPort(ID::B)));
bool a_signed = cell->hasParam(ID::A_SIGNED) ? cell->getParam(ID::A_SIGNED).as_bool() : false;
bool b_signed = cell->hasParam(ID::B_SIGNED) ? cell->getParam(ID::B_SIGNED).as_bool() : false;
@@ -224,11 +227,23 @@ struct BtorWorker
int sid = get_bv_sid(width);
int nid;
+ int nid_a;
+ if (cell->type.in(ID($shl), ID($shr), ID($shift), ID($shiftx)) && a_signed && width_ay < width) {
+ // sign-extend A up to the width of Y
+ int nid_a_padded = get_sig_nid(cell->getPort(ID::A), width_ay, a_signed);
+
+ // zero-extend the rest
+ int zeroes = get_sig_nid(Const(0, width-width_ay));
+ nid_a = next_nid++;
+ btorf("%d concat %d %d %d\n", nid_a, sid, zeroes, nid_a_padded);
+ } else {
+ nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed);
+ }
+
+ int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed);
+
if (btor_op == "shift")
{
- int nid_a = get_sig_nid(cell->getPort(ID::A), width, false);
- int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed);
-
int nid_r = next_nid++;
btorf("%d srl %d %d %d\n", nid_r, sid, nid_a, nid_b);
@@ -248,9 +263,6 @@ struct BtorWorker
}
else
{
- int nid_a = get_sig_nid(cell->getPort(ID::A), width, a_signed);
- int nid_b = get_sig_nid(cell->getPort(ID::B), width, b_signed);
-
nid = next_nid++;
btorf("%d %s %d %d %d%s\n", nid, btor_op.c_str(), sid, nid_a, nid_b, getinfo(cell).c_str());
}
@@ -696,49 +708,46 @@ struct BtorWorker
goto okay;
}
- if (cell->type == ID($mem))
+ if (cell->is_mem_cell())
{
- int abits = cell->getParam(ID::ABITS).as_int();
- int width = cell->getParam(ID::WIDTH).as_int();
- int nwords = cell->getParam(ID::SIZE).as_int();
- int rdports = cell->getParam(ID::RD_PORTS).as_int();
- int wrports = cell->getParam(ID::WR_PORTS).as_int();
-
- Const wr_clk_en = cell->getParam(ID::WR_CLK_ENABLE);
- Const rd_clk_en = cell->getParam(ID::RD_CLK_ENABLE);
+ Mem *mem = mem_cells[cell];
- bool asyncwr = wr_clk_en.is_fully_zero();
+ int abits = ceil_log2(mem->size);
- if (!asyncwr && !wr_clk_en.is_fully_ones())
- log_error("Memory %s.%s has mixed async/sync write ports.\n",
- log_id(module), log_id(cell));
+ bool asyncwr = false;
+ bool syncwr = false;
- if (!rd_clk_en.is_fully_zero())
- log_error("Memory %s.%s has sync read ports.\n",
- log_id(module), log_id(cell));
+ for (auto &port : mem->wr_ports) {
+ if (port.clk_enable)
+ syncwr = true;
+ else
+ asyncwr = true;
+ }
- SigSpec sig_rd_addr = sigmap(cell->getPort(ID::RD_ADDR));
- SigSpec sig_rd_data = sigmap(cell->getPort(ID::RD_DATA));
+ if (asyncwr && syncwr)
+ log_error("Memory %s.%s has mixed async/sync write ports.\n",
+ log_id(module), log_id(mem->memid));
- SigSpec sig_wr_addr = sigmap(cell->getPort(ID::WR_ADDR));
- SigSpec sig_wr_data = sigmap(cell->getPort(ID::WR_DATA));
- SigSpec sig_wr_en = sigmap(cell->getPort(ID::WR_EN));
+ for (auto &port : mem->rd_ports) {
+ if (port.clk_enable)
+ log_error("Memory %s.%s has sync read ports. Please use memory_nordff to convert them first.\n",
+ log_id(module), log_id(mem->memid));
+ }
- int data_sid = get_bv_sid(width);
+ int data_sid = get_bv_sid(mem->width);
int bool_sid = get_bv_sid(1);
- int sid = get_mem_sid(abits, width);
+ int sid = get_mem_sid(abits, mem->width);
- Const initdata = cell->getParam(ID::INIT);
- initdata.exts(nwords*width);
int nid_init_val = -1;
- if (!initdata.is_fully_undef())
+ if (!mem->inits.empty())
{
+ Const initdata = mem->get_init_data();
bool constword = true;
- Const firstword = initdata.extract(0, width);
+ Const firstword = initdata.extract(0, mem->width);
- for (int i = 1; i < nwords; i++) {
- Const thisword = initdata.extract(i*width, width);
+ for (int i = 1; i < mem->size; i++) {
+ Const thisword = initdata.extract(i*mem->width, mem->width);
if (thisword != firstword) {
constword = false;
break;
@@ -756,8 +765,8 @@ struct BtorWorker
nid_init_val = next_nid++;
btorf("%d state %d\n", nid_init_val, sid);
- for (int i = 0; i < nwords; i++) {
- Const thisword = initdata.extract(i*width, width);
+ for (int i = 0; i < mem->size; i++) {
+ Const thisword = initdata.extract(i*mem->width, mem->width);
if (thisword.is_fully_undef())
continue;
Const thisaddr(i, abits);
@@ -776,10 +785,10 @@ struct BtorWorker
int nid = next_nid++;
int nid_head = nid;
- if (cell->name[0] == '$')
+ if (mem->memid[0] == '$')
btorf("%d state %d\n", nid, sid);
else
- btorf("%d state %d %s\n", nid, sid, log_id(cell));
+ btorf("%d state %d %s\n", nid, sid, log_id(mem->memid));
if (nid_init_val >= 0)
{
@@ -789,15 +798,14 @@ struct BtorWorker
if (asyncwr)
{
- for (int port = 0; port < wrports; port++)
+ for (auto &port : mem->wr_ports)
{
- SigSpec wa = sig_wr_addr.extract(port*abits, abits);
- SigSpec wd = sig_wr_data.extract(port*width, width);
- SigSpec we = sig_wr_en.extract(port*width, width);
+ SigSpec wa = port.addr;
+ wa.extend_u0(abits);
int wa_nid = get_sig_nid(wa);
- int wd_nid = get_sig_nid(wd);
- int we_nid = get_sig_nid(we);
+ int wd_nid = get_sig_nid(port.data);
+ int we_nid = get_sig_nid(port.en);
int nid2 = next_nid++;
btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid);
@@ -827,22 +835,22 @@ struct BtorWorker
}
}
- for (int port = 0; port < rdports; port++)
+ for (auto &port : mem->rd_ports)
{
- SigSpec ra = sig_rd_addr.extract(port*abits, abits);
- SigSpec rd = sig_rd_data.extract(port*width, width);
+ SigSpec ra = port.addr;
+ ra.extend_u0(abits);
int ra_nid = get_sig_nid(ra);
int rd_nid = next_nid++;
btorf("%d read %d %d %d\n", rd_nid, data_sid, nid_head, ra_nid);
- add_nid_sig(rd_nid, rd);
+ add_nid_sig(rd_nid, port.data);
}
if (!asyncwr)
{
- ff_todo.push_back(make_pair(nid, cell));
+ mem_todo.push_back(make_pair(nid, mem));
}
else
{
@@ -853,7 +861,20 @@ struct BtorWorker
goto okay;
}
- log_error("Unsupported cell type: %s (%s)\n", log_id(cell->type), log_id(cell));
+ if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_btor`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ if (cell->type.in(ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre)) || cell->type.str().substr(0, 5) == "$_DFF" || cell->type.str().substr(0, 7) == "$_ALDFF") {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `async2sync; dffunmap` or `clk2fflogic` before `write_btor`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ if (cell->type.in(ID($sr), ID($dlatch), ID($adlatch), ID($dlatchsr)) || cell->type.str().substr(0, 8) == "$_DLATCH" || cell->type.str().substr(0, 5) == "$_SR_") {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `clk2fflogic` before `write_btor`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ log_error("Unsupported cell type %s for cell %s.%s.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
okay:
btorf_pop(log_id(cell));
@@ -1057,6 +1078,17 @@ struct BtorWorker
if (!info_filename.empty())
infof("name %s\n", log_id(module));
+ memories = Mem::get_all_memories(module);
+
+ dict<IdString, Mem*> mem_dict;
+ for (auto &mem : memories) {
+ mem.narrow();
+ mem_dict[mem.memid] = &mem;
+ }
+ for (auto cell : module->cells())
+ if (cell->is_mem_cell())
+ mem_cells[cell] = mem_dict[cell->parameters.at(ID::MEMID).decode_string()];
+
btorf_push("inputs");
for (auto wire : module->wires())
@@ -1193,7 +1225,7 @@ struct BtorWorker
continue;
}
- while (!ff_todo.empty())
+ while (!ff_todo.empty() || !mem_todo.empty())
{
vector<pair<int, Cell*>> todo;
todo.swap(ff_todo);
@@ -1205,70 +1237,71 @@ struct BtorWorker
btorf_push(stringf("next %s", log_id(cell)));
- if (cell->type == ID($mem))
- {
- int abits = cell->getParam(ID::ABITS).as_int();
- int width = cell->getParam(ID::WIDTH).as_int();
- int wrports = cell->getParam(ID::WR_PORTS).as_int();
+ SigSpec sig = sigmap(cell->getPort(ID::D));
+ int nid_q = get_sig_nid(sig);
+ int sid = get_bv_sid(GetSize(sig));
+ btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str());
- SigSpec sig_wr_addr = sigmap(cell->getPort(ID::WR_ADDR));
- SigSpec sig_wr_data = sigmap(cell->getPort(ID::WR_DATA));
- SigSpec sig_wr_en = sigmap(cell->getPort(ID::WR_EN));
+ btorf_pop(stringf("next %s", log_id(cell)));
+ }
- int data_sid = get_bv_sid(width);
- int bool_sid = get_bv_sid(1);
- int sid = get_mem_sid(abits, width);
- int nid_head = nid;
+ vector<pair<int, Mem*>> mtodo;
+ mtodo.swap(mem_todo);
- for (int port = 0; port < wrports; port++)
- {
- SigSpec wa = sig_wr_addr.extract(port*abits, abits);
- SigSpec wd = sig_wr_data.extract(port*width, width);
- SigSpec we = sig_wr_en.extract(port*width, width);
+ for (auto &it : mtodo)
+ {
+ int nid = it.first;
+ Mem *mem = it.second;
- int wa_nid = get_sig_nid(wa);
- int wd_nid = get_sig_nid(wd);
- int we_nid = get_sig_nid(we);
+ btorf_push(stringf("next %s", log_id(mem->memid)));
- int nid2 = next_nid++;
- btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid);
+ int abits = ceil_log2(mem->size);
- int nid3 = next_nid++;
- btorf("%d not %d %d\n", nid3, data_sid, we_nid);
+ int data_sid = get_bv_sid(mem->width);
+ int bool_sid = get_bv_sid(1);
+ int sid = get_mem_sid(abits, mem->width);
+ int nid_head = nid;
- int nid4 = next_nid++;
- btorf("%d and %d %d %d\n", nid4, data_sid, nid2, nid3);
+ for (auto &port : mem->wr_ports)
+ {
+ SigSpec wa = port.addr;
+ wa.extend_u0(abits);
- int nid5 = next_nid++;
- btorf("%d and %d %d %d\n", nid5, data_sid, wd_nid, we_nid);
+ int wa_nid = get_sig_nid(wa);
+ int wd_nid = get_sig_nid(port.data);
+ int we_nid = get_sig_nid(port.en);
- int nid6 = next_nid++;
- btorf("%d or %d %d %d\n", nid6, data_sid, nid5, nid4);
+ int nid2 = next_nid++;
+ btorf("%d read %d %d %d\n", nid2, data_sid, nid_head, wa_nid);
- int nid7 = next_nid++;
- btorf("%d write %d %d %d %d\n", nid7, sid, nid_head, wa_nid, nid6);
+ int nid3 = next_nid++;
+ btorf("%d not %d %d\n", nid3, data_sid, we_nid);
- int nid8 = next_nid++;
- btorf("%d redor %d %d\n", nid8, bool_sid, we_nid);
+ int nid4 = next_nid++;
+ btorf("%d and %d %d %d\n", nid4, data_sid, nid2, nid3);
- int nid9 = next_nid++;
- btorf("%d ite %d %d %d %d\n", nid9, sid, nid8, nid7, nid_head);
+ int nid5 = next_nid++;
+ btorf("%d and %d %d %d\n", nid5, data_sid, wd_nid, we_nid);
- nid_head = nid9;
- }
+ int nid6 = next_nid++;
+ btorf("%d or %d %d %d\n", nid6, data_sid, nid5, nid4);
- int nid2 = next_nid++;
- btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, getinfo(cell).c_str());
- }
- else
- {
- SigSpec sig = sigmap(cell->getPort(ID::D));
- int nid_q = get_sig_nid(sig);
- int sid = get_bv_sid(GetSize(sig));
- btorf("%d next %d %d %d%s\n", next_nid++, sid, nid, nid_q, getinfo(cell).c_str());
+ int nid7 = next_nid++;
+ btorf("%d write %d %d %d %d\n", nid7, sid, nid_head, wa_nid, nid6);
+
+ int nid8 = next_nid++;
+ btorf("%d redor %d %d\n", nid8, bool_sid, we_nid);
+
+ int nid9 = next_nid++;
+ btorf("%d ite %d %d %d %d\n", nid9, sid, nid8, nid7, nid_head);
+
+ nid_head = nid9;
}
- btorf_pop(stringf("next %s", log_id(cell)));
+ int nid2 = next_nid++;
+ btorf("%d next %d %d %d%s\n", nid2, sid, nid, nid_head, (mem->cell ? getinfo(mem->cell) : getinfo(mem->mem)).c_str());
+
+ btorf_pop(stringf("next %s", log_id(mem->memid)));
}
}
@@ -1366,6 +1399,11 @@ struct BtorBackend : public Backend {
log_header(design, "Executing BTOR backend.\n");
+ log_push();
+ Pass::call(design, "bmuxmap");
+ Pass::call(design, "demuxmap");
+ log_pop();
+
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
diff --git a/backends/btor/test_cells.sh b/backends/btor/test_cells.sh
index 3f077201a..0a011932d 100644..100755
--- a/backends/btor/test_cells.sh
+++ b/backends/btor/test_cells.sh
@@ -6,7 +6,7 @@ rm -rf test_cells.tmp
mkdir -p test_cells.tmp
cd test_cells.tmp
-../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$sop /$macc /$mul /$div /$mod /$divfloor /$modfloor'
+../../../yosys -p 'test_cell -n 5 -w test all /$alu /$fa /$lcu /$lut /$sop /$macc /$mul /$div /$mod /$divfloor /$modfloor /$shiftx'
for fn in test_*.il; do
../../../yosys -p "
@@ -19,7 +19,7 @@ for fn in test_*.il; do
hierarchy -top main
write_btor ${fn%.il}.btor
"
- boolectormc -kmax 1 --trace-gen --stop-first -v ${fn%.il}.btor > ${fn%.il}.out
+ btormc -kmax 1 --trace-gen --stop-first -v ${fn%.il}.btor > ${fn%.il}.out
if grep " SATISFIABLE" ${fn%.il}.out; then
echo "Check failed for ${fn%.il}."
exit 1
diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h
index f0d7b9fc7..b4ffa87cd 100644
--- a/backends/cxxrtl/cxxrtl.h
+++ b/backends/cxxrtl/cxxrtl.h
@@ -36,22 +36,48 @@
#include <map>
#include <algorithm>
#include <memory>
+#include <functional>
#include <sstream>
#include <backends/cxxrtl/cxxrtl_capi.h>
+#ifndef __has_attribute
+# define __has_attribute(x) 0
+#endif
+
// CXXRTL essentially uses the C++ compiler as a hygienic macro engine that feeds an instruction selector.
// It generates a lot of specialized template functions with relatively large bodies that, when inlined
// into the caller and (for those with loops) unrolled, often expose many new optimization opportunities.
// Because of this, most of the CXXRTL runtime must be always inlined for best performance.
-#ifndef __has_attribute
-# define __has_attribute(x) 0
-#endif
#if __has_attribute(always_inline)
#define CXXRTL_ALWAYS_INLINE inline __attribute__((__always_inline__))
#else
#define CXXRTL_ALWAYS_INLINE inline
#endif
+// Conversely, some functions in the generated code are extremely large yet very cold, with both of these
+// properties being extreme enough to confuse C++ compilers into spending pathological amounts of time
+// on a futile (the code becomes worse) attempt to optimize the least important parts of code.
+#if __has_attribute(optnone)
+#define CXXRTL_EXTREMELY_COLD __attribute__((__optnone__))
+#elif __has_attribute(optimize)
+#define CXXRTL_EXTREMELY_COLD __attribute__((__optimize__(0)))
+#else
+#define CXXRTL_EXTREMELY_COLD
+#endif
+
+// CXXRTL uses assert() to check for C++ contract violations (which may result in e.g. undefined behavior
+// of the simulation code itself), and CXXRTL_ASSERT to check for RTL contract violations (which may at
+// most result in undefined simulation results).
+//
+// Though by default, CXXRTL_ASSERT() expands to assert(), it may be overridden e.g. when integrating
+// the simulation into another process that should survive violating RTL contracts.
+#ifndef CXXRTL_ASSERT
+#ifndef CXXRTL_NDEBUG
+#define CXXRTL_ASSERT(x) assert(x)
+#else
+#define CXXRTL_ASSERT(x)
+#endif
+#endif
namespace cxxrtl {
@@ -96,9 +122,11 @@ struct value : public expr_base<value<Bits>> {
explicit constexpr value(Init ...init) : data{init...} {}
value(const value<Bits> &) = default;
- value(value<Bits> &&) = default;
value<Bits> &operator=(const value<Bits> &) = default;
+ value(value<Bits> &&) = default;
+ value<Bits> &operator=(value<Bits> &&) = default;
+
// A (no-op) helper that forces the cast to value<>.
CXXRTL_ALWAYS_INLINE
const value<Bits> &val() const {
@@ -289,6 +317,14 @@ struct value : public expr_base<value<Bits>> {
return sext_cast<NewBits>()(*this);
}
+ // Bit replication is far more efficient than the equivalent concatenation.
+ template<size_t Count>
+ CXXRTL_ALWAYS_INLINE
+ value<Bits * Count> repeat() const {
+ static_assert(Bits == 1, "repeat() is implemented only for 1-bit values");
+ return *this ? value<Bits * Count>().bit_not() : value<Bits * Count>();
+ }
+
// Operations with run-time parameters (offsets, amounts, etc).
//
// These operations are used for computations.
@@ -421,6 +457,42 @@ struct value : public expr_base<value<Bits>> {
return shr<AmountBits, /*Signed=*/true>(amount);
}
+ template<size_t ResultBits, size_t SelBits>
+ value<ResultBits> bmux(const value<SelBits> &sel) const {
+ static_assert(ResultBits << SelBits == Bits, "invalid sizes used in bmux()");
+ size_t amount = sel.data[0] * ResultBits;
+ size_t shift_chunks = amount / chunk::bits;
+ size_t shift_bits = amount % chunk::bits;
+ value<ResultBits> result;
+ chunk::type carry = 0;
+ if (ResultBits % chunk::bits + shift_bits > chunk::bits)
+ carry = data[result.chunks + shift_chunks] << (chunk::bits - shift_bits);
+ for (size_t n = 0; n < result.chunks; n++) {
+ result.data[result.chunks - 1 - n] = carry | (data[result.chunks + shift_chunks - 1 - n] >> shift_bits);
+ carry = (shift_bits == 0) ? 0
+ : data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits);
+ }
+ return result;
+ }
+
+ template<size_t ResultBits, size_t SelBits>
+ value<ResultBits> demux(const value<SelBits> &sel) const {
+ static_assert(Bits << SelBits == ResultBits, "invalid sizes used in demux()");
+ size_t amount = sel.data[0] * Bits;
+ size_t shift_chunks = amount / chunk::bits;
+ size_t shift_bits = amount % chunk::bits;
+ value<ResultBits> result;
+ chunk::type carry = 0;
+ for (size_t n = 0; n < chunks; n++) {
+ result.data[shift_chunks + n] = (data[n] << shift_bits) | carry;
+ carry = (shift_bits == 0) ? 0
+ : data[n] >> (chunk::bits - shift_bits);
+ }
+ if (Bits % chunk::bits + shift_bits > chunk::bits)
+ result.data[shift_chunks + chunks] = carry;
+ return result;
+ }
+
size_t ctpop() const {
size_t count = 0;
for (size_t n = 0; n < chunks; n++) {
@@ -452,10 +524,11 @@ struct value : public expr_base<value<Bits>> {
bool carry = CarryIn;
for (size_t n = 0; n < result.chunks; n++) {
result.data[n] = data[n] + (Invert ? ~other.data[n] : other.data[n]) + carry;
+ if (result.chunks - 1 == n)
+ result.data[result.chunks - 1] &= result.msb_mask;
carry = (result.data[n] < data[n]) ||
(result.data[n] == data[n] && carry);
}
- result.data[result.chunks - 1] &= result.msb_mask;
return {result, carry};
}
@@ -642,14 +715,20 @@ struct wire {
value<Bits> next;
wire() = default;
- constexpr wire(const value<Bits> &init) : curr(init), next(init) {}
+ explicit constexpr wire(const value<Bits> &init) : curr(init), next(init) {}
template<typename... Init>
explicit constexpr wire(Init ...init) : curr{init...}, next{init...} {}
+ // Copying and copy-assigning values is natural. If, however, a value is replaced with a wire,
+ // e.g. because a module is built with a different optimization level, then existing code could
+ // unintentionally copy a wire instead, which would create a subtle but serious bug. To make sure
+ // this doesn't happen, prohibit copying and copy-assigning wires.
wire(const wire<Bits> &) = delete;
- wire(wire<Bits> &&) = default;
wire<Bits> &operator=(const wire<Bits> &) = delete;
+ wire(wire<Bits> &&) = default;
+ wire<Bits> &operator=(wire<Bits> &&) = default;
+
template<class IntegerT>
CXXRTL_ALWAYS_INLINE
IntegerT get() const {
@@ -679,47 +758,32 @@ std::ostream &operator<<(std::ostream &os, const wire<Bits> &val) {
template<size_t Width>
struct memory {
- std::vector<value<Width>> data;
-
- size_t depth() const {
- return data.size();
- }
+ const size_t depth;
+ std::unique_ptr<value<Width>[]> data;
- memory() = delete;
- explicit memory(size_t depth) : data(depth) {}
+ explicit memory(size_t depth) : depth(depth), data(new value<Width>[depth]) {}
memory(const memory<Width> &) = delete;
memory<Width> &operator=(const memory<Width> &) = delete;
- // The only way to get the compiler to put the initializer in .rodata and do not copy it on stack is to stuff it
- // into a plain array. You'd think an std::initializer_list would work here, but it doesn't, because you can't
- // construct an initializer_list in a constexpr (or something) and so if you try to do that the whole thing is
- // first copied on the stack (probably overflowing it) and then again into `data`.
- template<size_t Size>
- struct init {
- size_t offset;
- value<Width> data[Size];
- };
-
- template<size_t... InitSize>
- explicit memory(size_t depth, const init<InitSize> &...init) : data(depth) {
- data.resize(depth);
- // This utterly reprehensible construct is the most reasonable way to apply a function to every element
- // of a parameter pack, if the elements all have different types and so cannot be cast to an initializer list.
- auto _ = {std::move(std::begin(init.data), std::end(init.data), data.begin() + init.offset)...};
- (void)_;
+ memory(memory<Width> &&) = default;
+ memory<Width> &operator=(memory<Width> &&other) {
+ assert(depth == other.depth);
+ data = std::move(other.data);
+ write_queue = std::move(other.write_queue);
+ return *this;
}
// An operator for direct memory reads. May be used at any time during the simulation.
const value<Width> &operator [](size_t index) const {
- assert(index < data.size());
+ assert(index < depth);
return data[index];
}
// An operator for direct memory writes. May only be used before the simulation is started. If used
// after the simulation is started, the design may malfunction.
value<Width> &operator [](size_t index) {
- assert(index < data.size());
+ assert(index < depth);
return data[index];
}
@@ -744,7 +808,7 @@ struct memory {
std::vector<write> write_queue;
void update(size_t index, const value<Width> &val, const value<Width> &mask, int priority = 0) {
- assert(index < data.size());
+ assert(index < depth);
// Queue up the write while keeping the queue sorted by priority.
write_queue.insert(
std::upper_bound(write_queue.begin(), write_queue.end(), priority,
@@ -814,35 +878,52 @@ struct metadata {
typedef std::map<std::string, metadata> metadata_map;
-// Helper class to disambiguate values/wires and their aliases.
+// Tag class to disambiguate values/wires and their aliases.
struct debug_alias {};
+// Tag declaration to disambiguate values and debug outlines.
+using debug_outline = ::_cxxrtl_outline;
+
// This structure is intended for consumption via foreign function interfaces, like Python's ctypes.
// Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++.
//
// To avoid violating strict aliasing rules, this structure has to be a subclass of the one used
// in the C API, or it would not be possible to cast between the pointers to these.
struct debug_item : ::cxxrtl_object {
+ // Object types.
enum : uint32_t {
- VALUE = CXXRTL_VALUE,
- WIRE = CXXRTL_WIRE,
- MEMORY = CXXRTL_MEMORY,
- ALIAS = CXXRTL_ALIAS,
+ VALUE = CXXRTL_VALUE,
+ WIRE = CXXRTL_WIRE,
+ MEMORY = CXXRTL_MEMORY,
+ ALIAS = CXXRTL_ALIAS,
+ OUTLINE = CXXRTL_OUTLINE,
+ };
+
+ // Object flags.
+ enum : uint32_t {
+ INPUT = CXXRTL_INPUT,
+ OUTPUT = CXXRTL_OUTPUT,
+ INOUT = CXXRTL_INOUT,
+ DRIVEN_SYNC = CXXRTL_DRIVEN_SYNC,
+ DRIVEN_COMB = CXXRTL_DRIVEN_COMB,
+ UNDRIVEN = CXXRTL_UNDRIVEN,
};
debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {}
template<size_t Bits>
- debug_item(value<Bits> &item, size_t lsb_offset = 0) {
+ debug_item(value<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = VALUE;
+ flags = flags_;
width = Bits;
lsb_at = lsb_offset;
depth = 1;
zero_at = 0;
curr = item.data;
next = item.data;
+ outline = nullptr;
}
template<size_t Bits>
@@ -850,26 +931,30 @@ struct debug_item : ::cxxrtl_object {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = VALUE;
+ flags = DRIVEN_COMB;
width = Bits;
lsb_at = lsb_offset;
depth = 1;
zero_at = 0;
curr = const_cast<chunk_t*>(item.data);
next = nullptr;
+ outline = nullptr;
}
template<size_t Bits>
- debug_item(wire<Bits> &item, size_t lsb_offset = 0) {
+ debug_item(wire<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) {
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
"wire<Bits> is not compatible with C layout");
type = WIRE;
+ flags = flags_;
width = Bits;
lsb_at = lsb_offset;
depth = 1;
zero_at = 0;
curr = item.curr.data;
next = item.next.data;
+ outline = nullptr;
}
template<size_t Width>
@@ -877,12 +962,14 @@ struct debug_item : ::cxxrtl_object {
static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t),
"memory<Width> is not compatible with C layout");
type = MEMORY;
+ flags = 0;
width = Width;
lsb_at = 0;
- depth = item.data.size();
+ depth = item.depth;
zero_at = zero_offset;
- curr = item.data.empty() ? nullptr : item.data[0].data;
+ curr = item.data ? item.data[0].data : nullptr;
next = nullptr;
+ outline = nullptr;
}
template<size_t Bits>
@@ -890,12 +977,14 @@ struct debug_item : ::cxxrtl_object {
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
"value<Bits> is not compatible with C layout");
type = ALIAS;
+ flags = DRIVEN_COMB;
width = Bits;
lsb_at = lsb_offset;
depth = 1;
zero_at = 0;
curr = const_cast<chunk_t*>(item.data);
next = nullptr;
+ outline = nullptr;
}
template<size_t Bits>
@@ -904,12 +993,45 @@ struct debug_item : ::cxxrtl_object {
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
"wire<Bits> is not compatible with C layout");
type = ALIAS;
+ flags = DRIVEN_COMB;
width = Bits;
lsb_at = lsb_offset;
depth = 1;
zero_at = 0;
curr = const_cast<chunk_t*>(item.curr.data);
next = nullptr;
+ outline = nullptr;
+ }
+
+ template<size_t Bits>
+ debug_item(debug_outline &group, const value<Bits> &item, size_t lsb_offset = 0) {
+ static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
+ "value<Bits> is not compatible with C layout");
+ type = OUTLINE;
+ flags = DRIVEN_COMB;
+ width = Bits;
+ lsb_at = lsb_offset;
+ depth = 1;
+ zero_at = 0;
+ curr = const_cast<chunk_t*>(item.data);
+ next = nullptr;
+ outline = &group;
+ }
+
+ template<size_t Bits, class IntegerT>
+ IntegerT get() const {
+ assert(width == Bits && depth == 1);
+ value<Bits> item;
+ std::copy(curr, curr + value<Bits>::chunks, item.data);
+ return item.template get<IntegerT>();
+ }
+
+ template<size_t Bits, class IntegerT>
+ void set(IntegerT other) const {
+ assert(width == Bits && depth == 1);
+ value<Bits> item;
+ item.template set<IntegerT>(other);
+ std::copy(item.data, item.data + value<Bits>::chunks, next);
}
};
static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout");
@@ -947,13 +1069,25 @@ struct debug_items {
}
};
+// Tag class to disambiguate the default constructor used by the toplevel module that calls reset(),
+// and the constructor of interior modules that should not call it.
+struct interior {};
+
struct module {
module() {}
virtual ~module() {}
+ // Modules with black boxes cannot be copied. Although not all designs include black boxes,
+ // delete the copy constructor and copy assignment operator to make sure that any downstream
+ // code that manipulates modules doesn't accidentally depend on their availability.
module(const module &) = delete;
module &operator=(const module &) = delete;
+ module(module &&) = default;
+ module &operator=(module &&) = default;
+
+ virtual void reset() = 0;
+
virtual bool eval() = 0;
virtual bool commit() = 0;
@@ -974,11 +1108,16 @@ struct module {
} // namespace cxxrtl
-// Internal structure used to communicate with the implementation of the C interface.
+// Internal structures used to communicate with the implementation of the C interface.
+
typedef struct _cxxrtl_toplevel {
std::unique_ptr<cxxrtl::module> module;
} *cxxrtl_toplevel;
+typedef struct _cxxrtl_outline {
+ std::function<void()> eval;
+} *cxxrtl_outline;
+
// Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic
// and indepenent of Yosys implementation details.
//
@@ -1112,49 +1251,49 @@ value<BitsY> xnor_ss(const value<BitsA> &a, const value<BitsB> &b) {
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> shl_uu(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template zcast<BitsY>().template shl(b);
+ return a.template zcast<BitsY>().shl(b);
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> shl_su(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template scast<BitsY>().template shl(b);
+ return a.template scast<BitsY>().shl(b);
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> sshl_uu(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template zcast<BitsY>().template shl(b);
+ return a.template zcast<BitsY>().shl(b);
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> sshl_su(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template scast<BitsY>().template shl(b);
+ return a.template scast<BitsY>().shl(b);
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> shr_uu(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template shr(b).template zcast<BitsY>();
+ return a.shr(b).template zcast<BitsY>();
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> shr_su(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template shr(b).template scast<BitsY>();
+ return a.shr(b).template scast<BitsY>();
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> sshr_uu(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template shr(b).template zcast<BitsY>();
+ return a.shr(b).template zcast<BitsY>();
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
CXXRTL_ALWAYS_INLINE
value<BitsY> sshr_su(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template sshr(b).template scast<BitsY>();
+ return a.sshr(b).template scast<BitsY>();
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc
index 5e5ba5ac0..404755b1e 100644
--- a/backends/cxxrtl/cxxrtl_backend.cc
+++ b/backends/cxxrtl/cxxrtl_backend.cc
@@ -22,6 +22,7 @@
#include "kernel/sigtools.h"
#include "kernel/utils.h"
#include "kernel/celltypes.h"
+#include "kernel/mem.h"
#include "kernel/log.h"
USING_YOSYS_NAMESPACE
@@ -194,27 +195,29 @@ bool is_extending_cell(RTLIL::IdString type)
ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool));
}
-bool is_elidable_cell(RTLIL::IdString type)
+bool is_inlinable_cell(RTLIL::IdString type)
{
return is_unary_cell(type) || is_binary_cell(type) || type.in(
- ID($mux), ID($concat), ID($slice), ID($pmux));
+ ID($mux), ID($concat), ID($slice), ID($pmux), ID($bmux), ID($demux));
}
-bool is_sync_ff_cell(RTLIL::IdString type)
+bool is_ff_cell(RTLIL::IdString type)
{
return type.in(
- ID($dff), ID($dffe), ID($sdff), ID($sdffe), ID($sdffce));
+ ID($dff), ID($dffe), ID($sdff), ID($sdffe), ID($sdffce),
+ ID($adff), ID($adffe), ID($dffsr), ID($dffsre),
+ ID($aldff), ID($aldffe),
+ ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr));
}
-bool is_ff_cell(RTLIL::IdString type)
+bool is_internal_cell(RTLIL::IdString type)
{
- return is_sync_ff_cell(type) || type.in(
- ID($adff), ID($adffe), ID($dffsr), ID($dffsre), ID($dlatch), ID($adlatch), ID($dlatchsr), ID($sr));
+ return !type.isPublic() && !type.begins_with("$paramod");
}
-bool is_internal_cell(RTLIL::IdString type)
+bool is_effectful_cell(RTLIL::IdString type)
{
- return type[0] == '$' && !type.begins_with("$paramod");
+ return type.isPublic();
}
bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell)
@@ -224,24 +227,29 @@ bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell)
return cell_module->get_bool_attribute(ID(cxxrtl_blackbox));
}
+bool is_memwr_process(const RTLIL::Process *process)
+{
+ for (auto sync : process->syncs)
+ if (!sync->mem_write_actions.empty())
+ return true;
+ return false;
+}
+
enum class CxxrtlPortType {
UNKNOWN = 0, // or mixed comb/sync
COMB = 1,
SYNC = 2,
};
-CxxrtlPortType cxxrtl_port_type(const RTLIL::Cell *cell, RTLIL::IdString port)
+CxxrtlPortType cxxrtl_port_type(RTLIL::Module *module, RTLIL::IdString port)
{
- RTLIL::Module *cell_module = cell->module->design->module(cell->type);
- if (cell_module == nullptr || !cell_module->get_bool_attribute(ID(cxxrtl_blackbox)))
- return CxxrtlPortType::UNKNOWN;
- RTLIL::Wire *cell_output_wire = cell_module->wire(port);
- log_assert(cell_output_wire != nullptr);
- bool is_comb = cell_output_wire->get_bool_attribute(ID(cxxrtl_comb));
- bool is_sync = cell_output_wire->get_bool_attribute(ID(cxxrtl_sync));
+ RTLIL::Wire *output_wire = module->wire(port);
+ log_assert(output_wire != nullptr);
+ bool is_comb = output_wire->get_bool_attribute(ID(cxxrtl_comb));
+ bool is_sync = output_wire->get_bool_attribute(ID(cxxrtl_sync));
if (is_comb && is_sync)
log_cmd_error("Port `%s.%s' is marked as both `cxxrtl_comb` and `cxxrtl_sync`.\n",
- log_id(cell_module), log_signal(cell_output_wire));
+ log_id(module), log_signal(output_wire));
else if (is_comb)
return CxxrtlPortType::COMB;
else if (is_sync)
@@ -249,6 +257,14 @@ CxxrtlPortType cxxrtl_port_type(const RTLIL::Cell *cell, RTLIL::IdString port)
return CxxrtlPortType::UNKNOWN;
}
+CxxrtlPortType cxxrtl_port_type(const RTLIL::Cell *cell, RTLIL::IdString port)
+{
+ RTLIL::Module *cell_module = cell->module->design->module(cell->type);
+ if (cell_module == nullptr || !cell_module->get_bool_attribute(ID(cxxrtl_blackbox)))
+ return CxxrtlPortType::UNKNOWN;
+ return cxxrtl_port_type(cell_module, port);
+}
+
bool is_cxxrtl_comb_port(const RTLIL::Cell *cell, RTLIL::IdString port)
{
return cxxrtl_port_type(cell, port) == CxxrtlPortType::COMB;
@@ -265,18 +281,26 @@ struct FlowGraph {
CONNECT,
CELL_SYNC,
CELL_EVAL,
- PROCESS
+ PROCESS_SYNC,
+ PROCESS_CASE,
+ MEM_RDPORT,
+ MEM_WRPORTS,
};
Type type;
RTLIL::SigSig connect = {};
- const RTLIL::Cell *cell = NULL;
- const RTLIL::Process *process = NULL;
+ const RTLIL::Cell *cell = nullptr;
+ const RTLIL::Process *process = nullptr;
+ const Mem *mem = nullptr;
+ int portidx;
};
std::vector<Node*> nodes;
dict<const RTLIL::Wire*, pool<Node*, hash_ptr_ops>> wire_comb_defs, wire_sync_defs, wire_uses;
- dict<const RTLIL::Wire*, bool> wire_def_elidable, wire_use_elidable;
+ dict<Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_comb_defs, node_sync_defs, node_uses;
+ dict<const RTLIL::Wire*, bool> wire_def_inlinable;
+ dict<const RTLIL::Wire*, dict<Node*, bool, hash_ptr_ops>> wire_use_inlinable;
+ dict<RTLIL::SigBit, bool> bit_has_state;
~FlowGraph()
{
@@ -284,18 +308,33 @@ struct FlowGraph {
delete node;
}
- void add_defs(Node *node, const RTLIL::SigSpec &sig, bool fully_sync, bool elidable)
+ void add_defs(Node *node, const RTLIL::SigSpec &sig, bool is_ff, bool inlinable)
{
for (auto chunk : sig.chunks())
if (chunk.wire) {
- if (fully_sync)
+ if (is_ff) {
+ // A sync def means that a wire holds design state because it is driven directly by
+ // a flip-flop output. Such a wire can never be unbuffered.
wire_sync_defs[chunk.wire].insert(node);
- else
+ node_sync_defs[node].insert(chunk.wire);
+ } else {
+ // A comb def means that a wire doesn't hold design state. It might still be connected,
+ // indirectly, to a flip-flop output.
wire_comb_defs[chunk.wire].insert(node);
+ node_comb_defs[node].insert(chunk.wire);
+ }
}
- // Only comb defs of an entire wire in the right order can be elided.
- if (!fully_sync && sig.is_wire())
- wire_def_elidable[sig.as_wire()] = elidable;
+ for (auto bit : sig.bits())
+ bit_has_state[bit] |= is_ff;
+ // Only comb defs of an entire wire in the right order can be inlined.
+ if (!is_ff && sig.is_wire()) {
+ // Only a single def of a wire can be inlined. (Multiple defs of a wire are unsound, but we
+ // handle them anyway to avoid assertion failures later.)
+ if (!wire_def_inlinable.count(sig.as_wire()))
+ wire_def_inlinable[sig.as_wire()] = inlinable;
+ else
+ wire_def_inlinable[sig.as_wire()] = false;
+ }
}
void add_uses(Node *node, const RTLIL::SigSpec &sig)
@@ -303,26 +342,41 @@ struct FlowGraph {
for (auto chunk : sig.chunks())
if (chunk.wire) {
wire_uses[chunk.wire].insert(node);
- // Only a single use of an entire wire in the right order can be elided.
- // (But the use can include other chunks.)
- if (!wire_use_elidable.count(chunk.wire))
- wire_use_elidable[chunk.wire] = true;
+ node_uses[node].insert(chunk.wire);
+ // Only a single use of an entire wire in the right order can be inlined. (But the use can include
+ // other chunks.) This is tracked per-node because a wire used by multiple nodes can still be inlined
+ // if all but one of those nodes is dead.
+ if (!wire_use_inlinable[chunk.wire].count(node))
+ wire_use_inlinable[chunk.wire][node] = true;
else
- wire_use_elidable[chunk.wire] = false;
+ wire_use_inlinable[chunk.wire][node] = false;
}
}
- bool is_elidable(const RTLIL::Wire *wire) const
+ bool is_inlinable(const RTLIL::Wire *wire) const
{
- if (wire_def_elidable.count(wire) && wire_use_elidable.count(wire))
- return wire_def_elidable.at(wire) && wire_use_elidable.at(wire);
+ // Can the wire be inlined at all?
+ if (wire_def_inlinable.count(wire))
+ return wire_def_inlinable.at(wire);
+ return false;
+ }
+
+ bool is_inlinable(const RTLIL::Wire *wire, const pool<Node*, hash_ptr_ops> &nodes) const
+ {
+ // Can the wire be inlined, knowing that the given nodes are reachable?
+ if (nodes.size() != 1)
+ return false;
+ Node *node = *nodes.begin();
+ log_assert(node_uses.at(node).count(wire));
+ if (is_inlinable(wire) && wire_use_inlinable.count(wire) && wire_use_inlinable.at(wire).count(node))
+ return wire_use_inlinable.at(wire).at(node);
return false;
}
// Connections
void add_connect_defs_uses(Node *node, const RTLIL::SigSig &conn)
{
- add_defs(node, conn.first, /*fully_sync=*/false, /*elidable=*/true);
+ add_defs(node, conn.first, /*is_ff=*/false, /*inlinable=*/true);
add_uses(node, conn.second);
}
@@ -368,8 +422,8 @@ struct FlowGraph {
for (auto conn : cell->connections())
if (cell->output(conn.first))
if (is_cxxrtl_sync_port(cell, conn.first)) {
- // See note regarding elidability below.
- add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false);
+ // See note regarding inlinability below.
+ add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false);
}
}
@@ -377,19 +431,19 @@ struct FlowGraph {
{
for (auto conn : cell->connections()) {
if (cell->output(conn.first)) {
- if (is_elidable_cell(cell->type))
- add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/true);
- else if (is_sync_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool()))
- add_defs(node, conn.second, /*fully_sync=*/true, /*elidable=*/false);
+ if (is_inlinable_cell(cell->type))
+ add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/true);
+ else if (is_ff_cell(cell->type))
+ add_defs(node, conn.second, /*is_ff=*/true, /*inlinable=*/false);
else if (is_internal_cell(cell->type))
- add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false);
+ add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false);
else if (!is_cxxrtl_sync_port(cell, conn.first)) {
- // Although at first it looks like outputs of user-defined cells may always be elided, the reality is
- // more complex. Fully sync outputs produce no defs and so don't participate in elision. Fully comb
+ // Although at first it looks like outputs of user-defined cells may always be inlined, the reality is
+ // more complex. Fully sync outputs produce no defs and so don't participate in inlining. Fully comb
// outputs are assigned in a different way depending on whether the cell's eval() immediately converged.
- // Unknown/mixed outputs could be elided, but should be rare in practical designs and don't justify
- // the infrastructure required to elide outputs of cells with many of them.
- add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false);
+ // Unknown/mixed outputs could be inlined, but should be rare in practical designs and don't justify
+ // the infrastructure required to inline outputs of cells with many of them.
+ add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false);
}
}
if (cell->input(conn.first))
@@ -424,10 +478,10 @@ struct FlowGraph {
}
// Processes
- void add_case_defs_uses(Node *node, const RTLIL::CaseRule *case_)
+ void add_case_rule_defs_uses(Node *node, const RTLIL::CaseRule *case_)
{
for (auto &action : case_->actions) {
- add_defs(node, action.first, /*is_sync=*/false, /*elidable=*/false);
+ add_defs(node, action.first, /*is_ff=*/false, /*inlinable=*/false);
add_uses(node, action.second);
}
for (auto sub_switch : case_->switches) {
@@ -435,33 +489,89 @@ struct FlowGraph {
for (auto sub_case : sub_switch->cases) {
for (auto &compare : sub_case->compare)
add_uses(node, compare);
- add_case_defs_uses(node, sub_case);
+ add_case_rule_defs_uses(node, sub_case);
}
}
}
- void add_process_defs_uses(Node *node, const RTLIL::Process *process)
+ void add_sync_rules_defs_uses(Node *node, const RTLIL::Process *process)
{
- add_case_defs_uses(node, &process->root_case);
- for (auto sync : process->syncs)
- for (auto action : sync->actions) {
+ for (auto sync : process->syncs) {
+ for (auto &action : sync->actions) {
if (sync->type == RTLIL::STp || sync->type == RTLIL::STn || sync->type == RTLIL::STe)
- add_defs(node, action.first, /*is_sync=*/true, /*elidable=*/false);
+ add_defs(node, action.first, /*is_ff=*/true, /*inlinable=*/false);
else
- add_defs(node, action.first, /*is_sync=*/false, /*elidable=*/false);
+ add_defs(node, action.first, /*is_ff=*/false, /*inlinable=*/false);
add_uses(node, action.second);
}
+ for (auto &memwr : sync->mem_write_actions) {
+ add_uses(node, memwr.address);
+ add_uses(node, memwr.data);
+ add_uses(node, memwr.enable);
+ }
+ }
}
Node *add_node(const RTLIL::Process *process)
{
Node *node = new Node;
- node->type = Node::Type::PROCESS;
+ node->type = Node::Type::PROCESS_SYNC;
node->process = process;
nodes.push_back(node);
- add_process_defs_uses(node, process);
+ add_sync_rules_defs_uses(node, process);
+
+ node = new Node;
+ node->type = Node::Type::PROCESS_CASE;
+ node->process = process;
+ nodes.push_back(node);
+ add_case_rule_defs_uses(node, &process->root_case);
return node;
}
+
+ // Memories
+ void add_node(const Mem *mem) {
+ for (int i = 0; i < GetSize(mem->rd_ports); i++) {
+ auto &port = mem->rd_ports[i];
+ Node *node = new Node;
+ node->type = Node::Type::MEM_RDPORT;
+ node->mem = mem;
+ node->portidx = i;
+ nodes.push_back(node);
+ add_defs(node, port.data, /*is_ff=*/port.clk_enable, /*inlinable=*/false);
+ add_uses(node, port.clk);
+ add_uses(node, port.en);
+ add_uses(node, port.arst);
+ add_uses(node, port.srst);
+ add_uses(node, port.addr);
+ bool transparent = false;
+ for (int j = 0; j < GetSize(mem->wr_ports); j++) {
+ auto &wrport = mem->wr_ports[j];
+ if (port.transparency_mask[j]) {
+ // Our implementation of transparent read ports reads en, addr and data from every write port
+ // the read port is transparent with.
+ add_uses(node, wrport.en);
+ add_uses(node, wrport.addr);
+ add_uses(node, wrport.data);
+ transparent = true;
+ }
+ }
+ // Also we read the read address twice in this case (prevent inlining).
+ if (transparent)
+ add_uses(node, port.addr);
+ }
+ if (!mem->wr_ports.empty()) {
+ Node *node = new Node;
+ node->type = Node::Type::MEM_WRPORTS;
+ node->mem = mem;
+ nodes.push_back(node);
+ for (auto &port : mem->wr_ports) {
+ add_uses(node, port.clk);
+ add_uses(node, port.en);
+ add_uses(node, port.addr);
+ add_uses(node, port.data);
+ }
+ }
+ }
};
std::vector<std::string> split_by(const std::string &str, const std::string &sep)
@@ -515,6 +625,58 @@ std::string get_hdl_name(T *object)
return object->name.str().substr(1);
}
+struct WireType {
+ enum Type {
+ // Non-referenced wire; is not a part of the design.
+ UNUSED,
+ // Double-buffered wire; is a class member, and holds design state.
+ BUFFERED,
+ // Single-buffered wire; is a class member, but holds no state.
+ MEMBER,
+ // Single-buffered wire; is a class member, and is computed on demand.
+ OUTLINE,
+ // Local wire; is a local variable in eval method.
+ LOCAL,
+ // Inline wire; is an unnamed temporary in eval method.
+ INLINE,
+ // Alias wire; is replaced with aliasee, except in debug info.
+ ALIAS,
+ // Const wire; is replaced with constant, except in debug info.
+ CONST,
+ };
+
+ Type type = UNUSED;
+ const RTLIL::Cell *cell_subst = nullptr; // for INLINE
+ RTLIL::SigSpec sig_subst = {}; // for INLINE, ALIAS, and CONST
+
+ WireType() = default;
+
+ WireType(Type type) : type(type) {
+ log_assert(type == UNUSED || type == BUFFERED || type == MEMBER || type == OUTLINE || type == LOCAL);
+ }
+
+ WireType(Type type, const RTLIL::Cell *cell) : type(type), cell_subst(cell) {
+ log_assert(type == INLINE && is_inlinable_cell(cell->type));
+ }
+
+ WireType(Type type, RTLIL::SigSpec sig) : type(type), sig_subst(sig) {
+ log_assert(type == INLINE || (type == ALIAS && sig.is_wire()) || (type == CONST && sig.is_fully_const()));
+ }
+
+ bool is_buffered() const { return type == BUFFERED; }
+ bool is_member() const { return type == BUFFERED || type == MEMBER || type == OUTLINE; }
+ bool is_outline() const { return type == OUTLINE; }
+ bool is_named() const { return is_member() || type == LOCAL; }
+ bool is_local() const { return type == LOCAL || type == INLINE; }
+ bool is_exact() const { return type == ALIAS || type == CONST; }
+};
+
+// Tests for a SigSpec that is a valid clock input, clocks have to have a backing wire and be a single bit
+// using this instead of sig.is_wire() solves issues when the clock is a slice instead of a full wire
+bool is_valid_clock(const RTLIL::SigSpec& sig) {
+ return sig.is_chunk() && sig.is_bit() && sig[0].wire;
+}
+
struct CxxrtlWorker {
bool split_intf = false;
std::string intf_filename;
@@ -522,6 +684,9 @@ struct CxxrtlWorker {
std::ostream *impl_f = nullptr;
std::ostream *intf_f = nullptr;
+ bool print_wire_types = false;
+ bool print_debug_wire_types = false;
+ bool run_hierarchy = false;
bool run_flatten = false;
bool run_proc = false;
@@ -529,26 +694,27 @@ struct CxxrtlWorker {
bool unbuffer_public = false;
bool localize_internal = false;
bool localize_public = false;
- bool elide_internal = false;
- bool elide_public = false;
+ bool inline_internal = false;
+ bool inline_public = false;
bool debug_info = false;
+ bool debug_member = false;
+ bool debug_alias = false;
+ bool debug_eval = false;
std::ostringstream f;
std::string indent;
int temporary = 0;
dict<const RTLIL::Module*, SigMap> sigmaps;
+ dict<const RTLIL::Module*, std::vector<Mem>> mod_memories;
+ pool<std::pair<const RTLIL::Module*, RTLIL::IdString>> writable_memories;
pool<const RTLIL::Wire*> edge_wires;
+ dict<const RTLIL::Wire*, RTLIL::Const> wire_init;
dict<RTLIL::SigBit, RTLIL::SyncType> edge_types;
- pool<const RTLIL::Memory*> writable_memories;
- dict<const RTLIL::Cell*, pool<const RTLIL::Cell*>> transparent_for;
- dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires;
- dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule;
- pool<const RTLIL::Wire*> unbuffered_wires;
- pool<const RTLIL::Wire*> localized_wires;
- dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires;
- dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires;
+ dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule, debug_schedule;
+ dict<const RTLIL::Wire*, WireType> wire_types, debug_wire_types;
+ dict<RTLIL::SigBit, bool> bit_has_state;
dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations;
dict<const RTLIL::Module*, bool> eval_converges;
@@ -629,6 +795,11 @@ struct CxxrtlWorker {
return mangle_module_name(module->name, /*is_blackbox=*/module->get_bool_attribute(ID(cxxrtl_blackbox)));
}
+ std::string mangle(const Mem *mem)
+ {
+ return mangle_memory_name(mem->memid);
+ }
+
std::string mangle(const RTLIL::Memory *memory)
{
return mangle_memory_name(memory->name);
@@ -763,11 +934,6 @@ struct CxxrtlWorker {
f << "}";
}
- void dump_const_init(const RTLIL::Const &data)
- {
- dump_const_init(data, data.size());
- }
-
void dump_const(const RTLIL::Const &data, int width, int offset = 0, bool fixed_width = false)
{
f << "value<" << width << ">";
@@ -779,30 +945,37 @@ struct CxxrtlWorker {
dump_const(data, data.size());
}
- bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs)
+ bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs, bool for_debug = false)
{
if (chunk.wire == NULL) {
dump_const(chunk.data, chunk.width, chunk.offset);
return false;
} else {
- if (elided_wires.count(chunk.wire)) {
- log_assert(!is_lhs);
- const FlowGraph::Node &node = elided_wires[chunk.wire];
- switch (node.type) {
- case FlowGraph::Node::Type::CONNECT:
- dump_connect_elided(node.connect);
- break;
- case FlowGraph::Node::Type::CELL_EVAL:
- log_assert(is_elidable_cell(node.cell->type));
- dump_cell_elided(node.cell);
+ const auto &wire_type = (for_debug ? debug_wire_types : wire_types)[chunk.wire];
+ switch (wire_type.type) {
+ case WireType::BUFFERED:
+ f << mangle(chunk.wire) << (is_lhs ? ".next" : ".curr");
+ break;
+ case WireType::MEMBER:
+ case WireType::LOCAL:
+ case WireType::OUTLINE:
+ f << mangle(chunk.wire);
+ break;
+ case WireType::INLINE:
+ log_assert(!is_lhs);
+ if (wire_type.cell_subst != nullptr) {
+ dump_cell_expr(wire_type.cell_subst, for_debug);
break;
- default:
- log_assert(false);
- }
- } else if (unbuffered_wires[chunk.wire]) {
- f << mangle(chunk.wire);
- } else {
- f << mangle(chunk.wire) << (is_lhs ? ".next" : ".curr");
+ }
+ YS_FALLTHROUGH
+ case WireType::ALIAS:
+ case WireType::CONST:
+ log_assert(!is_lhs);
+ return dump_sigspec(wire_type.sig_subst.extract(chunk.offset, chunk.width), is_lhs, for_debug);
+ case WireType::UNUSED:
+ log_assert(is_lhs);
+ f << "value<" << chunk.width << ">()";
+ return false;
}
if (chunk.width == chunk.wire->width && chunk.offset == 0)
return false;
@@ -814,92 +987,116 @@ struct CxxrtlWorker {
}
}
- bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs)
+ bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs, bool for_debug = false)
{
if (sig.empty()) {
f << "value<0>()";
return false;
} else if (sig.is_chunk()) {
- return dump_sigchunk(sig.as_chunk(), is_lhs);
+ return dump_sigchunk(sig.as_chunk(), is_lhs, for_debug);
} else {
- dump_sigchunk(*sig.chunks().rbegin(), is_lhs);
- for (auto it = sig.chunks().rbegin() + 1; it != sig.chunks().rend(); ++it) {
- f << ".concat(";
- dump_sigchunk(*it, is_lhs);
- f << ")";
+ bool first = true;
+ auto chunks = sig.chunks();
+ for (auto it = chunks.rbegin(); it != chunks.rend(); it++) {
+ if (!first)
+ f << ".concat(";
+ bool is_complex = dump_sigchunk(*it, is_lhs, for_debug);
+ if (!is_lhs && it->width == 1) {
+ size_t repeat = 1;
+ while ((it + repeat) != chunks.rend() && *(it + repeat) == *it)
+ repeat++;
+ if (repeat > 1) {
+ if (is_complex)
+ f << ".val()";
+ f << ".repeat<" << repeat << ">()";
+ }
+ it += repeat - 1;
+ }
+ if (!first)
+ f << ")";
+ first = false;
}
return true;
}
}
- void dump_sigspec_lhs(const RTLIL::SigSpec &sig)
+ void dump_sigspec_lhs(const RTLIL::SigSpec &sig, bool for_debug = false)
{
- dump_sigspec(sig, /*is_lhs=*/true);
+ dump_sigspec(sig, /*is_lhs=*/true, for_debug);
}
- void dump_sigspec_rhs(const RTLIL::SigSpec &sig)
+ void dump_sigspec_rhs(const RTLIL::SigSpec &sig, bool for_debug = false)
{
// In the contexts where we want template argument deduction to occur for `template<size_t Bits> ... value<Bits>`,
// it is necessary to have the argument to already be a `value<N>`, since template argument deduction and implicit
// type conversion are mutually exclusive. In these contexts, we use dump_sigspec_rhs() to emit an explicit
// type conversion, but only if the expression needs it.
- bool is_complex = dump_sigspec(sig, /*is_lhs=*/false);
+ bool is_complex = dump_sigspec(sig, /*is_lhs=*/false, for_debug);
if (is_complex)
f << ".val()";
}
- void collect_sigspec_rhs(const RTLIL::SigSpec &sig, std::vector<RTLIL::IdString> &cells)
+ void dump_inlined_cells(const std::vector<const RTLIL::Cell*> &cells)
+ {
+ if (cells.empty()) {
+ f << indent << "// connection\n";
+ } else if (cells.size() == 1) {
+ dump_attrs(cells.front());
+ f << indent << "// cell " << cells.front()->name.str() << "\n";
+ } else {
+ f << indent << "// cells";
+ for (auto cell : cells)
+ f << " " << cell->name.str();
+ f << "\n";
+ }
+ }
+
+ void collect_sigspec_rhs(const RTLIL::SigSpec &sig, bool for_debug, std::vector<const RTLIL::Cell*> &cells)
{
for (auto chunk : sig.chunks()) {
- if (!chunk.wire || !elided_wires.count(chunk.wire))
+ if (!chunk.wire)
continue;
-
- const FlowGraph::Node &node = elided_wires[chunk.wire];
- switch (node.type) {
- case FlowGraph::Node::Type::CONNECT:
- collect_connect(node.connect, cells);
- break;
- case FlowGraph::Node::Type::CELL_EVAL:
- collect_cell_eval(node.cell, cells);
+ const auto &wire_type = wire_types[chunk.wire];
+ switch (wire_type.type) {
+ case WireType::INLINE:
+ if (wire_type.cell_subst != nullptr) {
+ collect_cell_eval(wire_type.cell_subst, for_debug, cells);
+ break;
+ }
+ YS_FALLTHROUGH
+ case WireType::ALIAS:
+ collect_sigspec_rhs(wire_type.sig_subst, for_debug, cells);
break;
default:
- log_assert(false);
+ break;
}
}
}
- void dump_connect_elided(const RTLIL::SigSig &conn)
- {
- dump_sigspec_rhs(conn.second);
- }
-
- bool is_connect_elided(const RTLIL::SigSig &conn)
+ void dump_connect_expr(const RTLIL::SigSig &conn, bool for_debug = false)
{
- return conn.first.is_wire() && elided_wires.count(conn.first.as_wire());
+ dump_sigspec_rhs(conn.second, for_debug);
}
- void collect_connect(const RTLIL::SigSig &conn, std::vector<RTLIL::IdString> &cells)
+ void dump_connect(const RTLIL::SigSig &conn, bool for_debug = false)
{
- if (!is_connect_elided(conn))
- return;
-
- collect_sigspec_rhs(conn.second, cells);
- }
+ std::vector<const RTLIL::Cell*> inlined_cells;
+ collect_sigspec_rhs(conn.second, for_debug, inlined_cells);
+ dump_inlined_cells(inlined_cells);
- void dump_connect(const RTLIL::SigSig &conn)
- {
- if (is_connect_elided(conn))
- return;
-
- f << indent << "// connection\n";
f << indent;
- dump_sigspec_lhs(conn.first);
+ dump_sigspec_lhs(conn.first, for_debug);
f << " = ";
- dump_connect_elided(conn);
+ dump_connect_expr(conn, for_debug);
f << ";\n";
}
- void dump_cell_sync(const RTLIL::Cell *cell)
+ void collect_connect(const RTLIL::SigSig &conn, bool for_debug, std::vector<const RTLIL::Cell*> &cells)
+ {
+ collect_sigspec_rhs(conn.second, for_debug, cells);
+ }
+
+ void dump_cell_sync(const RTLIL::Cell *cell, bool for_debug = false)
{
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
f << indent << "// cell " << cell->name.str() << " syncs\n";
@@ -907,12 +1104,12 @@ struct CxxrtlWorker {
if (cell->output(conn.first))
if (is_cxxrtl_sync_port(cell, conn.first)) {
f << indent;
- dump_sigspec_lhs(conn.second);
+ dump_sigspec_lhs(conn.second, for_debug);
f << " = " << mangle(cell) << access << mangle_wire_name(conn.first) << ".curr;\n";
}
}
- void dump_cell_elided(const RTLIL::Cell *cell)
+ void dump_cell_expr(const RTLIL::Cell *cell, bool for_debug = false)
{
// Unary cells
if (is_unary_cell(cell->type)) {
@@ -920,7 +1117,7 @@ struct CxxrtlWorker {
if (is_extending_cell(cell->type))
f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u');
f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
- dump_sigspec_rhs(cell->getPort(ID::A));
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
f << ")";
// Binary cells
} else if (is_binary_cell(cell->type)) {
@@ -929,18 +1126,18 @@ struct CxxrtlWorker {
f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') <<
(cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u');
f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
- dump_sigspec_rhs(cell->getPort(ID::A));
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
f << ", ";
- dump_sigspec_rhs(cell->getPort(ID::B));
+ dump_sigspec_rhs(cell->getPort(ID::B), for_debug);
f << ")";
// Muxes
} else if (cell->type == ID($mux)) {
f << "(";
- dump_sigspec_rhs(cell->getPort(ID::S));
+ dump_sigspec_rhs(cell->getPort(ID::S), for_debug);
f << " ? ";
- dump_sigspec_rhs(cell->getPort(ID::B));
+ dump_sigspec_rhs(cell->getPort(ID::B), for_debug);
f << " : ";
- dump_sigspec_rhs(cell->getPort(ID::A));
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
f << ")";
// Parallel (one-hot) muxes
} else if (cell->type == ID($pmux)) {
@@ -948,24 +1145,40 @@ struct CxxrtlWorker {
int s_width = cell->getParam(ID::S_WIDTH).as_int();
for (int part = 0; part < s_width; part++) {
f << "(";
- dump_sigspec_rhs(cell->getPort(ID::S).extract(part));
+ dump_sigspec_rhs(cell->getPort(ID::S).extract(part), for_debug);
f << " ? ";
- dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width));
+ dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width), for_debug);
f << " : ";
}
- dump_sigspec_rhs(cell->getPort(ID::A));
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
for (int part = 0; part < s_width; part++) {
f << ")";
}
+ // Big muxes
+ } else if (cell->type == ID($bmux)) {
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
+ f << ".bmux<";
+ f << cell->getParam(ID::WIDTH).as_int();
+ f << ">(";
+ dump_sigspec_rhs(cell->getPort(ID::S), for_debug);
+ f << ").val()";
+ // Demuxes
+ } else if (cell->type == ID($demux)) {
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
+ f << ".demux<";
+ f << GetSize(cell->getPort(ID::Y));
+ f << ">(";
+ dump_sigspec_rhs(cell->getPort(ID::S), for_debug);
+ f << ").val()";
// Concats
} else if (cell->type == ID($concat)) {
- dump_sigspec_rhs(cell->getPort(ID::B));
+ dump_sigspec_rhs(cell->getPort(ID::B), for_debug);
f << ".concat(";
- dump_sigspec_rhs(cell->getPort(ID::A));
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
f << ").val()";
// Slices
} else if (cell->type == ID($slice)) {
- dump_sigspec_rhs(cell->getPort(ID::A));
+ dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
f << ".slice<";
f << cell->getParam(ID::OFFSET).as_int() + cell->getParam(ID::Y_WIDTH).as_int() - 1;
f << ",";
@@ -976,61 +1189,33 @@ struct CxxrtlWorker {
}
}
- bool is_cell_elided(const RTLIL::Cell *cell)
- {
- return is_elidable_cell(cell->type) && cell->hasPort(ID::Y) && cell->getPort(ID::Y).is_wire() &&
- elided_wires.count(cell->getPort(ID::Y).as_wire());
- }
-
- void collect_cell_eval(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells)
+ void dump_cell_eval(const RTLIL::Cell *cell, bool for_debug = false)
{
- if (!is_cell_elided(cell))
- return;
-
- cells.push_back(cell->name);
- for (auto port : cell->connections())
- if (port.first != ID::Y)
- collect_sigspec_rhs(port.second, cells);
- }
-
- void dump_cell_eval(const RTLIL::Cell *cell)
- {
- if (is_cell_elided(cell))
- return;
- if (cell->type == ID($meminit))
- return; // Handled elsewhere.
-
- std::vector<RTLIL::IdString> elided_cells;
- if (is_elidable_cell(cell->type)) {
- for (auto port : cell->connections())
- if (port.first != ID::Y)
- collect_sigspec_rhs(port.second, elided_cells);
- }
- if (elided_cells.empty()) {
- dump_attrs(cell);
- f << indent << "// cell " << cell->name.str() << "\n";
- } else {
- f << indent << "// cells";
- for (auto elided_cell : elided_cells)
- f << " " << elided_cell.str();
- f << "\n";
- }
+ std::vector<const RTLIL::Cell*> inlined_cells;
+ collect_cell_eval(cell, for_debug, inlined_cells);
+ dump_inlined_cells(inlined_cells);
// Elidable cells
- if (is_elidable_cell(cell->type)) {
+ if (is_inlinable_cell(cell->type)) {
f << indent;
- dump_sigspec_lhs(cell->getPort(ID::Y));
+ dump_sigspec_lhs(cell->getPort(ID::Y), for_debug);
f << " = ";
- dump_cell_elided(cell);
+ dump_cell_expr(cell, for_debug);
f << ";\n";
// Flip-flops
} else if (is_ff_cell(cell->type)) {
- if (cell->hasPort(ID::CLK) && cell->getPort(ID::CLK).is_wire()) {
+ log_assert(!for_debug);
+ // Clocks might be slices of larger signals but should only ever be single bit
+ if (cell->hasPort(ID::CLK) && is_valid_clock(cell->getPort(ID::CLK))) {
// Edge-sensitive logic
RTLIL::SigBit clk_bit = cell->getPort(ID::CLK)[0];
clk_bit = sigmaps[clk_bit.wire->module](clk_bit);
- f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
- << mangle(clk_bit) << ") {\n";
+ if (clk_bit.wire) {
+ f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
+ << mangle(clk_bit) << ") {\n";
+ } else {
+ f << indent << "if (false) {\n";
+ }
inc_indent();
if (cell->hasPort(ID::EN)) {
f << indent << "if (";
@@ -1094,6 +1279,20 @@ struct CxxrtlWorker {
dec_indent();
f << indent << "}\n";
}
+ if (cell->hasPort(ID::ALOAD)) {
+ // Asynchronous load
+ f << indent << "if (";
+ dump_sigspec_rhs(cell->getPort(ID::ALOAD));
+ f << " == value<1> {" << cell->getParam(ID::ALOAD_POLARITY).as_bool() << "u}) {\n";
+ inc_indent();
+ f << indent;
+ dump_sigspec_lhs(cell->getPort(ID::Q));
+ f << " = ";
+ dump_sigspec_rhs(cell->getPort(ID::AD));
+ f << ";\n";
+ dec_indent();
+ f << indent << "}\n";
+ }
if (cell->hasPort(ID::SET)) {
// Asynchronous set (for individual bits)
f << indent;
@@ -1118,120 +1317,29 @@ struct CxxrtlWorker {
dump_sigspec_rhs(cell->getPort(ID::CLR));
f << (cell->getParam(ID::CLR_POLARITY).as_bool() ? "" : ".bit_not()") << ");\n";
}
- // Memory ports
- } else if (cell->type.in(ID($memrd), ID($memwr))) {
- if (cell->getParam(ID::CLK_ENABLE).as_bool()) {
- RTLIL::SigBit clk_bit = cell->getPort(ID::CLK)[0];
- clk_bit = sigmaps[clk_bit.wire->module](clk_bit);
- f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
- << mangle(clk_bit) << ") {\n";
- inc_indent();
- }
- RTLIL::Memory *memory = cell->module->memories[cell->getParam(ID::MEMID).decode_string()];
- std::string valid_index_temp = fresh_temporary();
- f << indent << "auto " << valid_index_temp << " = memory_index(";
- dump_sigspec_rhs(cell->getPort(ID::ADDR));
- f << ", " << memory->start_offset << ", " << memory->size << ");\n";
- if (cell->type == ID($memrd)) {
- bool has_enable = cell->getParam(ID::CLK_ENABLE).as_bool() && !cell->getPort(ID::EN).is_fully_ones();
- if (has_enable) {
- f << indent << "if (";
- dump_sigspec_rhs(cell->getPort(ID::EN));
- f << ") {\n";
- inc_indent();
- }
- // The generated code has two bounds checks; one in an assertion, and another that guards the read.
- // This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless
- // loudly crashes if an illegal condition is encountered. The assert may be turned off with -NDEBUG not
- // just for release builds, but also to make sure the simulator (which is presumably embedded in some
- // larger program) will never crash the code that calls into it.
- //
- // If assertions are disabled, out of bounds reads are defined to return zero.
- f << indent << "assert(" << valid_index_temp << ".valid && \"out of bounds read\");\n";
- f << indent << "if(" << valid_index_temp << ".valid) {\n";
- inc_indent();
- if (writable_memories[memory]) {
- std::string lhs_temp = fresh_temporary();
- f << indent << "value<" << memory->width << "> " << lhs_temp << " = "
- << mangle(memory) << "[" << valid_index_temp << ".index];\n";
- std::vector<const RTLIL::Cell*> memwr_cells(transparent_for[cell].begin(), transparent_for[cell].end());
- if (!memwr_cells.empty()) {
- std::string addr_temp = fresh_temporary();
- f << indent << "const value<" << cell->getPort(ID::ADDR).size() << "> &" << addr_temp << " = ";
- dump_sigspec_rhs(cell->getPort(ID::ADDR));
- f << ";\n";
- std::sort(memwr_cells.begin(), memwr_cells.end(),
- [](const RTLIL::Cell *a, const RTLIL::Cell *b) {
- return a->getParam(ID::PRIORITY).as_int() < b->getParam(ID::PRIORITY).as_int();
- });
- for (auto memwr_cell : memwr_cells) {
- f << indent << "if (" << addr_temp << " == ";
- dump_sigspec_rhs(memwr_cell->getPort(ID::ADDR));
- f << ") {\n";
- inc_indent();
- f << indent << lhs_temp << " = " << lhs_temp;
- f << ".update(";
- dump_sigspec_rhs(memwr_cell->getPort(ID::DATA));
- f << ", ";
- dump_sigspec_rhs(memwr_cell->getPort(ID::EN));
- f << ");\n";
- dec_indent();
- f << indent << "}\n";
- }
- }
- f << indent;
- dump_sigspec_lhs(cell->getPort(ID::DATA));
- f << " = " << lhs_temp << ";\n";
- } else {
- f << indent;
- dump_sigspec_lhs(cell->getPort(ID::DATA));
- f << " = " << mangle(memory) << "[" << valid_index_temp << ".index];\n";
- }
- dec_indent();
- f << indent << "} else {\n";
- inc_indent();
- f << indent;
- dump_sigspec_lhs(cell->getPort(ID::DATA));
- f << " = value<" << memory->width << "> {};\n";
- dec_indent();
- f << indent << "}\n";
- if (has_enable) {
- dec_indent();
- f << indent << "}\n";
- }
- } else /*if (cell->type == ID($memwr))*/ {
- log_assert(writable_memories[memory]);
- // See above for rationale of having both the assert and the condition.
- //
- // If assertions are disabled, out of bounds writes are defined to do nothing.
- f << indent << "assert(" << valid_index_temp << ".valid && \"out of bounds write\");\n";
- f << indent << "if (" << valid_index_temp << ".valid) {\n";
- inc_indent();
- f << indent << mangle(memory) << ".update(" << valid_index_temp << ".index, ";
- dump_sigspec_rhs(cell->getPort(ID::DATA));
- f << ", ";
- dump_sigspec_rhs(cell->getPort(ID::EN));
- f << ", " << cell->getParam(ID::PRIORITY).as_int() << ");\n";
- dec_indent();
- f << indent << "}\n";
- }
- if (cell->getParam(ID::CLK_ENABLE).as_bool()) {
- dec_indent();
- f << indent << "}\n";
- }
// Internal cells
} else if (is_internal_cell(cell->type)) {
log_cmd_error("Unsupported internal cell `%s'.\n", cell->type.c_str());
// User cells
} else {
+ log_assert(!for_debug);
log_assert(cell->known());
+ bool buffered_inputs = false;
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
for (auto conn : cell->connections())
- if (cell->input(conn.first) && !cell->output(conn.first)) {
- f << indent << mangle(cell) << access << mangle_wire_name(conn.first) << " = ";
+ if (cell->input(conn.first)) {
+ RTLIL::Module *cell_module = cell->module->design->module(cell->type);
+ log_assert(cell_module != nullptr && cell_module->wire(conn.first));
+ RTLIL::Wire *cell_module_wire = cell_module->wire(conn.first);
+ f << indent << mangle(cell) << access << mangle_wire_name(conn.first);
+ if (!is_cxxrtl_blackbox_cell(cell) && wire_types[cell_module_wire].is_buffered()) {
+ buffered_inputs = true;
+ f << ".next";
+ }
+ f << " = ";
dump_sigspec_rhs(conn.second);
f << ";\n";
- if (getenv("CXXRTL_VOID_MY_WARRANTY")) {
+ if (getenv("CXXRTL_VOID_MY_WARRANTY") && conn.second.is_wire()) {
// Until we have proper clock tree detection, this really awful hack that opportunistically
// propagates prev_* values for clocks can be used to estimate how much faster a design could
// be if only one clock edge was simulated by replacing:
@@ -1240,19 +1348,11 @@ struct CxxrtlWorker {
// with:
// top.prev_p_clk = value<1>{0u}; top.p_clk = value<1>{1u}; top.step();
// Don't rely on this; it will be removed without warning.
- RTLIL::Module *cell_module = cell->module->design->module(cell->type);
- if (cell_module != nullptr && cell_module->wire(conn.first) && conn.second.is_wire()) {
- RTLIL::Wire *cell_module_wire = cell_module->wire(conn.first);
- if (edge_wires[conn.second.as_wire()] && edge_wires[cell_module_wire]) {
- f << indent << mangle(cell) << access << "prev_" << mangle(cell_module_wire) << " = ";
- f << "prev_" << mangle(conn.second.as_wire()) << ";\n";
- }
+ if (edge_wires[conn.second.as_wire()] && edge_wires[cell_module_wire]) {
+ f << indent << mangle(cell) << access << "prev_" << mangle(cell_module_wire) << " = ";
+ f << "prev_" << mangle(conn.second.as_wire()) << ";\n";
}
}
- } else if (cell->input(conn.first)) {
- f << indent << mangle(cell) << access << mangle_wire_name(conn.first) << ".next = ";
- dump_sigspec_rhs(conn.second);
- f << ";\n";
}
auto assign_from_outputs = [&](bool cell_converged) {
for (auto conn : cell->connections()) {
@@ -1270,9 +1370,9 @@ struct CxxrtlWorker {
// have any buffered wires if they were not output ports. Imagine inlining the cell's eval() function,
// and consider the fate of the localized wires that used to be output ports.)
//
- // Unlike cell inputs (which are never buffered), it is not possible to know apriori whether the cell
- // (which may be late bound) will converge immediately. Because of this, the choice between using .curr
- // (appropriate for buffered outputs) and .next (appropriate for unbuffered outputs) is made at runtime.
+ // It is not possible to know apriori whether the cell (which may be late bound) will converge immediately.
+ // Because of this, the choice between using .curr (appropriate for buffered outputs) and .next (appropriate
+ // for unbuffered outputs) is made at runtime.
if (cell_converged && is_cxxrtl_comb_port(cell, conn.first))
f << ".next;\n";
else
@@ -1280,43 +1380,58 @@ struct CxxrtlWorker {
}
}
};
- f << indent << "if (" << mangle(cell) << access << "eval()) {\n";
- inc_indent();
- assign_from_outputs(/*cell_converged=*/true);
- dec_indent();
- f << indent << "} else {\n";
- inc_indent();
+ if (buffered_inputs) {
+ // If we have any buffered inputs, there's no chance of converging immediately.
+ f << indent << mangle(cell) << access << "eval();\n";
f << indent << "converged = false;\n";
assign_from_outputs(/*cell_converged=*/false);
- dec_indent();
- f << indent << "}\n";
+ } else {
+ f << indent << "if (" << mangle(cell) << access << "eval()) {\n";
+ inc_indent();
+ assign_from_outputs(/*cell_converged=*/true);
+ dec_indent();
+ f << indent << "} else {\n";
+ inc_indent();
+ f << indent << "converged = false;\n";
+ assign_from_outputs(/*cell_converged=*/false);
+ dec_indent();
+ f << indent << "}\n";
+ }
}
}
- void dump_assign(const RTLIL::SigSig &sigsig)
+ void collect_cell_eval(const RTLIL::Cell *cell, bool for_debug, std::vector<const RTLIL::Cell*> &cells)
+ {
+ cells.push_back(cell);
+ for (auto port : cell->connections())
+ if (cell->input(port.first))
+ collect_sigspec_rhs(port.second, for_debug, cells);
+ }
+
+ void dump_assign(const RTLIL::SigSig &sigsig, bool for_debug = false)
{
f << indent;
- dump_sigspec_lhs(sigsig.first);
+ dump_sigspec_lhs(sigsig.first, for_debug);
f << " = ";
- dump_sigspec_rhs(sigsig.second);
+ dump_sigspec_rhs(sigsig.second, for_debug);
f << ";\n";
}
- void dump_case_rule(const RTLIL::CaseRule *rule)
+ void dump_case_rule(const RTLIL::CaseRule *rule, bool for_debug = false)
{
for (auto action : rule->actions)
- dump_assign(action);
+ dump_assign(action, for_debug);
for (auto switch_ : rule->switches)
- dump_switch_rule(switch_);
+ dump_switch_rule(switch_, for_debug);
}
- void dump_switch_rule(const RTLIL::SwitchRule *rule)
+ void dump_switch_rule(const RTLIL::SwitchRule *rule, bool for_debug = false)
{
// The switch attributes are printed before the switch condition is captured.
dump_attrs(rule);
std::string signal_temp = fresh_temporary();
f << indent << "const value<" << rule->signal.size() << "> &" << signal_temp << " = ";
- dump_sigspec(rule->signal, /*is_lhs=*/false);
+ dump_sigspec(rule->signal, /*is_lhs=*/false, for_debug);
f << ";\n";
bool first = true;
@@ -1336,7 +1451,7 @@ struct CxxrtlWorker {
first = false;
if (compare.is_fully_def()) {
f << signal_temp << " == ";
- dump_sigspec(compare, /*is_lhs=*/false);
+ dump_sigspec(compare, /*is_lhs=*/false, for_debug);
} else if (compare.is_fully_const()) {
RTLIL::Const compare_mask, compare_value;
for (auto bit : compare.as_const()) {
@@ -1370,24 +1485,34 @@ struct CxxrtlWorker {
}
f << "{\n";
inc_indent();
- dump_case_rule(case_);
+ dump_case_rule(case_, for_debug);
dec_indent();
}
f << indent << "}\n";
}
- void dump_process(const RTLIL::Process *proc)
+ void dump_process_case(const RTLIL::Process *proc, bool for_debug = false)
{
dump_attrs(proc);
- f << indent << "// process " << proc->name.str() << "\n";
+ f << indent << "// process " << proc->name.str() << " case\n";
// The case attributes (for root case) are always empty.
log_assert(proc->root_case.attributes.empty());
- dump_case_rule(&proc->root_case);
+ dump_case_rule(&proc->root_case, for_debug);
+ }
+
+ void dump_process_syncs(const RTLIL::Process *proc, bool for_debug = false)
+ {
+ dump_attrs(proc);
+ f << indent << "// process " << proc->name.str() << " syncs\n";
for (auto sync : proc->syncs) {
+ log_assert(!for_debug || sync->type == RTLIL::STa);
+
RTLIL::SigBit sync_bit;
if (!sync->signal.empty()) {
sync_bit = sync->signal[0];
sync_bit = sigmaps[sync_bit.wire->module](sync_bit);
+ if (!sync_bit.is_wire())
+ continue; // a clock, or more commonly a reset, can be tied to a constant driver
}
pool<std::string> events;
@@ -1427,129 +1552,364 @@ struct CxxrtlWorker {
}
f << ") {\n";
inc_indent();
- for (auto action : sync->actions)
- dump_assign(action);
+ for (auto &action : sync->actions)
+ dump_assign(action, for_debug);
+ for (auto &memwr : sync->mem_write_actions) {
+ RTLIL::Memory *memory = proc->module->memories.at(memwr.memid);
+ std::string valid_index_temp = fresh_temporary();
+ f << indent << "auto " << valid_index_temp << " = memory_index(";
+ dump_sigspec_rhs(memwr.address);
+ f << ", " << memory->start_offset << ", " << memory->size << ");\n";
+ // See below for rationale of having both the assert and the condition.
+ //
+ // If assertions are disabled, out of bounds writes are defined to do nothing.
+ f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds write\");\n";
+ f << indent << "if (" << valid_index_temp << ".valid) {\n";
+ inc_indent();
+ f << indent << mangle(memory) << ".update(" << valid_index_temp << ".index, ";
+ dump_sigspec_rhs(memwr.data);
+ f << ", ";
+ dump_sigspec_rhs(memwr.enable);
+ f << ");\n";
+ dec_indent();
+ f << indent << "}\n";
+ }
dec_indent();
f << indent << "}\n";
}
}
}
- void dump_wire(const RTLIL::Wire *wire, bool is_local_context)
+ void dump_mem_rdport(const Mem *mem, int portidx, bool for_debug = false)
{
- if (elided_wires.count(wire))
- return;
-
- if (localized_wires[wire] && is_local_context) {
- dump_attrs(wire);
- f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n";
+ auto &port = mem->rd_ports[portidx];
+ dump_attrs(&port);
+ f << indent << "// memory " << mem->memid.str() << " read port " << portidx << "\n";
+ if (port.clk_enable) {
+ log_assert(!for_debug);
+ RTLIL::SigBit clk_bit = port.clk[0];
+ clk_bit = sigmaps[clk_bit.wire->module](clk_bit);
+ if (clk_bit.wire) {
+ f << indent << "if (" << (port.clk_polarity ? "posedge_" : "negedge_")
+ << mangle(clk_bit) << ") {\n";
+ } else {
+ f << indent << "if (false) {\n";
+ }
+ inc_indent();
}
- if (!localized_wires[wire] && !is_local_context) {
- std::string width;
- if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) {
- width = wire->get_string_attribute(ID(cxxrtl_width));
+ std::vector<const RTLIL::Cell*> inlined_cells_addr;
+ collect_sigspec_rhs(port.addr, for_debug, inlined_cells_addr);
+ if (!inlined_cells_addr.empty())
+ dump_inlined_cells(inlined_cells_addr);
+ std::string valid_index_temp = fresh_temporary();
+ f << indent << "auto " << valid_index_temp << " = memory_index(";
+ // Almost all non-elidable cells cannot appear in debug_eval(), but $memrd is an exception; asynchronous
+ // memory read ports can.
+ dump_sigspec_rhs(port.addr, for_debug);
+ f << ", " << mem->start_offset << ", " << mem->size << ");\n";
+ bool has_enable = port.clk_enable && !port.en.is_fully_ones();
+ if (has_enable) {
+ std::vector<const RTLIL::Cell*> inlined_cells_en;
+ collect_sigspec_rhs(port.en, for_debug, inlined_cells_en);
+ if (!inlined_cells_en.empty())
+ dump_inlined_cells(inlined_cells_en);
+ f << indent << "if (";
+ dump_sigspec_rhs(port.en);
+ f << ") {\n";
+ inc_indent();
+ }
+ // The generated code has two bounds checks; one in an assertion, and another that guards the read.
+ // This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless
+ // loudly crashes if an illegal condition is encountered. The assert may be turned off with -DCXXRTL_NDEBUG
+ // not only for release builds, but also to make sure the simulator (which is presumably embedded in some
+ // larger program) will never crash the code that calls into it.
+ //
+ // If assertions are disabled, out of bounds reads are defined to return zero.
+ f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds read\");\n";
+ f << indent << "if(" << valid_index_temp << ".valid) {\n";
+ inc_indent();
+ if (!mem->wr_ports.empty()) {
+ std::string lhs_temp = fresh_temporary();
+ f << indent << "value<" << mem->width << "> " << lhs_temp << " = "
+ << mangle(mem) << "[" << valid_index_temp << ".index];\n";
+ bool transparent = false;
+ for (auto bit : port.transparency_mask)
+ if (bit)
+ transparent = true;
+ if (transparent) {
+ std::string addr_temp = fresh_temporary();
+ f << indent << "const value<" << port.addr.size() << "> &" << addr_temp << " = ";
+ dump_sigspec_rhs(port.addr);
+ f << ";\n";
+ for (int i = 0; i < GetSize(mem->wr_ports); i++) {
+ auto &wrport = mem->wr_ports[i];
+ if (!port.transparency_mask[i])
+ continue;
+ f << indent << "if (" << addr_temp << " == ";
+ dump_sigspec_rhs(wrport.addr);
+ f << ") {\n";
+ inc_indent();
+ f << indent << lhs_temp << " = " << lhs_temp;
+ f << ".update(";
+ dump_sigspec_rhs(wrport.data);
+ f << ", ";
+ dump_sigspec_rhs(wrport.en);
+ f << ");\n";
+ dec_indent();
+ f << indent << "}\n";
+ }
+ }
+ f << indent;
+ dump_sigspec_lhs(port.data);
+ f << " = " << lhs_temp << ";\n";
} else {
- width = std::to_string(wire->width);
+ f << indent;
+ dump_sigspec_lhs(port.data);
+ f << " = " << mangle(mem) << "[" << valid_index_temp << ".index];\n";
}
-
- dump_attrs(wire);
+ dec_indent();
+ f << indent << "} else {\n";
+ inc_indent();
f << indent;
- if (wire->port_input && wire->port_output)
- f << "/*inout*/ ";
- else if (wire->port_input)
- f << "/*input*/ ";
- else if (wire->port_output)
- f << "/*output*/ ";
- f << (unbuffered_wires[wire] ? "value" : "wire") << "<" << width << "> " << mangle(wire);
- if (wire->has_attribute(ID::init)) {
- f << " ";
- dump_const_init(wire->attributes.at(ID::init));
+ dump_sigspec_lhs(port.data);
+ f << " = value<" << mem->width << "> {};\n";
+ dec_indent();
+ f << indent << "}\n";
+ if (has_enable && !port.ce_over_srst) {
+ dec_indent();
+ f << indent << "}\n";
+ }
+ if (port.srst != State::S0) {
+ // Synchronous reset
+ std::vector<const RTLIL::Cell*> inlined_cells_srst;
+ collect_sigspec_rhs(port.srst, for_debug, inlined_cells_srst);
+ if (!inlined_cells_srst.empty())
+ dump_inlined_cells(inlined_cells_srst);
+ f << indent << "if (";
+ dump_sigspec_rhs(port.srst);
+ f << " == value<1> {1u}) {\n";
+ inc_indent();
+ f << indent;
+ dump_sigspec_lhs(port.data);
+ f << " = ";
+ dump_const(port.srst_value);
+ f << ";\n";
+ dec_indent();
+ f << indent << "}\n";
+ }
+ if (has_enable && port.ce_over_srst) {
+ dec_indent();
+ f << indent << "}\n";
+ }
+ if (port.clk_enable) {
+ dec_indent();
+ f << indent << "}\n";
+ }
+ if (port.arst != State::S0) {
+ // Asynchronous reset
+ std::vector<const RTLIL::Cell*> inlined_cells_arst;
+ collect_sigspec_rhs(port.arst, for_debug, inlined_cells_arst);
+ if (!inlined_cells_arst.empty())
+ dump_inlined_cells(inlined_cells_arst);
+ f << indent << "if (";
+ dump_sigspec_rhs(port.arst);
+ f << " == value<1> {1u}) {\n";
+ inc_indent();
+ f << indent;
+ dump_sigspec_lhs(port.data);
+ f << " = ";
+ dump_const(port.arst_value);
+ f << ";\n";
+ dec_indent();
+ f << indent << "}\n";
+ }
+ }
+
+ void dump_mem_wrports(const Mem *mem, bool for_debug = false)
+ {
+ log_assert(!for_debug);
+ for (int portidx = 0; portidx < GetSize(mem->wr_ports); portidx++) {
+ auto &port = mem->wr_ports[portidx];
+ dump_attrs(&port);
+ f << indent << "// memory " << mem->memid.str() << " write port " << portidx << "\n";
+ if (port.clk_enable) {
+ RTLIL::SigBit clk_bit = port.clk[0];
+ clk_bit = sigmaps[clk_bit.wire->module](clk_bit);
+ if (clk_bit.wire) {
+ f << indent << "if (" << (port.clk_polarity ? "posedge_" : "negedge_")
+ << mangle(clk_bit) << ") {\n";
+ } else {
+ f << indent << "if (false) {\n";
+ }
+ inc_indent();
}
- f << ";\n";
- if (edge_wires[wire]) {
- if (unbuffered_wires[wire]) {
- f << indent << "value<" << width << "> prev_" << mangle(wire);
- if (wire->has_attribute(ID::init)) {
- f << " ";
- dump_const_init(wire->attributes.at(ID::init));
+ std::vector<const RTLIL::Cell*> inlined_cells_addr;
+ collect_sigspec_rhs(port.addr, for_debug, inlined_cells_addr);
+ if (!inlined_cells_addr.empty())
+ dump_inlined_cells(inlined_cells_addr);
+ std::string valid_index_temp = fresh_temporary();
+ f << indent << "auto " << valid_index_temp << " = memory_index(";
+ dump_sigspec_rhs(port.addr);
+ f << ", " << mem->start_offset << ", " << mem->size << ");\n";
+ // See above for rationale of having both the assert and the condition.
+ //
+ // If assertions are disabled, out of bounds writes are defined to do nothing.
+ f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds write\");\n";
+ f << indent << "if (" << valid_index_temp << ".valid) {\n";
+ inc_indent();
+ std::vector<const RTLIL::Cell*> inlined_cells;
+ collect_sigspec_rhs(port.data, for_debug, inlined_cells);
+ collect_sigspec_rhs(port.en, for_debug, inlined_cells);
+ if (!inlined_cells.empty())
+ dump_inlined_cells(inlined_cells);
+ f << indent << mangle(mem) << ".update(" << valid_index_temp << ".index, ";
+ dump_sigspec_rhs(port.data);
+ f << ", ";
+ dump_sigspec_rhs(port.en);
+ f << ", " << portidx << ");\n";
+ dec_indent();
+ f << indent << "}\n";
+ if (port.clk_enable) {
+ dec_indent();
+ f << indent << "}\n";
+ }
+ }
+ }
+
+ void dump_wire(const RTLIL::Wire *wire, bool is_local)
+ {
+ const auto &wire_type = wire_types[wire];
+ if (!wire_type.is_named() || wire_type.is_local() != is_local)
+ return;
+
+ dump_attrs(wire);
+ f << indent;
+ if (wire->port_input && wire->port_output)
+ f << "/*inout*/ ";
+ else if (wire->port_input)
+ f << "/*input*/ ";
+ else if (wire->port_output)
+ f << "/*output*/ ";
+ f << (wire_type.is_buffered() ? "wire" : "value");
+ if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) {
+ f << "<" << wire->get_string_attribute(ID(cxxrtl_width)) << ">";
+ } else {
+ f << "<" << wire->width << ">";
+ }
+ f << " " << mangle(wire) << ";\n";
+ if (edge_wires[wire]) {
+ if (!wire_type.is_buffered()) {
+ f << indent << "value<" << wire->width << "> prev_" << mangle(wire) << ";\n";
+ }
+ for (auto edge_type : edge_types) {
+ if (edge_type.first.wire == wire) {
+ std::string prev, next;
+ if (!wire_type.is_buffered()) {
+ prev = "prev_" + mangle(edge_type.first.wire);
+ next = mangle(edge_type.first.wire);
+ } else {
+ prev = mangle(edge_type.first.wire) + ".curr";
+ next = mangle(edge_type.first.wire) + ".next";
}
- f << ";\n";
- }
- for (auto edge_type : edge_types) {
- if (edge_type.first.wire == wire) {
- std::string prev, next;
- if (unbuffered_wires[wire]) {
- prev = "prev_" + mangle(edge_type.first.wire);
- next = mangle(edge_type.first.wire);
- } else {
- prev = mangle(edge_type.first.wire) + ".curr";
- next = mangle(edge_type.first.wire) + ".next";
- }
- prev += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()";
- next += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()";
- if (edge_type.second != RTLIL::STn) {
- f << indent << "bool posedge_" << mangle(edge_type.first) << "() const {\n";
- inc_indent();
- f << indent << "return !" << prev << " && " << next << ";\n";
- dec_indent();
- f << indent << "}\n";
- }
- if (edge_type.second != RTLIL::STp) {
- f << indent << "bool negedge_" << mangle(edge_type.first) << "() const {\n";
- inc_indent();
- f << indent << "return " << prev << " && !" << next << ";\n";
- dec_indent();
- f << indent << "}\n";
- }
+ prev += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()";
+ next += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()";
+ if (edge_type.second != RTLIL::STn) {
+ f << indent << "bool posedge_" << mangle(edge_type.first) << "() const {\n";
+ inc_indent();
+ f << indent << "return !" << prev << " && " << next << ";\n";
+ dec_indent();
+ f << indent << "}\n";
+ }
+ if (edge_type.second != RTLIL::STp) {
+ f << indent << "bool negedge_" << mangle(edge_type.first) << "() const {\n";
+ inc_indent();
+ f << indent << "return " << prev << " && !" << next << ";\n";
+ dec_indent();
+ f << indent << "}\n";
}
}
}
}
}
- void dump_memory(RTLIL::Module *module, const RTLIL::Memory *memory)
+ void dump_debug_wire(const RTLIL::Wire *wire, bool is_local)
{
- vector<const RTLIL::Cell*> init_cells;
- for (auto cell : module->cells())
- if (cell->type == ID($meminit) && cell->getParam(ID::MEMID).decode_string() == memory->name.str())
- init_cells.push_back(cell);
+ const auto &wire_type = wire_types[wire];
+ if (wire_type.is_member())
+ return;
- std::sort(init_cells.begin(), init_cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) {
- int a_addr = a->getPort(ID::ADDR).as_int(), b_addr = b->getPort(ID::ADDR).as_int();
- int a_prio = a->getParam(ID::PRIORITY).as_int(), b_prio = b->getParam(ID::PRIORITY).as_int();
- return a_prio > b_prio || (a_prio == b_prio && a_addr < b_addr);
- });
+ const auto &debug_wire_type = debug_wire_types[wire];
+ if (!debug_wire_type.is_named() || debug_wire_type.is_local() != is_local)
+ return;
- dump_attrs(memory);
- f << indent << "memory<" << memory->width << "> " << mangle(memory)
- << " { " << memory->size << "u";
- if (init_cells.empty()) {
- f << " };\n";
- } else {
- f << ",\n";
- inc_indent();
- for (auto cell : init_cells) {
- dump_attrs(cell);
- RTLIL::Const data = cell->getPort(ID::DATA).as_const();
- size_t width = cell->getParam(ID::WIDTH).as_int();
- size_t words = cell->getParam(ID::WORDS).as_int();
- f << indent << "memory<" << memory->width << ">::init<" << words << "> { "
- << stringf("%#x", cell->getPort(ID::ADDR).as_int()) << ", {";
+ dump_attrs(wire);
+ f << indent;
+ if (debug_wire_type.is_outline())
+ f << "/*outline*/ ";
+ f << "value<" << wire->width << "> " << mangle(wire) << ";\n";
+ }
+
+ void dump_reset_method(RTLIL::Module *module)
+ {
+ int mem_init_idx = 0;
+ inc_indent();
+ for (auto wire : module->wires()) {
+ const auto &wire_type = wire_types[wire];
+ if (!wire_type.is_named() || wire_type.is_local()) continue;
+ if (!wire_init.count(wire)) continue;
+
+ f << indent << mangle(wire) << " = ";
+ if (wire_types[wire].is_buffered()) {
+ f << "wire<" << wire->width << ">";
+ } else {
+ f << "value<" << wire->width << ">";
+ }
+ dump_const_init(wire_init.at(wire), wire->width);
+ f << ";\n";
+
+ if (edge_wires[wire] && !wire_types[wire].is_buffered()) {
+ f << indent << "prev_" << mangle(wire) << " = ";
+ dump_const(wire_init.at(wire), wire->width);
+ f << ";\n";
+ }
+ }
+ for (auto &mem : mod_memories[module]) {
+ for (auto &init : mem.inits) {
+ if (init.removed)
+ continue;
+ dump_attrs(&init);
+ int words = GetSize(init.data) / mem.width;
+ f << indent << "static const value<" << mem.width << "> ";
+ f << "mem_init_" << ++mem_init_idx << "[" << words << "] {";
inc_indent();
- for (size_t n = 0; n < words; n++) {
+ for (int n = 0; n < words; n++) {
if (n % 4 == 0)
f << "\n" << indent;
else
f << " ";
- dump_const(data, width, n * width, /*fixed_width=*/true);
+ dump_const(init.data, mem.width, n * mem.width, /*fixed_width=*/true);
f << ",";
}
dec_indent();
- f << "\n" << indent << "}},\n";
+ f << "\n";
+ f << indent << "};\n";
+ f << indent << "std::copy(std::begin(mem_init_" << mem_init_idx << "), ";
+ f << "std::end(mem_init_" << mem_init_idx << "), ";
+ f << "&" << mangle(&mem) << ".data[" << stringf("%#x", init.addr.as_int()) << "]);\n";
}
- dec_indent();
- f << indent << "};\n";
- }
+ }
+ for (auto cell : module->cells()) {
+ if (is_internal_cell(cell->type))
+ continue;
+ f << indent << mangle(cell);
+ RTLIL::Module *cell_module = module->design->module(cell->type);
+ if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) {
+ f << "->reset();\n";
+ } else {
+ f << ".reset();\n";
+ }
+ }
+ dec_indent();
}
void dump_eval_method(RTLIL::Module *module)
@@ -1574,7 +1934,7 @@ struct CxxrtlWorker {
}
}
for (auto wire : module->wires())
- dump_wire(wire, /*is_local_context=*/true);
+ dump_wire(wire, /*is_local=*/true);
for (auto node : schedule[module]) {
switch (node.type) {
case FlowGraph::Node::Type::CONNECT:
@@ -1586,8 +1946,17 @@ struct CxxrtlWorker {
case FlowGraph::Node::Type::CELL_EVAL:
dump_cell_eval(node.cell);
break;
- case FlowGraph::Node::Type::PROCESS:
- dump_process(node.process);
+ case FlowGraph::Node::Type::PROCESS_CASE:
+ dump_process_case(node.process);
+ break;
+ case FlowGraph::Node::Type::PROCESS_SYNC:
+ dump_process_syncs(node.process);
+ break;
+ case FlowGraph::Node::Type::MEM_RDPORT:
+ dump_mem_rdport(node.mem, node.portidx);
+ break;
+ case FlowGraph::Node::Type::MEM_WRPORTS:
+ dump_mem_wrports(node.mem);
break;
}
}
@@ -1596,32 +1965,63 @@ struct CxxrtlWorker {
dec_indent();
}
+ void dump_debug_eval_method(RTLIL::Module *module)
+ {
+ inc_indent();
+ for (auto wire : module->wires())
+ dump_debug_wire(wire, /*is_local=*/true);
+ for (auto node : debug_schedule[module]) {
+ switch (node.type) {
+ case FlowGraph::Node::Type::CONNECT:
+ dump_connect(node.connect, /*for_debug=*/true);
+ break;
+ case FlowGraph::Node::Type::CELL_SYNC:
+ dump_cell_sync(node.cell, /*for_debug=*/true);
+ break;
+ case FlowGraph::Node::Type::CELL_EVAL:
+ dump_cell_eval(node.cell, /*for_debug=*/true);
+ break;
+ case FlowGraph::Node::Type::PROCESS_CASE:
+ dump_process_case(node.process, /*for_debug=*/true);
+ break;
+ case FlowGraph::Node::Type::PROCESS_SYNC:
+ dump_process_syncs(node.process, /*for_debug=*/true);
+ break;
+ case FlowGraph::Node::Type::MEM_RDPORT:
+ dump_mem_rdport(node.mem, node.portidx, /*for_debug=*/true);
+ break;
+ case FlowGraph::Node::Type::MEM_WRPORTS:
+ dump_mem_wrports(node.mem, /*for_debug=*/true);
+ break;
+ default:
+ log_abort();
+ }
+ }
+ dec_indent();
+ }
+
void dump_commit_method(RTLIL::Module *module)
{
inc_indent();
f << indent << "bool changed = false;\n";
for (auto wire : module->wires()) {
- if (elided_wires.count(wire))
- continue;
- if (unbuffered_wires[wire]) {
- if (edge_wires[wire])
- f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n";
- continue;
- }
- if (!module->get_bool_attribute(ID(cxxrtl_blackbox)) || wire->port_id != 0)
- f << indent << "changed |= " << mangle(wire) << ".commit();\n";
+ const auto &wire_type = wire_types[wire];
+ if (wire_type.type == WireType::MEMBER && edge_wires[wire])
+ f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n";
+ if (wire_type.is_buffered())
+ f << indent << "if (" << mangle(wire) << ".commit()) changed = true;\n";
}
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
- for (auto memory : module->memories) {
- if (!writable_memories[memory.second])
+ for (auto &mem : mod_memories[module]) {
+ if (!writable_memories.count({module, mem.memid}))
continue;
- f << indent << "changed |= " << mangle(memory.second) << ".commit();\n";
+ f << indent << "if (" << mangle(&mem) << ".commit()) changed = true;\n";
}
for (auto cell : module->cells()) {
if (is_internal_cell(cell->type))
continue;
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
- f << indent << "changed |= " << mangle(cell) << access << "commit();\n";
+ f << indent << "if (" << mangle(cell) << access << "commit()) changed = true;\n";
}
}
f << indent << "return changed;\n";
@@ -1631,50 +2031,138 @@ struct CxxrtlWorker {
void dump_debug_info_method(RTLIL::Module *module)
{
size_t count_public_wires = 0;
- size_t count_const_wires = 0;
- size_t count_alias_wires = 0;
size_t count_member_wires = 0;
+ size_t count_undriven = 0;
+ size_t count_driven_sync = 0;
+ size_t count_driven_comb = 0;
+ size_t count_mixed_driver = 0;
+ size_t count_alias_wires = 0;
+ size_t count_const_wires = 0;
+ size_t count_inline_wires = 0;
size_t count_skipped_wires = 0;
inc_indent();
f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
for (auto wire : module->wires()) {
- if (wire->name[0] != '\\')
- continue;
- if (module->get_bool_attribute(ID(cxxrtl_blackbox)) && (wire->port_id == 0))
+ const auto &debug_wire_type = debug_wire_types[wire];
+ if (!wire->name.isPublic())
continue;
count_public_wires++;
- if (debug_const_wires.count(wire)) {
- // Wire tied to a constant
- f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
- dump_const(debug_const_wires[wire]);
- f << ";\n";
- f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
- f << ", debug_item(const_" << mangle(wire) << ", ";
- f << wire->start_offset << "));\n";
- count_const_wires++;
- } else if (debug_alias_wires.count(wire)) {
- // Alias of a member wire
- f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
- f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", ";
- f << wire->start_offset << "));\n";
- count_alias_wires++;
- } else if (!localized_wires.count(wire)) {
- // Member wire
- f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
- f << ", debug_item(" << mangle(wire) << ", ";
- f << wire->start_offset << "));\n";
- count_member_wires++;
- } else {
- count_skipped_wires++;
+ switch (debug_wire_type.type) {
+ case WireType::BUFFERED:
+ case WireType::MEMBER: {
+ // Member wire
+ std::vector<std::string> flags;
+
+ if (wire->port_input && wire->port_output)
+ flags.push_back("INOUT");
+ else if (wire->port_output)
+ flags.push_back("OUTPUT");
+ else if (wire->port_input)
+ flags.push_back("INPUT");
+
+ bool has_driven_sync = false;
+ bool has_driven_comb = false;
+ bool has_undriven = false;
+ if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
+ for (auto bit : SigSpec(wire))
+ if (!bit_has_state.count(bit))
+ has_undriven = true;
+ else if (bit_has_state[bit])
+ has_driven_sync = true;
+ else
+ has_driven_comb = true;
+ } else if (wire->port_output) {
+ switch (cxxrtl_port_type(module, wire->name)) {
+ case CxxrtlPortType::SYNC:
+ has_driven_sync = true;
+ break;
+ case CxxrtlPortType::COMB:
+ has_driven_comb = true;
+ break;
+ case CxxrtlPortType::UNKNOWN:
+ has_driven_sync = has_driven_comb = true;
+ break;
+ }
+ } else {
+ has_undriven = true;
+ }
+ if (has_undriven)
+ flags.push_back("UNDRIVEN");
+ if (!has_driven_sync && !has_driven_comb && has_undriven)
+ count_undriven++;
+ if (has_driven_sync)
+ flags.push_back("DRIVEN_SYNC");
+ if (has_driven_sync && !has_driven_comb && !has_undriven)
+ count_driven_sync++;
+ if (has_driven_comb)
+ flags.push_back("DRIVEN_COMB");
+ if (!has_driven_sync && has_driven_comb && !has_undriven)
+ count_driven_comb++;
+ if (has_driven_sync + has_driven_comb + has_undriven > 1)
+ count_mixed_driver++;
+
+ f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
+ f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset;
+ bool first = true;
+ for (auto flag : flags) {
+ if (first) {
+ first = false;
+ f << ", ";
+ } else {
+ f << "|";
+ }
+ f << "debug_item::" << flag;
+ }
+ f << "));\n";
+ count_member_wires++;
+ break;
+ }
+ case WireType::ALIAS: {
+ // Alias of a member wire
+ const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire();
+ f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
+ f << ", debug_item(";
+ // If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream
+ // tooling has no way to find out about the outline.
+ if (debug_wire_types[aliasee].is_outline())
+ f << "debug_eval_outline";
+ else
+ f << "debug_alias()";
+ f << ", " << mangle(aliasee) << ", " << wire->start_offset << "));\n";
+ count_alias_wires++;
+ break;
+ }
+ case WireType::CONST: {
+ // Wire tied to a constant
+ f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
+ dump_const(debug_wire_type.sig_subst.as_const());
+ f << ";\n";
+ f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
+ f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "));\n";
+ count_const_wires++;
+ break;
+ }
+ case WireType::OUTLINE: {
+ // Localized or inlined, but rematerializable wire
+ f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
+ f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "));\n";
+ count_inline_wires++;
+ break;
+ }
+ default: {
+ // Localized or inlined wire with no debug information
+ count_skipped_wires++;
+ break;
+ }
}
}
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
- for (auto &memory_it : module->memories) {
- if (memory_it.first[0] != '\\')
+ for (auto &mem : mod_memories[module]) {
+ if (!mem.memid.isPublic())
continue;
- f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(memory_it.second));
- f << ", debug_item(" << mangle(memory_it.second) << ", ";
- f << memory_it.second->start_offset << "));\n";
+ f << indent << "items.add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem));
+ f << ", debug_item(" << mangle(&mem) << ", ";
+ f << mem.start_offset << "));\n";
}
for (auto cell : module->cells()) {
if (is_internal_cell(cell->type))
@@ -1688,10 +2176,18 @@ struct CxxrtlWorker {
log_debug("Debug information statistics for module `%s':\n", log_id(module));
log_debug(" Public wires: %zu, of which:\n", count_public_wires);
- log_debug(" Const wires: %zu\n", count_const_wires);
- log_debug(" Alias wires: %zu\n", count_alias_wires);
- log_debug(" Member wires: %zu\n", count_member_wires);
- log_debug(" Other wires: %zu (no debug information)\n", count_skipped_wires);
+ log_debug(" Member wires: %zu, of which:\n", count_member_wires);
+ log_debug(" Undriven: %zu (incl. inputs)\n", count_undriven);
+ log_debug(" Driven sync: %zu\n", count_driven_sync);
+ log_debug(" Driven comb: %zu\n", count_driven_comb);
+ log_debug(" Mixed driver: %zu\n", count_mixed_driver);
+ if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
+ log_debug(" Inline wires: %zu\n", count_inline_wires);
+ log_debug(" Alias wires: %zu\n", count_alias_wires);
+ log_debug(" Const wires: %zu\n", count_const_wires);
+ log_debug(" Other wires: %zu%s\n", count_skipped_wires,
+ count_skipped_wires > 0 ? " (debug unavailable)" : "");
+ }
}
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map)
@@ -1731,9 +2227,13 @@ struct CxxrtlWorker {
inc_indent();
for (auto wire : module->wires()) {
if (wire->port_id != 0)
- dump_wire(wire, /*is_local_context=*/false);
+ dump_wire(wire, /*is_local=*/false);
}
f << "\n";
+ f << indent << "void reset() override {\n";
+ dump_reset_method(module);
+ f << indent << "}\n";
+ f << "\n";
f << indent << "bool eval() override {\n";
dump_eval_method(module);
f << indent << "}\n";
@@ -1777,11 +2277,14 @@ struct CxxrtlWorker {
f << indent << "struct " << mangle(module) << " : public module {\n";
inc_indent();
for (auto wire : module->wires())
- dump_wire(wire, /*is_local_context=*/false);
- f << "\n";
+ dump_wire(wire, /*is_local=*/false);
+ for (auto wire : module->wires())
+ dump_debug_wire(wire, /*is_local=*/false);
bool has_memories = false;
- for (auto memory : module->memories) {
- dump_memory(module, memory.second);
+ for (auto &mem : mod_memories[module]) {
+ dump_attrs(&mem);
+ f << indent << "memory<" << mem.width << "> " << mangle(&mem)
+ << " { " << mem.size << "u };\n";
has_memories = true;
}
if (has_memories)
@@ -1802,16 +2305,36 @@ struct CxxrtlWorker {
dump_metadata_map(cell->attributes);
f << ");\n";
} else {
- f << indent << mangle(cell_module) << " " << mangle(cell) << ";\n";
+ f << indent << mangle(cell_module) << " " << mangle(cell) << " {interior()};\n";
}
has_cells = true;
}
if (has_cells)
f << "\n";
+ f << indent << mangle(module) << "(interior) {}\n";
+ f << indent << mangle(module) << "() {\n";
+ inc_indent();
+ f << indent << "reset();\n";
+ dec_indent();
+ f << indent << "};\n";
+ f << "\n";
+ f << indent << "void reset() override;\n";
f << indent << "bool eval() override;\n";
f << indent << "bool commit() override;\n";
- if (debug_info)
+ if (debug_info) {
+ if (debug_eval) {
+ f << "\n";
+ f << indent << "void debug_eval();\n";
+ for (auto wire : module->wires())
+ if (debug_wire_types[wire].is_outline()) {
+ f << indent << "debug_outline debug_eval_outline { std::bind(&"
+ << mangle(module) << "::debug_eval, this) };\n";
+ break;
+ }
+ }
+ f << "\n";
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n";
+ }
dec_indent();
f << indent << "}; // struct " << mangle(module) << "\n";
f << "\n";
@@ -1822,6 +2345,10 @@ struct CxxrtlWorker {
{
if (module->get_bool_attribute(ID(cxxrtl_blackbox)))
return;
+ f << indent << "void " << mangle(module) << "::reset() {\n";
+ dump_reset_method(module);
+ f << indent << "}\n";
+ f << "\n";
f << indent << "bool " << mangle(module) << "::eval() {\n";
dump_eval_method(module);
f << indent << "}\n";
@@ -1831,6 +2358,13 @@ struct CxxrtlWorker {
f << indent << "}\n";
f << "\n";
if (debug_info) {
+ if (debug_eval) {
+ f << indent << "void " << mangle(module) << "::debug_eval() {\n";
+ dump_debug_eval_method(module);
+ f << indent << "}\n";
+ f << "\n";
+ }
+ f << indent << "CXXRTL_EXTREMELY_COLD\n";
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
dump_debug_info_method(module);
f << indent << "}\n";
@@ -1935,6 +2469,7 @@ struct CxxrtlWorker {
f << "} // namespace " << design_ns << "\n";
f << "\n";
if (top_module != nullptr && debug_info) {
+ f << "extern \"C\"\n";
f << "cxxrtl_toplevel " << design_ns << "_create() {\n";
inc_indent();
std::string top_type = design_ns + "::" + mangle(top_module);
@@ -1959,7 +2494,9 @@ struct CxxrtlWorker {
void register_edge_signal(SigMap &sigmap, RTLIL::SigSpec signal, RTLIL::SyncType type)
{
signal = sigmap(signal);
- log_assert(signal.is_wire() && signal.is_bit());
+ if (signal.is_fully_const())
+ return; // a clock, or more commonly a reset, can be tied to a constant driver
+ log_assert(is_valid_clock(signal));
log_assert(type == RTLIL::STp || type == RTLIL::STn || type == RTLIL::STe);
RTLIL::SigBit sigbit = signal[0];
@@ -1967,7 +2504,8 @@ struct CxxrtlWorker {
edge_types[sigbit] = type;
else if (edge_types[sigbit] != type)
edge_types[sigbit] = RTLIL::STe;
- edge_wires.insert(signal.as_wire());
+ // Cannot use as_wire because signal might not be a full wire, instead extract the wire from the sigbit
+ edge_wires.insert(sigbit.wire);
}
void analyze_design(RTLIL::Design *design)
@@ -1982,11 +2520,21 @@ struct CxxrtlWorker {
SigMap &sigmap = sigmaps[module];
sigmap.set(module);
+ std::vector<Mem> &memories = mod_memories[module];
+ memories = Mem::get_all_memories(module);
+ for (auto &mem : memories) {
+ mem.narrow();
+ mem.coalesce_inits();
+ }
+
if (module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto port : module->ports) {
RTLIL::Wire *wire = module->wire(port);
- if (wire->port_input && !wire->port_output)
- unbuffered_wires.insert(wire);
+ if (wire->port_input && !wire->port_output) {
+ wire_types[wire] = debug_wire_types[wire] = {WireType::MEMBER};
+ } else if (wire->port_input || wire->port_output) {
+ wire_types[wire] = debug_wire_types[wire] = {WireType::BUFFERED};
+ }
if (wire->has_attribute(ID(cxxrtl_edge))) {
RTLIL::Const edge_attr = wire->attributes[ID(cxxrtl_edge)];
if (!(edge_attr.flags & RTLIL::CONST_FLAG_STRING) || (int)edge_attr.decode_string().size() != GetSize(wire))
@@ -2016,18 +2564,24 @@ struct CxxrtlWorker {
continue;
}
+ for (auto wire : module->wires())
+ if (wire->has_attribute(ID::init))
+ wire_init[wire] = wire->attributes.at(ID::init);
+
+ // Construct a flow graph where each node is a basic computational operation generally corresponding
+ // to a fragment of the RTLIL netlist.
FlowGraph flow;
for (auto conn : module->connections())
flow.add_node(conn);
- dict<const RTLIL::Cell*, FlowGraph::Node*> memrw_cell_nodes;
- dict<std::pair<RTLIL::SigBit, const RTLIL::Memory*>,
- pool<const RTLIL::Cell*>> memwr_per_domain;
for (auto cell : module->cells()) {
if (!cell->known())
log_cmd_error("Unknown cell `%s'.\n", log_id(cell->type));
+ if (cell->is_mem_cell())
+ continue;
+
RTLIL::Module *cell_module = design->module(cell->type);
if (cell_module &&
cell_module->get_blackbox_attribute() &&
@@ -2039,58 +2593,54 @@ struct CxxrtlWorker {
cell_module->get_bool_attribute(ID(cxxrtl_template)))
blackbox_specializations[cell_module].insert(template_args(cell));
- FlowGraph::Node *node = flow.add_node(cell);
+ flow.add_node(cell);
// Various DFF cells are treated like posedge/negedge processes, see above for details.
- if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($adffe), ID($dffsr), ID($dffsre), ID($sdff), ID($sdffe), ID($sdffce))) {
- if (cell->getPort(ID::CLK).is_wire())
+ if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre), ID($sdff), ID($sdffe), ID($sdffce))) {
+ if (is_valid_clock(cell->getPort(ID::CLK)))
register_edge_signal(sigmap, cell->getPort(ID::CLK),
cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn);
}
- // Similar for memory port cells.
- if (cell->type.in(ID($memrd), ID($memwr))) {
- if (cell->getParam(ID::CLK_ENABLE).as_bool()) {
- if (cell->getPort(ID::CLK).is_wire())
- register_edge_signal(sigmap, cell->getPort(ID::CLK),
- cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn);
- }
- memrw_cell_nodes[cell] = node;
- }
- // Optimize access to read-only memories.
- if (cell->type == ID($memwr))
- writable_memories.insert(module->memories[cell->getParam(ID::MEMID).decode_string()]);
- // Collect groups of memory write ports in the same domain.
- if (cell->type == ID($memwr) && cell->getParam(ID::CLK_ENABLE).as_bool() && cell->getPort(ID::CLK).is_wire()) {
- RTLIL::SigBit clk_bit = sigmap(cell->getPort(ID::CLK))[0];
- const RTLIL::Memory *memory = module->memories[cell->getParam(ID::MEMID).decode_string()];
- memwr_per_domain[{clk_bit, memory}].insert(cell);
- }
- // Handling of packed memories is delegated to the `memory_unpack` pass, so we can rely on the presence
- // of RTLIL memory objects and $memrd/$memwr/$meminit cells.
- if (cell->type.in(ID($mem)))
- log_assert(false);
}
- for (auto cell : module->cells()) {
- // Collect groups of memory write ports read by every transparent read port.
- if (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool() && cell->getPort(ID::CLK).is_wire() &&
- cell->getParam(ID::TRANSPARENT).as_bool()) {
- RTLIL::SigBit clk_bit = sigmap(cell->getPort(ID::CLK))[0];
- const RTLIL::Memory *memory = module->memories[cell->getParam(ID::MEMID).decode_string()];
- for (auto memwr_cell : memwr_per_domain[{clk_bit, memory}]) {
- transparent_for[cell].insert(memwr_cell);
- // Our implementation of transparent $memrd cells reads \EN, \ADDR and \DATA from every $memwr cell
- // in the same domain, which isn't directly visible in the netlist. Add these uses explicitly.
- flow.add_uses(memrw_cell_nodes[cell], memwr_cell->getPort(ID::EN));
- flow.add_uses(memrw_cell_nodes[cell], memwr_cell->getPort(ID::ADDR));
- flow.add_uses(memrw_cell_nodes[cell], memwr_cell->getPort(ID::DATA));
+
+ for (auto &mem : memories) {
+ flow.add_node(&mem);
+
+ // Clocked memory cells are treated like posedge/negedge processes as well.
+ for (auto &port : mem.rd_ports) {
+ if (port.clk_enable)
+ if (is_valid_clock(port.clk))
+ register_edge_signal(sigmap, port.clk,
+ port.clk_polarity ? RTLIL::STp : RTLIL::STn);
+ // For read ports, also move initial value to wire_init (if any).
+ for (int i = 0; i < GetSize(port.data); i++) {
+ if (port.init_value[i] != State::Sx) {
+ SigBit bit = port.data[i];
+ if (bit.wire) {
+ auto &init = wire_init[bit.wire];
+ if (init == RTLIL::Const()) {
+ init = RTLIL::Const(State::Sx, GetSize(bit.wire));
+ }
+ init[bit.offset] = port.init_value[i];
+ }
+ }
}
}
+ for (auto &port : mem.wr_ports) {
+ if (port.clk_enable)
+ if (is_valid_clock(port.clk))
+ register_edge_signal(sigmap, port.clk,
+ port.clk_polarity ? RTLIL::STp : RTLIL::STn);
+ }
+
+ if (!mem.wr_ports.empty())
+ writable_memories.insert({module, mem.memid});
}
for (auto proc : module->processes) {
flow.add_node(proc.second);
- for (auto sync : proc.second->syncs)
+ for (auto sync : proc.second->syncs) {
switch (sync->type) {
// Edge-type sync rules require pre-registration.
case RTLIL::STp:
@@ -2113,59 +2663,46 @@ struct CxxrtlWorker {
case RTLIL::STi:
log_assert(false);
}
+ for (auto &memwr : sync->mem_write_actions) {
+ writable_memories.insert({module, memwr.memid});
+ }
+ }
}
- for (auto wire : module->wires()) {
- if (!flow.is_elidable(wire)) continue;
- if (wire->port_id != 0) continue;
- if (wire->get_bool_attribute(ID::keep)) continue;
- if (wire->name.begins_with("$") && !elide_internal) continue;
- if (wire->name.begins_with("\\") && !elide_public) continue;
- if (edge_wires[wire]) continue;
- log_assert(flow.wire_comb_defs[wire].size() == 1);
- elided_wires[wire] = **flow.wire_comb_defs[wire].begin();
- }
-
- dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_defs;
- for (auto wire_comb_def : flow.wire_comb_defs)
- for (auto node : wire_comb_def.second)
- node_defs[node].insert(wire_comb_def.first);
-
+ // Construct a linear order of the flow graph that minimizes the amount of feedback arcs. A flow graph
+ // without feedback arcs can generally be evaluated in a single pass, i.e. it always requires only
+ // a single delta cycle.
Scheduler<FlowGraph::Node> scheduler;
- dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map;
+ dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_vertex_map;
for (auto node : flow.nodes)
- node_map[node] = scheduler.add(node);
- for (auto node_def : node_defs) {
- auto vertex = node_map[node_def.first];
- for (auto wire : node_def.second)
+ node_vertex_map[node] = scheduler.add(node);
+ for (auto node_comb_def : flow.node_comb_defs) {
+ auto vertex = node_vertex_map[node_comb_def.first];
+ for (auto wire : node_comb_def.second)
for (auto succ_node : flow.wire_uses[wire]) {
- auto succ_vertex = node_map[succ_node];
+ auto succ_vertex = node_vertex_map[succ_node];
vertex->succs.insert(succ_vertex);
succ_vertex->preds.insert(vertex);
}
}
- auto eval_order = scheduler.schedule();
- pool<FlowGraph::Node*, hash_ptr_ops> evaluated;
+ // Find out whether the order includes any feedback arcs.
+ std::vector<FlowGraph::Node*> node_order;
+ pool<FlowGraph::Node*, hash_ptr_ops> evaluated_nodes;
pool<const RTLIL::Wire*> feedback_wires;
- for (auto vertex : eval_order) {
+ for (auto vertex : scheduler.schedule()) {
auto node = vertex->data;
- schedule[module].push_back(*node);
+ node_order.push_back(node);
// Any wire that is an output of node vo and input of node vi where vo is scheduled later than vi
// is a feedback wire. Feedback wires indicate apparent logic loops in the design, which may be
// caused by a true logic loop, but usually are a benign result of dependency tracking that works
- // on wire, not bit, level. Nevertheless, feedback wires cannot be localized.
- evaluated.insert(node);
- for (auto wire : node_defs[node])
+ // on wire, not bit, level. Nevertheless, feedback wires cannot be unbuffered.
+ evaluated_nodes.insert(node);
+ for (auto wire : flow.node_comb_defs[node])
for (auto succ_node : flow.wire_uses[wire])
- if (evaluated[succ_node]) {
+ if (evaluated_nodes[succ_node])
feedback_wires.insert(wire);
- // Feedback wires may never be elided because feedback requires state, but the point of elision
- // (and localization) is to eliminate state.
- elided_wires.erase(wire);
- }
}
-
if (!feedback_wires.empty()) {
has_feedback_arcs = true;
log("Module `%s' contains feedback arcs through wires:\n", log_id(module));
@@ -2173,21 +2710,92 @@ struct CxxrtlWorker {
log(" %s\n", log_id(wire));
}
+ // Conservatively assign wire types. Assignment of types BUFFERED and MEMBER is final, but assignment
+ // of type LOCAL may be further refined to UNUSED or INLINE.
for (auto wire : module->wires()) {
+ auto &wire_type = wire_types[wire];
+ wire_type = {WireType::BUFFERED};
+
if (feedback_wires[wire]) continue;
if (wire->port_output && !module->get_bool_attribute(ID::top)) continue;
- if (wire->name.begins_with("$") && !unbuffer_internal) continue;
- if (wire->name.begins_with("\\") && !unbuffer_public) continue;
+ if (!wire->name.isPublic() && !unbuffer_internal) continue;
+ if (wire->name.isPublic() && !unbuffer_public) continue;
if (flow.wire_sync_defs.count(wire) > 0) continue;
- unbuffered_wires.insert(wire);
+ wire_type = {WireType::MEMBER};
+
if (edge_wires[wire]) continue;
if (wire->get_bool_attribute(ID::keep)) continue;
if (wire->port_input || wire->port_output) continue;
- if (wire->name.begins_with("$") && !localize_internal) continue;
- if (wire->name.begins_with("\\") && !localize_public) continue;
- localized_wires.insert(wire);
+ if (!wire->name.isPublic() && !localize_internal) continue;
+ if (wire->name.isPublic() && !localize_public) continue;
+ wire_type = {WireType::LOCAL};
+ }
+
+ // Discover nodes reachable from primary outputs (i.e. members) and collect reachable wire users.
+ pool<FlowGraph::Node*, hash_ptr_ops> worklist;
+ for (auto node : flow.nodes) {
+ if (node->type == FlowGraph::Node::Type::CELL_EVAL && is_effectful_cell(node->cell->type))
+ worklist.insert(node); // node has effects
+ else if (node->type == FlowGraph::Node::Type::MEM_WRPORTS)
+ worklist.insert(node); // node is memory write
+ else if (node->type == FlowGraph::Node::Type::PROCESS_SYNC && is_memwr_process(node->process))
+ worklist.insert(node); // node is memory write
+ else if (flow.node_sync_defs.count(node))
+ worklist.insert(node); // node is a flip-flop
+ else if (flow.node_comb_defs.count(node)) {
+ for (auto wire : flow.node_comb_defs[node])
+ if (wire_types[wire].is_member())
+ worklist.insert(node); // node drives public wires
+ }
+ }
+ dict<const RTLIL::Wire*, pool<FlowGraph::Node*, hash_ptr_ops>> live_wires;
+ pool<FlowGraph::Node*, hash_ptr_ops> live_nodes;
+ while (!worklist.empty()) {
+ auto node = worklist.pop();
+ live_nodes.insert(node);
+ for (auto wire : flow.node_uses[node]) {
+ live_wires[wire].insert(node);
+ for (auto pred_node : flow.wire_comb_defs[wire])
+ if (!live_nodes[pred_node])
+ worklist.insert(pred_node);
+ }
}
+ // Refine wire types taking into account the amount of uses from reachable nodes only.
+ for (auto wire : module->wires()) {
+ auto &wire_type = wire_types[wire];
+ if (!wire_type.is_local()) continue;
+ if (live_wires[wire].empty()) {
+ wire_type = {WireType::UNUSED}; // wire never used
+ continue;
+ }
+
+ if (!wire->name.isPublic() && !inline_internal) continue;
+ if (wire->name.isPublic() && !inline_public) continue;
+ if (flow.is_inlinable(wire, live_wires[wire])) {
+ if (flow.wire_comb_defs[wire].size() > 1)
+ log_cmd_error("Wire %s.%s has multiple drivers!\n", log_id(module), log_id(wire));
+ log_assert(flow.wire_comb_defs[wire].size() == 1);
+ FlowGraph::Node *node = *flow.wire_comb_defs[wire].begin();
+ switch (node->type) {
+ case FlowGraph::Node::Type::CELL_EVAL:
+ if (!is_inlinable_cell(node->cell->type)) continue;
+ wire_type = {WireType::INLINE, node->cell}; // wire replaced with cell
+ break;
+ case FlowGraph::Node::Type::CONNECT:
+ wire_type = {WireType::INLINE, node->connect.second}; // wire replaced with sig
+ break;
+ default: continue;
+ }
+ live_nodes.erase(node);
+ }
+ }
+
+ // Emit reachable nodes in eval().
+ for (auto node : node_order)
+ if (live_nodes[node])
+ schedule[module].push_back(*node);
+
// For maximum performance, the state of the simulation (which is the same as the set of its double buffered
// wires, since using a singly buffered wire for any kind of state introduces a race condition) should contain
// no wires attached to combinatorial outputs. Feedback wires, by definition, make that impossible. However,
@@ -2195,10 +2803,9 @@ struct CxxrtlWorker {
// as a wire with multiple drivers where one of them is combinatorial and the other is synchronous. Such designs
// also require more than one delta cycle to converge.
pool<const RTLIL::Wire*> buffered_comb_wires;
- for (auto wire : module->wires()) {
- if (flow.wire_comb_defs[wire].size() > 0 && !unbuffered_wires[wire] && !feedback_wires[wire])
+ for (auto wire : module->wires())
+ if (wire_types[wire].is_buffered() && !feedback_wires[wire] && flow.wire_comb_defs[wire].size() > 0)
buffered_comb_wires.insert(wire);
- }
if (!buffered_comb_wires.empty()) {
has_buffered_comb_wires = true;
log("Module `%s' contains buffered combinatorial wires:\n", log_id(module));
@@ -2206,44 +2813,154 @@ struct CxxrtlWorker {
log(" %s\n", log_id(wire));
}
+ // Record whether eval() requires only one delta cycle in this module.
eval_converges[module] = feedback_wires.empty() && buffered_comb_wires.empty();
if (debug_info) {
- // Find wires that alias other wires or are tied to a constant; debug information can be enriched with these
- // at essentially zero additional cost.
- //
- // Note that the information collected here can't be used for optimizing the netlist: debug information queries
- // are pure and run on a design in a stable state, which allows assumptions that do not otherwise hold.
+ // Annotate wire bits with the type of their driver; this is exposed in the debug metadata.
+ for (auto item : flow.bit_has_state)
+ bit_has_state.insert(item);
+
+ // Assign debug information wire types to public wires according to the chosen debug level.
+ // Unlike with optimized wire types, all assignments here are final.
for (auto wire : module->wires()) {
- if (wire->name[0] != '\\')
- continue;
- if (!unbuffered_wires[wire])
- continue;
- const RTLIL::Wire *wire_it = wire;
- while (1) {
- if (!(flow.wire_def_elidable.count(wire_it) && flow.wire_def_elidable[wire_it]))
- break; // not an alias: complex def
- log_assert(flow.wire_comb_defs[wire_it].size() == 1);
- FlowGraph::Node *node = *flow.wire_comb_defs[wire_it].begin();
- if (node->type != FlowGraph::Node::Type::CONNECT)
- break; // not an alias: def by cell
- RTLIL::SigSpec rhs_sig = node->connect.second;
- if (rhs_sig.is_wire()) {
- RTLIL::Wire *rhs_wire = rhs_sig.as_wire();
- if (unbuffered_wires[rhs_wire]) {
- wire_it = rhs_wire; // maybe an alias
- } else {
- debug_alias_wires[wire] = rhs_wire; // is an alias
+ const auto &wire_type = wire_types[wire];
+ auto &debug_wire_type = debug_wire_types[wire];
+
+ if (!debug_info) continue;
+ if (wire->port_input || wire_type.is_buffered())
+ debug_wire_type = wire_type; // wire contains state
+ else if (!wire->name.isPublic())
+ continue; // internal and stateless
+
+ if (!debug_member) continue;
+ if (wire_type.is_member())
+ debug_wire_type = wire_type; // wire is a member
+
+ if (!debug_alias) continue;
+ const RTLIL::Wire *it = wire;
+ while (flow.is_inlinable(it)) {
+ log_assert(flow.wire_comb_defs[it].size() == 1);
+ FlowGraph::Node *node = *flow.wire_comb_defs[it].begin();
+ if (node->type != FlowGraph::Node::Type::CONNECT) break; // not an alias
+ RTLIL::SigSpec rhs = node->connect.second;
+ if (rhs.is_fully_const()) {
+ debug_wire_type = {WireType::CONST, rhs}; // wire replaced with const
+ } else if (rhs.is_wire()) {
+ if (wire_types[rhs.as_wire()].is_member())
+ debug_wire_type = {WireType::ALIAS, rhs}; // wire replaced with wire
+ else if (debug_eval && rhs.as_wire()->name.isPublic())
+ debug_wire_type = {WireType::ALIAS, rhs}; // wire replaced with outline
+ it = rhs.as_wire(); // and keep looking
+ continue;
+ }
+ break;
+ }
+
+ if (!debug_eval) continue;
+ if (!debug_wire_type.is_exact() && !wire_type.is_member())
+ debug_wire_type = {WireType::OUTLINE}; // wire is local or inlined
+ }
+
+ // Discover nodes reachable from primary outputs (i.e. outlines) up until primary inputs (i.e. members)
+ // and collect reachable wire users.
+ pool<FlowGraph::Node*, hash_ptr_ops> worklist;
+ for (auto node : flow.nodes) {
+ if (flow.node_comb_defs.count(node))
+ for (auto wire : flow.node_comb_defs[node])
+ if (debug_wire_types[wire].is_outline())
+ worklist.insert(node); // node drives outline
+ }
+ dict<const RTLIL::Wire*, pool<FlowGraph::Node*, hash_ptr_ops>> debug_live_wires;
+ pool<FlowGraph::Node*, hash_ptr_ops> debug_live_nodes;
+ while (!worklist.empty()) {
+ auto node = worklist.pop();
+ debug_live_nodes.insert(node);
+ for (auto wire : flow.node_uses[node]) {
+ if (debug_wire_types[wire].is_member())
+ continue; // node uses member
+ if (debug_wire_types[wire].is_exact())
+ continue; // node uses alias or const
+ debug_live_wires[wire].insert(node);
+ for (auto pred_node : flow.wire_comb_defs[wire])
+ if (!debug_live_nodes[pred_node])
+ worklist.insert(pred_node);
+ }
+ }
+
+ // Assign debug information wire types to internal wires used by reachable nodes. This is similar
+ // to refining optimized wire types with the exception that the assignments here are first and final.
+ for (auto wire : module->wires()) {
+ const auto &wire_type = wire_types[wire];
+ auto &debug_wire_type = debug_wire_types[wire];
+ if (wire->name.isPublic()) continue;
+
+ if (debug_live_wires[wire].empty()) {
+ continue; // wire never used
+ } else if (flow.is_inlinable(wire, debug_live_wires[wire])) {
+ log_assert(flow.wire_comb_defs[wire].size() == 1);
+ FlowGraph::Node *node = *flow.wire_comb_defs[wire].begin();
+ switch (node->type) {
+ case FlowGraph::Node::Type::CELL_EVAL:
+ if (!is_inlinable_cell(node->cell->type)) continue;
+ debug_wire_type = {WireType::INLINE, node->cell}; // wire replaced with cell
+ break;
+ case FlowGraph::Node::Type::CONNECT:
+ debug_wire_type = {WireType::INLINE, node->connect.second}; // wire replaced with sig
break;
+ default: continue;
+ }
+ debug_live_nodes.erase(node);
+ } else if (wire_type.is_member() || wire_type.type == WireType::LOCAL) {
+ debug_wire_type = wire_type; // wire not inlinable
+ } else {
+ log_assert(wire_type.type == WireType::INLINE ||
+ wire_type.type == WireType::UNUSED);
+ if (flow.wire_comb_defs[wire].size() == 0) {
+ if (wire_init.count(wire)) { // wire never modified
+ debug_wire_type = {WireType::CONST, wire_init.at(wire)};
+ } else {
+ debug_wire_type = {WireType::CONST, RTLIL::SigSpec(RTLIL::S0, wire->width)};
}
- } else if (rhs_sig.is_fully_const()) {
- debug_const_wires[wire] = rhs_sig.as_const(); // is a const
- break;
} else {
- break; // not an alias: complex rhs
+ debug_wire_type = {WireType::LOCAL}; // wire used only for debug
}
}
}
+
+ // Emit reachable nodes in debug_eval().
+ for (auto node : node_order)
+ if (debug_live_nodes[node])
+ debug_schedule[module].push_back(*node);
+ }
+
+ auto show_wire_type = [&](const RTLIL::Wire* wire, const WireType &wire_type) {
+ const char *type_str;
+ switch (wire_type.type) {
+ case WireType::UNUSED: type_str = "UNUSED"; break;
+ case WireType::BUFFERED: type_str = "BUFFERED"; break;
+ case WireType::MEMBER: type_str = "MEMBER"; break;
+ case WireType::OUTLINE: type_str = "OUTLINE"; break;
+ case WireType::LOCAL: type_str = "LOCAL"; break;
+ case WireType::INLINE: type_str = "INLINE"; break;
+ case WireType::ALIAS: type_str = "ALIAS"; break;
+ case WireType::CONST: type_str = "CONST"; break;
+ default: type_str = "(invalid)";
+ }
+ if (wire_type.sig_subst.empty())
+ log_debug(" %s: %s\n", log_signal((RTLIL::Wire*)wire), type_str);
+ else
+ log_debug(" %s: %s = %s\n", log_signal((RTLIL::Wire*)wire), type_str, log_signal(wire_type.sig_subst));
+ };
+ if (print_wire_types && !wire_types.empty()) {
+ log_debug("Wire types:\n");
+ for (auto wire_type : wire_types)
+ show_wire_type(wire_type.first, wire_type.second);
+ }
+ if (print_debug_wire_types && !debug_wire_types.empty()) {
+ log_debug("Debug wire types:\n");
+ for (auto debug_wire_type : debug_wire_types)
+ show_wire_type(debug_wire_type.first, debug_wire_type.second);
}
}
if (has_feedback_arcs || has_buffered_comb_wires) {
@@ -2263,9 +2980,9 @@ struct CxxrtlWorker {
}
}
- void check_design(RTLIL::Design *design, bool &has_sync_init, bool &has_packed_mem)
+ void check_design(RTLIL::Design *design, bool &has_sync_init)
{
- has_sync_init = has_packed_mem = false;
+ has_sync_init = false;
for (auto module : design->modules()) {
if (module->get_blackbox_attribute() && !module->has_attribute(ID(cxxrtl_blackbox)))
@@ -2281,19 +2998,19 @@ struct CxxrtlWorker {
for (auto sync : proc.second->syncs)
if (sync->type == RTLIL::STi)
has_sync_init = true;
-
- for (auto cell : module->cells())
- if (cell->type == ID($mem))
- has_packed_mem = true;
}
}
void prepare_design(RTLIL::Design *design)
{
bool did_anything = false;
- bool has_sync_init, has_packed_mem;
+ bool has_sync_init;
log_push();
- check_design(design, has_sync_init, has_packed_mem);
+ check_design(design, has_sync_init);
+ if (run_hierarchy) {
+ Pass::call(design, "hierarchy -auto-top");
+ did_anything = true;
+ }
if (run_flatten) {
Pass::call(design, "flatten");
did_anything = true;
@@ -2309,14 +3026,10 @@ struct CxxrtlWorker {
Pass::call(design, "proc_init");
did_anything = true;
}
- if (has_packed_mem) {
- Pass::call(design, "memory_unpack");
- did_anything = true;
- }
// Recheck the design if it was modified.
- if (has_sync_init || has_packed_mem)
- check_design(design, has_sync_init, has_packed_mem);
- log_assert(!(has_sync_init || has_packed_mem));
+ if (did_anything)
+ check_design(design, has_sync_init);
+ log_assert(!has_sync_init);
log_pop();
if (did_anything)
log_spacer();
@@ -2326,8 +3039,7 @@ struct CxxrtlWorker {
struct CxxrtlBackend : public Backend {
static const int DEFAULT_OPT_LEVEL = 6;
- static const int OPT_LEVEL_DEBUG = 4;
- static const int DEFAULT_DEBUG_LEVEL = 1;
+ static const int DEFAULT_DEBUG_LEVEL = 4;
CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { }
void help() override
@@ -2488,6 +3200,9 @@ struct CxxrtlBackend : public Backend {
log("\n");
log("The following options are supported by this backend:\n");
log("\n");
+ log(" -print-wire-types, -print-debug-wire-types\n");
+ log(" enable additional debug logging, for pass developers.\n");
+ log("\n");
log(" -header\n");
log(" generate separate interface (.h) and implementation (.cc) files.\n");
log(" if specified, the backend must be called with a filename, and filename\n");
@@ -2498,6 +3213,11 @@ struct CxxrtlBackend : public Backend {
log(" place the generated code into namespace <ns-name>. if not specified,\n");
log(" \"cxxrtl_design\" is used.\n");
log("\n");
+ log(" -nohierarchy\n");
+ log(" use design hierarchy as-is. in most designs, a top module should be\n");
+ log(" present as it is exposed through the C API and has unbuffered outputs\n");
+ log(" for improved performance; it will be determined automatically if absent.\n");
+ log("\n");
log(" -noflatten\n");
log(" don't flatten the design. fully flattened designs can evaluate within\n");
log(" one delta cycle if they have no combinatorial feedback.\n");
@@ -2518,13 +3238,13 @@ struct CxxrtlBackend : public Backend {
log(" no optimization.\n");
log("\n");
log(" -O1\n");
- log(" localize internal wires if possible.\n");
+ log(" unbuffer internal wires if possible.\n");
log("\n");
log(" -O2\n");
- log(" like -O1, and unbuffer internal wires if possible.\n");
+ log(" like -O1, and localize internal wires if possible.\n");
log("\n");
log(" -O3\n");
- log(" like -O2, and elide internal wires if possible.\n");
+ log(" like -O2, and inline internal wires if possible.\n");
log("\n");
log(" -O4\n");
log(" like -O3, and unbuffer public wires not marked (*keep*) if possible.\n");
@@ -2533,27 +3253,38 @@ struct CxxrtlBackend : public Backend {
log(" like -O4, and localize public wires not marked (*keep*) if possible.\n");
log("\n");
log(" -O6\n");
- log(" like -O5, and elide public wires not marked (*keep*) if possible.\n");
- log("\n");
- log(" -Og\n");
- log(" highest optimization level that provides debug information for all\n");
- log(" public wires. currently, alias for -O%d.\n", OPT_LEVEL_DEBUG);
+ log(" like -O5, and inline public wires not marked (*keep*) if possible.\n");
log("\n");
log(" -g <level>\n");
log(" set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL);
log(" more visibility and generate more code, but do not pessimize evaluation.\n");
log("\n");
log(" -g0\n");
- log(" no debug information.\n");
+ log(" no debug information. the C API is disabled.\n");
log("\n");
log(" -g1\n");
- log(" debug information for non-optimized public wires. this also makes it\n");
- log(" possible to use the C API.\n");
+ log(" include bare minimum of debug information necessary to access all design\n");
+ log(" state. the C API is enabled.\n");
+ log("\n");
+ log(" -g2\n");
+ log(" like -g1, but include debug information for all public wires that are\n");
+ log(" directly accessible through the C++ interface.\n");
+ log("\n");
+ log(" -g3\n");
+ log(" like -g2, and include debug information for public wires that are tied\n");
+ log(" to a constant or another public wire.\n");
+ log("\n");
+ log(" -g4\n");
+ log(" like -g3, and compute debug information on demand for all public wires\n");
+ log(" that were optimized out.\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{
+ bool print_wire_types = false;
+ bool print_debug_wire_types = false;
+ bool nohierarchy = false;
bool noflatten = false;
bool noproc = false;
int opt_level = DEFAULT_OPT_LEVEL;
@@ -2565,6 +3296,18 @@ struct CxxrtlBackend : public Backend {
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
+ if (args[argidx] == "-print-wire-types") {
+ print_wire_types = true;
+ continue;
+ }
+ if (args[argidx] == "-print-debug-wire-types") {
+ print_debug_wire_types = true;
+ continue;
+ }
+ if (args[argidx] == "-nohierarchy") {
+ nohierarchy = true;
+ continue;
+ }
if (args[argidx] == "-noflatten") {
noflatten = true;
continue;
@@ -2574,12 +3317,14 @@ struct CxxrtlBackend : public Backend {
continue;
}
if (args[argidx] == "-Og") {
- opt_level = OPT_LEVEL_DEBUG;
+ log_warning("The `-Og` option has been removed. Use `-g3` instead for complete "
+ "design coverage regardless of optimization level.\n");
continue;
}
if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") {
argidx++;
- opt_level = OPT_LEVEL_DEBUG;
+ log_warning("The `-Og` option has been removed. Use `-g3` instead for complete "
+ "design coverage regardless of optimization level.\n");
continue;
}
if (args[argidx] == "-O" && argidx+1 < args.size()) {
@@ -2610,12 +3355,15 @@ struct CxxrtlBackend : public Backend {
}
extra_args(f, filename, args, argidx);
+ worker.print_wire_types = print_wire_types;
+ worker.print_debug_wire_types = print_debug_wire_types;
+ worker.run_hierarchy = !nohierarchy;
worker.run_flatten = !noflatten;
worker.run_proc = !noproc;
switch (opt_level) {
// the highest level here must match DEFAULT_OPT_LEVEL
case 6:
- worker.elide_public = true;
+ worker.inline_public = true;
YS_FALLTHROUGH
case 5:
worker.localize_public = true;
@@ -2624,7 +3372,7 @@ struct CxxrtlBackend : public Backend {
worker.unbuffer_public = true;
YS_FALLTHROUGH
case 3:
- worker.elide_internal = true;
+ worker.inline_internal = true;
YS_FALLTHROUGH
case 2:
worker.localize_internal = true;
@@ -2639,6 +3387,15 @@ struct CxxrtlBackend : public Backend {
}
switch (debug_level) {
// the highest level here must match DEFAULT_DEBUG_LEVEL
+ case 4:
+ worker.debug_eval = true;
+ YS_FALLTHROUGH
+ case 3:
+ worker.debug_alias = true;
+ YS_FALLTHROUGH
+ case 2:
+ worker.debug_member = true;
+ YS_FALLTHROUGH
case 1:
worker.debug_info = true;
YS_FALLTHROUGH
diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc
index e0566e152..227173ba8 100644
--- a/backends/cxxrtl/cxxrtl_capi.cc
+++ b/backends/cxxrtl/cxxrtl_capi.cc
@@ -32,9 +32,22 @@ const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle)
}
cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) {
+ return cxxrtl_create_at(design, "");
+}
+
+cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root) {
+ std::string path = root;
+ if (!path.empty()) {
+ // module::debug_info() accepts either an empty path, or a path ending in space to simplify
+ // the logic in generated code. While this is sketchy at best to expose in the C++ API, this
+ // would be a lot worse in the C API, so don't expose it here.
+ assert(path.back() != ' ');
+ path += ' ';
+ }
+
cxxrtl_handle handle = new _cxxrtl_handle;
handle->module = std::move(design->module);
- handle->module->debug_info(handle->objects);
+ handle->module->debug_info(handle->objects, path);
delete design;
return handle;
}
@@ -43,6 +56,18 @@ void cxxrtl_destroy(cxxrtl_handle handle) {
delete handle;
}
+void cxxrtl_reset(cxxrtl_handle handle) {
+ handle->module->reset();
+}
+
+int cxxrtl_eval(cxxrtl_handle handle) {
+ return handle->module->eval();
+}
+
+int cxxrtl_commit(cxxrtl_handle handle) {
+ return handle->module->commit();
+}
+
size_t cxxrtl_step(cxxrtl_handle handle) {
return handle->module->step();
}
@@ -61,3 +86,7 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,
for (auto &it : handle->objects.table)
callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size());
}
+
+void cxxrtl_outline_eval(cxxrtl_outline outline) {
+ outline->eval();
+}
diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h
index 599284898..2df2b7287 100644
--- a/backends/cxxrtl/cxxrtl_capi.h
+++ b/backends/cxxrtl/cxxrtl_capi.h
@@ -52,15 +52,45 @@ typedef struct _cxxrtl_handle *cxxrtl_handle;
// The `design` is consumed by this operation and cannot be used afterwards.
cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design);
+// Create a design handle at a given hierarchy position from a design toplevel.
+//
+// This operation is similar to `cxxrtl_create`, except the full hierarchical name of every object
+// is prepended with `root`.
+cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root);
+
// Release all resources used by a design and its handle.
void cxxrtl_destroy(cxxrtl_handle handle);
+// Reinitialize the design, replacing the internal state with the reset values while preserving
+// black boxes.
+//
+// This operation is essentially equivalent to a power-on reset. Values, wires, and memories are
+// returned to their reset state while preserving the state of black boxes and keeping all of
+// the interior pointers obtained with e.g. `cxxrtl_get` valid.
+void cxxrtl_reset(cxxrtl_handle handle);
+
+// Evaluate the design, propagating changes on inputs to the `next` value of internal state and
+// output wires.
+//
+// Returns 1 if the design is known to immediately converge, 0 otherwise.
+int cxxrtl_eval(cxxrtl_handle handle);
+
+// Commit the design, replacing the `curr` value of internal state and output wires with the `next`
+// value.
+//
+// Return 1 if any of the `curr` values were updated, 0 otherwise.
+int cxxrtl_commit(cxxrtl_handle handle);
+
// Simulate the design to a fixed point.
//
// Returns the number of delta cycles.
size_t cxxrtl_step(cxxrtl_handle handle);
// Type of a simulated object.
+//
+// The type of a simulated object indicates the way it is stored and the operations that are legal
+// to perform on it (i.e. won't crash the simulation). It says very little about object semantics,
+// which is specified through flags.
enum cxxrtl_type {
// Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by
// combinatorial cells, or toplevel input nodes.
@@ -74,7 +104,8 @@ enum cxxrtl_type {
CXXRTL_VALUE = 0,
// Wires correspond to doubly buffered netlist nodes, i.e. nodes driven, at least in part, by
- // storage cells, or by combinatorial cells that are a part of a feedback path.
+ // storage cells, or by combinatorial cells that are a part of a feedback path. They are also
+ // present in non-optimized builds.
//
// Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are
// distinct for wires). Note that changes to the bits driven by combinatorial cells will be
@@ -91,19 +122,91 @@ enum cxxrtl_type {
CXXRTL_MEMORY = 2,
// Aliases correspond to netlist nodes driven by another node such that their value is always
- // exactly equal, or driven by a constant value.
+ // exactly equal.
//
// Aliases can be inspected via the `curr` pointer. They cannot be modified, and the `next`
// pointer is always NULL.
CXXRTL_ALIAS = 3,
+ // Outlines correspond to netlist nodes that were optimized in a way that makes them inaccessible
+ // outside of a module's `eval()` function. At the highest debug information level, every inlined
+ // node has a corresponding outline object.
+ //
+ // Outlines can be inspected via the `curr` pointer and can never be modified; the `next` pointer
+ // is always NULL. Unlike all other objects, the bits of an outline object are meaningful only
+ // after a call to `cxxrtl_outline_eval` and until any subsequent modification to the netlist.
+ // Observing this requirement is the responsibility of the caller; it is not enforced.
+ //
+ // Outlines always correspond to combinatorial netlist nodes that are not ports.
+ CXXRTL_OUTLINE = 4,
+
// More object types may be added in the future, but the existing ones will never change.
};
+// Flags of a simulated object.
+//
+// The flags of a simulated object indicate its role in the netlist:
+// * The flags `CXXRTL_INPUT` and `CXXRTL_OUTPUT` designate module ports.
+// * The flags `CXXRTL_DRIVEN_SYNC`, `CXXRTL_DRIVEN_COMB`, and `CXXRTL_UNDRIVEN` specify
+// the semantics of node state. An object with several of these flags set has different bits
+// follow different semantics.
+enum cxxrtl_flag {
+ // Node is a module input port.
+ //
+ // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
+ // with `CXXRTL_OUTPUT`, as well as other flags.
+ CXXRTL_INPUT = 1 << 0,
+
+ // Node is a module output port.
+ //
+ // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with `CXXRTL_INPUT`,
+ // as well as other flags.
+ CXXRTL_OUTPUT = 1 << 1,
+
+ // Node is a module inout port.
+ //
+ // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with other flags.
+ CXXRTL_INOUT = (CXXRTL_INPUT|CXXRTL_OUTPUT),
+
+ // Node has bits that are driven by a storage cell.
+ //
+ // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with
+ // `CXXRTL_DRIVEN_COMB` and `CXXRTL_UNDRIVEN`, as well as other flags.
+ //
+ // This flag is set on wires that have bits connected directly to the output of a flip-flop or
+ // a latch, and hold its state. Many `CXXRTL_WIRE` objects may not have the `CXXRTL_DRIVEN_SYNC`
+ // flag set; for example, output ports and feedback wires generally won't. Writing to the `next`
+ // pointer of these wires updates stored state, and for designs without combinatorial loops,
+ // capturing the value from every of these wires through the `curr` pointer creates a complete
+ // snapshot of the design state.
+ CXXRTL_DRIVEN_SYNC = 1 << 2,
+
+ // Node has bits that are driven by a combinatorial cell or another node.
+ //
+ // This flag can be set on objects of type `CXXRTL_VALUE`, `CXXRTL_WIRE`, and `CXXRTL_OUTLINE`.
+ // It may be combined with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags.
+ //
+ // This flag is set on objects that have bits connected to the output of a combinatorial cell,
+ // or directly to another node. For designs without combinatorial loops, writing to such bits
+ // through the `next` pointer (if it is not NULL) has no effect.
+ CXXRTL_DRIVEN_COMB = 1 << 3,
+
+ // Node has bits that are not driven.
+ //
+ // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
+ // with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_DRIVEN_COMB`, as well as other flags.
+ //
+ // This flag is set on objects that have bits not driven by an output of any cell or by another
+ // node, such as inputs and dangling wires.
+ CXXRTL_UNDRIVEN = 1 << 4,
+
+ // More object flags may be added in the future, but the existing ones will never change.
+};
+
// Description of a simulated object.
//
-// The `data` array can be accessed directly to inspect and, if applicable, modify the bits
-// stored in the object.
+// The `curr` and `next` arrays can be accessed directly to inspect and, if applicable, modify
+// the bits stored in the object.
struct cxxrtl_object {
// Type of the object.
//
@@ -111,6 +214,9 @@ struct cxxrtl_object {
// determines all other properties of the object.
uint32_t type; // actually `enum cxxrtl_type`
+ // Flags of the object.
+ uint32_t flags; // actually bit mask of `enum cxxrtl_flags`
+
// Width of the object in bits.
size_t width;
@@ -137,6 +243,12 @@ struct cxxrtl_object {
uint32_t *curr;
uint32_t *next;
+ // Opaque reference to an outline. Only meaningful for outline objects.
+ //
+ // See the documentation of `cxxrtl_outline` for details. When creating a `cxxrtl_object`, set
+ // this field to NULL.
+ struct _cxxrtl_outline *outline;
+
// More description fields may be added in the future, but the existing ones will never change.
};
@@ -160,7 +272,7 @@ struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, s
// This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that,
// if the object exists, it consists of a single part. If assertions are disabled, it returns NULL
// for multi-part objects.
-inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) {
+static inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) {
size_t parts = 0;
struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts);
assert(object == NULL || parts == 1);
@@ -178,6 +290,20 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,
void (*callback)(void *data, const char *name,
struct cxxrtl_object *object, size_t parts));
+// Opaque reference to an outline.
+//
+// An outline is a group of outline objects that are evaluated simultaneously. The identity of
+// an outline can be compared to determine whether any two objects belong to the same outline.
+typedef struct _cxxrtl_outline *cxxrtl_outline;
+
+// Evaluate an outline.
+//
+// After evaluating an outline, the bits of every outline object contained in it are consistent
+// with the current state of the netlist. In general, any further modification to the netlist
+// causes every outline object to become stale, after which the corresponding outline must be
+// re-evaluated, otherwise the bits read from that object are meaningless.
+void cxxrtl_outline_eval(cxxrtl_outline outline);
+
#ifdef __cplusplus
}
#endif
diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h
index dbeabbaf2..b76922bbd 100644
--- a/backends/cxxrtl/cxxrtl_vcd.h
+++ b/backends/cxxrtl/cxxrtl_vcd.h
@@ -28,10 +28,13 @@ class vcd_writer {
size_t ident;
size_t width;
chunk_t *curr;
- size_t prev_off;
+ size_t cache_offset;
+ debug_outline *outline;
+ bool *outline_warm;
};
std::vector<std::string> current_scope;
+ std::map<debug_outline*, bool> outlines;
std::vector<variable> variables;
std::vector<chunk_t> cache;
std::map<chunk_t*, size_t> aliases;
@@ -66,12 +69,25 @@ class vcd_writer {
} while (ident != 0);
}
+ void emit_name(const std::string &name) {
+ for (char c : name) {
+ if (c == ':') {
+ // Due to a bug, GTKWave cannot parse a colon in the variable name, causing the VCD file
+ // to be unreadable. It cannot be escaped either, so replace it with the sideways colon.
+ buffer += "..";
+ } else {
+ buffer += c;
+ }
+ }
+ }
+
void emit_var(const variable &var, const std::string &type, const std::string &name,
size_t lsb_at, bool multipart) {
assert(!streaming);
buffer += "$var " + type + " " + std::to_string(var.width) + " ";
emit_ident(var.ident);
- buffer += " " + name;
+ buffer += " ";
+ emit_name(name);
if (multipart || name.back() == ']' || lsb_at != 0) {
if (var.width == 1)
buffer += " [" + std::to_string(lsb_at) + "]";
@@ -112,16 +128,22 @@ class vcd_writer {
buffer += '\n';
}
- const variable &register_variable(size_t width, chunk_t *curr, bool constant = false) {
+ void reset_outlines() {
+ for (auto &outline_it : outlines)
+ outline_it.second = /*warm=*/(outline_it.first == nullptr);
+ }
+
+ variable &register_variable(size_t width, chunk_t *curr, bool constant = false, debug_outline *outline = nullptr) {
if (aliases.count(curr)) {
return variables[aliases[curr]];
} else {
+ auto outline_it = outlines.emplace(outline, /*warm=*/(outline == nullptr)).first;
const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
aliases[curr] = variables.size();
if (constant) {
- variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1 });
+ variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1, outline_it->first, &outline_it->second });
} else {
- variables.emplace_back(variable { variables.size(), width, curr, cache.size() });
+ variables.emplace_back(variable { variables.size(), width, curr, cache.size(), outline_it->first, &outline_it->second });
cache.insert(cache.end(), &curr[0], &curr[chunks]);
}
return variables.back();
@@ -129,13 +151,17 @@ class vcd_writer {
}
bool test_variable(const variable &var) {
- if (var.prev_off == (size_t)-1)
+ if (var.cache_offset == (size_t)-1)
return false; // constant
+ if (!*var.outline_warm) {
+ var.outline->eval();
+ *var.outline_warm = true;
+ }
const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
- if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) {
+ if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset])) {
return false;
} else {
- std::copy(&var.curr[0], &var.curr[chunks], &cache[var.prev_off]);
+ std::copy(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset]);
return true;
}
}
@@ -197,6 +223,10 @@ public:
emit_var(register_variable(item.width, item.curr),
"wire", name, item.lsb_at, multipart);
break;
+ case debug_item::OUTLINE:
+ emit_var(register_variable(item.width, item.curr, /*constant=*/false, item.outline),
+ "wire", name, item.lsb_at, multipart);
+ break;
}
}
@@ -211,13 +241,13 @@ public:
}
void add(const debug_items &items) {
- this->template add(items, [](const std::string &, const debug_item &) {
+ this->add(items, [](const std::string &, const debug_item &) {
return true;
});
}
void add_without_memories(const debug_items &items) {
- this->template add(items, [](const std::string &, const debug_item &item) {
+ this->add(items, [](const std::string &, const debug_item &item) {
return item.type != debug_item::MEMORY;
});
}
@@ -228,6 +258,7 @@ public:
emit_scope({});
emit_enddefinitions();
}
+ reset_outlines();
emit_time(timestamp);
for (auto var : variables)
if (test_variable(var) || first_sample) {
diff --git a/backends/edif/edif.cc b/backends/edif/edif.cc
index 5e6becfd0..370108444 100644
--- a/backends/edif/edif.cc
+++ b/backends/edif/edif.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -330,7 +330,7 @@ struct EdifBackend : public Backend {
}
*f << stringf("\n (property %s (string \"%d'h%s\"))", EDIF_DEF(name), GetSize(val.bits), hex_string.c_str());
}
- };
+ };
for (auto module : sorted_modules)
{
if (module->get_blackbox_attribute())
@@ -373,8 +373,8 @@ struct EdifBackend : public Backend {
}
{
- int c1 = w1->name[0] == '\\';
- int c2 = w2->name[0] == '\\';
+ int c1 = w1->name.isPublic();
+ int c2 = w2->name.isPublic();
if (c1 > c2) goto promote;
if (c1 < c2) goto nopromote;
@@ -524,7 +524,7 @@ struct EdifBackend : public Backend {
*f << stringf(" (portRef %c (instanceRef GND))\n", gndvccy ? 'Y' : 'G');
if (sig == RTLIL::State::S1)
*f << stringf(" (portRef %c (instanceRef VCC))\n", gndvccy ? 'Y' : 'P');
- }
+ }
*f << stringf(" )");
if (attr_properties && sig.wire != NULL)
for (auto &p : sig.wire->attributes)
diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc
index 9739a7a9f..85c44824f 100644
--- a/backends/firrtl/firrtl.cc
+++ b/backends/firrtl/firrtl.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -23,6 +23,7 @@
#include "kernel/celltypes.h"
#include "kernel/cellaigs.h"
#include "kernel/log.h"
+#include "kernel/mem.h"
#include <algorithm>
#include <string>
#include <vector>
@@ -102,135 +103,269 @@ const char *make_id(IdString id)
return namecache.at(id).c_str();
}
-struct FirrtlWorker
+std::string dump_const_string(const RTLIL::Const &data)
{
- Module *module;
- std::ostream &f;
+ std::string res_str;
- dict<SigBit, pair<string, int>> reverse_wire_map;
- string unconn_id;
- RTLIL::Design *design;
- std::string indent;
+ std::string str = data.decode_string();
+ for (size_t i = 0; i < str.size(); i++)
+ {
+ if (str[i] == '\n')
+ res_str += "\\n";
+ else if (str[i] == '\t')
+ res_str += "\\t";
+ else if (str[i] < 32)
+ res_str += stringf("\\%03o", str[i]);
+ else if (str[i] == '"')
+ res_str += "\\\"";
+ else if (str[i] == '\\')
+ res_str += "\\\\";
+ else
+ res_str += str[i];
+ }
- // Define read/write ports and memories.
- // We'll collect their definitions and emit the corresponding FIRRTL definitions at the appropriate point in module construction.
- // For the moment, we don't handle $readmemh or $readmemb.
- // These will be part of a subsequent PR.
- struct read_port {
- string name;
- bool clk_enable;
- bool clk_parity;
- bool transparent;
- RTLIL::SigSpec clk;
- RTLIL::SigSpec ena;
- RTLIL::SigSpec addr;
- read_port(string name, bool clk_enable, bool clk_parity, bool transparent, RTLIL::SigSpec clk, RTLIL::SigSpec ena, RTLIL::SigSpec addr) : name(name), clk_enable(clk_enable), clk_parity(clk_parity), transparent(transparent), clk(clk), ena(ena), addr(addr) {
- // Current (3/13/2019) conventions:
- // generate a constant 0 for clock and a constant 1 for enable if they are undefined.
- if (!clk.is_fully_def())
- this->clk = SigSpec(State::S0);
- if (!ena.is_fully_def())
- this->ena = SigSpec(State::S1);
- }
- string gen_read(const char * indent) {
- string addr_expr = make_expr(addr);
- string ena_expr = make_expr(ena);
- string clk_expr = make_expr(clk);
- string addr_str = stringf("%s%s.addr <= %s\n", indent, name.c_str(), addr_expr.c_str());
- string ena_str = stringf("%s%s.en <= %s\n", indent, name.c_str(), ena_expr.c_str());
- string clk_str = stringf("%s%s.clk <= asClock(%s)\n", indent, name.c_str(), clk_expr.c_str());
- return addr_str + ena_str + clk_str;
- }
- };
- struct write_port : read_port {
- RTLIL::SigSpec mask;
- write_port(string name, bool clk_enable, bool clk_parity, bool transparent, RTLIL::SigSpec clk, RTLIL::SigSpec ena, RTLIL::SigSpec addr, RTLIL::SigSpec mask) : read_port(name, clk_enable, clk_parity, transparent, clk, ena, addr), mask(mask) {
- if (!clk.is_fully_def())
- this->clk = SigSpec(RTLIL::Const(0));
- if (!ena.is_fully_def())
- this->ena = SigSpec(RTLIL::Const(0));
- if (!mask.is_fully_def())
- this->ena = SigSpec(RTLIL::Const(1));
- }
- string gen_read(const char * /* indent */) {
- log_error("gen_read called on write_port: %s\n", name.c_str());
- return stringf("gen_read called on write_port: %s\n", name.c_str());
- }
- string gen_write(const char * indent) {
- string addr_expr = make_expr(addr);
- string ena_expr = make_expr(ena);
- string clk_expr = make_expr(clk);
- string mask_expr = make_expr(mask);
- string mask_str = stringf("%s%s.mask <= %s\n", indent, name.c_str(), mask_expr.c_str());
- string addr_str = stringf("%s%s.addr <= %s\n", indent, name.c_str(), addr_expr.c_str());
- string ena_str = stringf("%s%s.en <= %s\n", indent, name.c_str(), ena_expr.c_str());
- string clk_str = stringf("%s%s.clk <= asClock(%s)\n", indent, name.c_str(), clk_expr.c_str());
- return addr_str + ena_str + clk_str + mask_str;
- }
- };
- /* Memories defined within this module. */
- struct memory {
- Cell *pCell; // for error reporting
- string name; // memory name
- int abits; // number of address bits
- int size; // size (in units) of the memory
- int width; // size (in bits) of each element
- int read_latency;
- int write_latency;
- vector<read_port> read_ports;
- vector<write_port> write_ports;
- std::string init_file;
- std::string init_file_srcFileSpec;
- string srcLine;
- memory(Cell *pCell, string name, int abits, int size, int width) : pCell(pCell), name(name), abits(abits), size(size), width(width), read_latency(0), write_latency(1), init_file(""), init_file_srcFileSpec("") {
- // Provide defaults for abits or size if one (but not the other) is specified.
- if (this->abits == 0 && this->size != 0) {
- this->abits = ceil_log2(this->size);
- } else if (this->abits != 0 && this->size == 0) {
- this->size = 1 << this->abits;
- }
- // Sanity-check this construction.
- if (this->name == "") {
- log_error("Nameless memory%s\n", this->atLine());
- }
- if (this->abits == 0 && this->size == 0) {
- log_error("Memory %s has zero address bits and size%s\n", this->name.c_str(), this->atLine());
- }
- if (this->width == 0) {
- log_error("Memory %s has zero width%s\n", this->name.c_str(), this->atLine());
- }
- }
+ return res_str;
+}
- // We need a default constructor for the dict insert.
- memory() : pCell(0), read_latency(0), write_latency(1), init_file(""), init_file_srcFileSpec(""){}
+std::string dump_const(const RTLIL::Const &data)
+{
+ std::string res_str;
+
+ // // For debugging purposes to find out how Yosys encodes flags.
+ // res_str += stringf("flags_%x --> ", data.flags);
- const char *atLine() {
- if (srcLine == "") {
- if (pCell) {
- auto p = pCell->attributes.find(ID::src);
- srcLine = " at " + p->second.decode_string();
+ // Real-valued parameter.
+ if (data.flags & RTLIL::CONST_FLAG_REAL)
+ {
+ // Yosys stores real values as strings, so we call the string dumping code.
+ res_str += dump_const_string(data);
+ }
+ // String parameter.
+ else if (data.flags & RTLIL::CONST_FLAG_STRING)
+ {
+ res_str += "\"";
+ res_str += dump_const_string(data);
+ res_str += "\"";
+ }
+ // Numeric (non-real) parameter.
+ else
+ {
+ int width = data.bits.size();
+
+ // If a standard 32-bit int, then emit standard int value like "56" or
+ // "-56". Firrtl supports negative-valued int literals.
+ //
+ // SignedInt
+ // : ( '+' | '-' ) PosInt
+ // ;
+ if (width <= 32)
+ {
+ int32_t int_val = 0;
+
+ for (int i = 0; i < width; i++)
+ {
+ switch (data.bits[i])
+ {
+ case State::S0: break;
+ case State::S1: int_val |= (1 << i); break;
+ default:
+ log_error("Unexpected int value\n");
+ break;
}
}
- return srcLine.c_str();
- }
- void add_memory_read_port(read_port &rp) {
- read_ports.push_back(rp);
+
+ res_str += stringf("%d", int_val);
}
- void add_memory_write_port(write_port &wp) {
- write_ports.push_back(wp);
+ else
+ {
+ // If value is larger than 32 bits, then emit a binary representation of
+ // the number as integers are not large enough to contain the result.
+ // There is a caveat to this approach though:
+ //
+ // Note that parameter may be defined as having a fixed width as follows:
+ //
+ // parameter signed [26:0] test_signed;
+ // parameter [26:0] test_unsigned;
+ // parameter signed [40:0] test_signed_large;
+ //
+ // However, if you assign a value on the RHS without specifying the
+ // precision, then yosys considers the value you used as an int and
+ // assigns it a width of 32 bits regardless of the type of the parameter.
+ //
+ // defparam <inst_name> .test_signed = 49; (width = 32, though should be 27 based on definition)
+ // defparam <inst_name> .test_unsigned = 40'd35; (width = 40, though should be 27 based on definition)
+ // defparam <inst_name> .test_signed_large = 40'd12; (width = 40)
+ //
+ // We therefore may lose the precision of the original verilog literal if
+ // it was written without its bitwidth specifier.
+
+ // Emit binary prefix for string.
+ res_str += "\"b";
+
+ // Emit bits.
+ for (int i = width - 1; i >= 0; i--)
+ {
+ log_assert(i < width);
+ switch (data.bits[i])
+ {
+ case State::S0: res_str += "0"; break;
+ case State::S1: res_str += "1"; break;
+ case State::Sx: res_str += "x"; break;
+ case State::Sz: res_str += "z"; break;
+ case State::Sa: res_str += "-"; break;
+ case State::Sm: res_str += "m"; break;
+ }
+ }
+
+ res_str += "\"";
}
- void add_memory_file(std::string init_file, std::string init_file_srcFileSpec) {
- this->init_file = init_file;
- this->init_file_srcFileSpec = init_file_srcFileSpec;
+ }
+
+ return res_str;
+}
+
+std::string extmodule_name(RTLIL::Cell *cell, RTLIL::Module *mod_instance)
+{
+ // Since we are creating a custom extmodule for every cell that instantiates
+ // this blackbox, we need to create a custom name for it. We just use the
+ // name of the blackbox itself followed by the name of the cell.
+ const std::string cell_name = std::string(make_id(cell->name));
+ const std::string blackbox_name = std::string(make_id(mod_instance->name));
+ const std::string extmodule_name = blackbox_name + "_" + cell_name;
+ return extmodule_name;
+}
+
+/**
+ * Emits a parameterized extmodule. Instance parameters are obtained from
+ * ''cell'' as it represents the instantiation of the blackbox defined by
+ * ''mod_instance'' and therefore contains all its instance parameters.
+ */
+void emit_extmodule(RTLIL::Cell *cell, RTLIL::Module *mod_instance, std::ostream &f)
+{
+ const std::string indent = " ";
+
+ const std::string blackbox_name = std::string(make_id(mod_instance->name));
+ const std::string exported_name = extmodule_name(cell, mod_instance);
+
+ // We use the cell's fileinfo for this extmodule as its parameters come from
+ // the cell and not from the module itself (the module contains default
+ // parameters, not the instance-specific ones we're using to emit the
+ // extmodule).
+ const std::string extmoduleFileinfo = getFileinfo(cell);
+
+ // Emit extmodule header.
+ f << stringf(" extmodule %s: %s\n", exported_name.c_str(), extmoduleFileinfo.c_str());
+
+ // Emit extmodule ports.
+ for (auto wire : mod_instance->wires())
+ {
+ const auto wireName = make_id(wire->name);
+ const std::string wireFileinfo = getFileinfo(wire);
+
+ if (wire->port_input && wire->port_output)
+ {
+ log_error("Module port %s.%s is inout!\n", log_id(mod_instance), log_id(wire));
}
- };
- dict<string, memory> memories;
+ const std::string portDecl = stringf("%s%s %s: UInt<%d> %s\n",
+ indent.c_str(),
+ wire->port_input ? "input" : "output",
+ wireName,
+ wire->width,
+ wireFileinfo.c_str()
+ );
+
+ f << portDecl;
+ }
- void register_memory(memory &m)
+ // Emit extmodule "defname" field. This is the name of the verilog blackbox
+ // that is used when verilog is emitted, so we use the name of mod_instance
+ // here.
+ f << stringf("%sdefname = %s\n", indent.c_str(), blackbox_name.c_str());
+
+ // Emit extmodule generic parameters.
+ for (const auto &p : cell->parameters)
{
- memories[m.name] = m;
+ const RTLIL::IdString p_id = p.first;
+ const RTLIL::Const p_value = p.second;
+
+ std::string param_name(p_id.c_str());
+ const std::string param_value = dump_const(p_value);
+
+ // Remove backslashes from parameters as these come from the internal RTLIL
+ // naming scheme, but should not exist in the emitted firrtl blackboxes.
+ // When firrtl is converted to verilog and given to downstream synthesis
+ // tools, these tools expect to find blackbox names and parameters as they
+ // were originally defined, i.e. without the extra RTLIL naming conventions.
+ param_name.erase(
+ std::remove(param_name.begin(), param_name.end(), '\\'),
+ param_name.end()
+ );
+
+ f << stringf("%sparameter %s = %s\n", indent.c_str(), param_name.c_str(), param_value.c_str());
+ }
+
+ f << "\n";
+}
+
+/**
+ * Emits extmodules for every instantiated blackbox in the design.
+ *
+ * RTLIL stores instance parameters at the cell's instantiation location.
+ * However, firrtl does not support module parameterization (everything is
+ * already elaborated). Firrtl instead supports external modules (extmodule),
+ * i.e. blackboxes that are defined by verilog and which have no body in
+ * firrtl itself other than the declaration of the blackboxes ports and
+ * parameters.
+ *
+ * Furthermore, firrtl does not support parameterization (even of extmodules)
+ * at a module's instantiation location and users must instead declare
+ * different extmodules with different instance parameters in the extmodule
+ * definition itself.
+ *
+ * This function goes through the design to identify all RTLIL blackboxes
+ * and emit parameterized extmodules with a unique name for each of them. The
+ * name that's given to the extmodule is
+ *
+ * <blackbox_name>_<instance_name>
+ *
+ * Beware that it is therefore necessary for users to replace "parameterized"
+ * instances in the RTLIL sense with these custom extmodules for the firrtl to
+ * be valid.
+ */
+void emit_elaborated_extmodules(RTLIL::Design *design, std::ostream &f)
+{
+ for (auto module : design->modules())
+ {
+ for (auto cell : module->cells())
+ {
+ // Is this cell a module instance?
+ bool cellIsModuleInstance = cell->type[0] != '$';
+
+ if (cellIsModuleInstance)
+ {
+ // Find the module corresponding to this instance.
+ auto modInstance = design->module(cell->type);
+ bool modIsBlackbox = modInstance->get_blackbox_attribute();
+
+ if (modIsBlackbox)
+ {
+ emit_extmodule(cell, modInstance, f);
+ }
+ }
+ }
}
+}
+
+struct FirrtlWorker
+{
+ Module *module;
+ std::ostream &f;
+
+ dict<SigBit, pair<string, int>> reverse_wire_map;
+ string unconn_id;
+ RTLIL::Design *design;
+ std::string indent;
void register_reverse_wire_map(string id, SigSpec sig)
{
@@ -328,8 +463,16 @@ struct FirrtlWorker
log_warning("No instance for %s.%s\n", cell_type.c_str(), cell_name.c_str());
return;
}
+
+ // If the instance is that of a blackbox, use the modified extmodule name
+ // that contains per-instance parameterizations. These instances were
+ // emitted earlier in the firrtl backend.
+ const std::string instanceName = instModule->get_blackbox_attribute() ?
+ extmodule_name(cell, instModule) :
+ instanceOf;
+
std::string cellFileinfo = getFileinfo(cell);
- wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceOf.c_str(), cellFileinfo.c_str()));
+ wire_exprs.push_back(stringf("%s" "inst %s%s of %s %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceName.c_str(), cellFileinfo.c_str()));
for (auto it = cell->connections().begin(); it != cell->connections().end(); ++it) {
if (it->second.size() > 0) {
@@ -392,38 +535,15 @@ struct FirrtlWorker
return result;
}
- void emit_extmodule()
- {
- std::string moduleFileinfo = getFileinfo(module);
- f << stringf(" extmodule %s: %s\n", make_id(module->name), moduleFileinfo.c_str());
- vector<std::string> port_decls;
-
- for (auto wire : module->wires())
- {
- const auto wireName = make_id(wire->name);
- std::string wireFileinfo = getFileinfo(wire);
-
- if (wire->port_input && wire->port_output)
- {
- log_error("Module port %s.%s is inout!\n", log_id(module), log_id(wire));
- }
- port_decls.push_back(stringf(" %s %s: UInt<%d> %s\n", wire->port_input ? "input" : "output",
- wireName, wire->width, wireFileinfo.c_str()));
- }
-
- for (auto &str : port_decls)
- {
- f << str;
- }
-
- f << stringf("\n");
- }
-
void emit_module()
{
std::string moduleFileinfo = getFileinfo(module);
f << stringf(" module %s: %s\n", make_id(module->name), moduleFileinfo.c_str());
- vector<string> port_decls, wire_decls, cell_exprs, wire_exprs;
+ vector<string> port_decls, wire_decls, mem_exprs, cell_exprs, wire_exprs;
+
+ std::vector<Mem> memories = Mem::get_all_memories(module);
+ for (auto &mem : memories)
+ mem.narrow();
for (auto wire : module->wires())
{
@@ -440,25 +560,26 @@ struct FirrtlWorker
{
if (wire->port_input && wire->port_output)
log_error("Module port %s.%s is inout!\n", log_id(module), log_id(wire));
- port_decls.push_back(stringf(" %s %s: UInt<%d> %s\n", wire->port_input ? "input" : "output",
+ port_decls.push_back(stringf("%s%s %s: UInt<%d> %s\n", indent.c_str(), wire->port_input ? "input" : "output",
wireName, wire->width, wireFileinfo.c_str()));
}
else
{
- wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", wireName, wire->width, wireFileinfo.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), wireName, wire->width, wireFileinfo.c_str()));
}
}
for (auto cell : module->cells())
{
- static Const ndef(0, 0);
+ Const ndef(0, 0);
// Is this cell is a module instance?
- if (cell->type[0] != '$')
+ if (module->design->module(cell->type))
{
process_instance(cell, wire_exprs);
continue;
}
+
// Not a module instance. Set up cell properties
bool extract_y_bits = false; // Assume no extraction of final bits will be required.
int a_width = cell->parameters.at(ID::A_WIDTH, ndef).as_int(); // The width of "A"
@@ -476,7 +597,7 @@ struct FirrtlWorker
if (cell->type.in(ID($not), ID($logic_not), ID($_NOT_), ID($neg), ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_bool), ID($reduce_xnor)))
{
string a_expr = make_expr(cell->getPort(ID::A));
- wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", y_id.c_str(), y_width, cellFileinfo.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str()));
if (a_signed) {
a_expr = "asSInt(" + a_expr + ")";
@@ -516,7 +637,7 @@ struct FirrtlWorker
if ((firrtl_is_signed && !always_uint))
expr = stringf("asUInt(%s)", expr.c_str());
- cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
+ cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
@@ -528,7 +649,7 @@ struct FirrtlWorker
string a_expr = make_expr(cell->getPort(ID::A));
string b_expr = make_expr(cell->getPort(ID::B));
std::string cellFileinfo = getFileinfo(cell);
- wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", y_id.c_str(), y_width, cellFileinfo.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), y_width, cellFileinfo.c_str()));
if (a_signed) {
a_expr = "asSInt(" + a_expr + ")";
@@ -746,7 +867,7 @@ struct FirrtlWorker
if ((firrtl_is_signed && !always_uint))
expr = stringf("asUInt(%s)", expr.c_str());
- cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
+ cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
@@ -759,136 +880,19 @@ struct FirrtlWorker
string a_expr = make_expr(cell->getPort(ID::A));
string b_expr = make_expr(cell->getPort(ID::B));
string s_expr = make_expr(cell->getPort(ID::S));
- wire_decls.push_back(stringf(" wire %s: UInt<%d> %s\n", y_id.c_str(), width, cellFileinfo.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d> %s\n", indent.c_str(), y_id.c_str(), width, cellFileinfo.c_str()));
string expr = stringf("mux(%s, %s, %s)", s_expr.c_str(), b_expr.c_str(), a_expr.c_str());
- cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
+ cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
- if (cell->type.in(ID($mem)))
+ if (cell->is_mem_cell())
{
- string mem_id = make_id(cell->name);
- int abits = cell->parameters.at(ID::ABITS).as_int();
- int width = cell->parameters.at(ID::WIDTH).as_int();
- int size = cell->parameters.at(ID::SIZE).as_int();
- memory m(cell, mem_id, abits, size, width);
- int rd_ports = cell->parameters.at(ID::RD_PORTS).as_int();
- int wr_ports = cell->parameters.at(ID::WR_PORTS).as_int();
-
- Const initdata = cell->parameters.at(ID::INIT);
- for (State bit : initdata.bits)
- if (bit != State::Sx)
- log_error("Memory with initialization data: %s.%s\n", log_id(module), log_id(cell));
-
- Const rd_clk_enable = cell->parameters.at(ID::RD_CLK_ENABLE);
- Const wr_clk_enable = cell->parameters.at(ID::WR_CLK_ENABLE);
- Const wr_clk_polarity = cell->parameters.at(ID::WR_CLK_POLARITY);
-
- int offset = cell->parameters.at(ID::OFFSET).as_int();
- if (offset != 0)
- log_error("Memory with nonzero offset: %s.%s\n", log_id(module), log_id(cell));
-
- for (int i = 0; i < rd_ports; i++)
- {
- if (rd_clk_enable[i] != State::S0)
- log_error("Clocked read port %d on memory %s.%s.\n", i, log_id(module), log_id(cell));
-
- SigSpec addr_sig = cell->getPort(ID::RD_ADDR).extract(i*abits, abits);
- SigSpec data_sig = cell->getPort(ID::RD_DATA).extract(i*width, width);
- string addr_expr = make_expr(addr_sig);
- string name(stringf("%s.r%d", m.name.c_str(), i));
- bool clk_enable = false;
- bool clk_parity = true;
- bool transparency = false;
- SigSpec ena_sig = RTLIL::SigSpec(RTLIL::State::S1, 1);
- SigSpec clk_sig = RTLIL::SigSpec(RTLIL::State::S0, 1);
- read_port rp(name, clk_enable, clk_parity, transparency, clk_sig, ena_sig, addr_sig);
- m.add_memory_read_port(rp);
- cell_exprs.push_back(rp.gen_read(indent.c_str()));
- register_reverse_wire_map(stringf("%s.data", name.c_str()), data_sig);
- }
-
- for (int i = 0; i < wr_ports; i++)
- {
- if (wr_clk_enable[i] != State::S1)
- log_error("Unclocked write port %d on memory %s.%s.\n", i, log_id(module), log_id(cell));
-
- if (wr_clk_polarity[i] != State::S1)
- log_error("Negedge write port %d on memory %s.%s.\n", i, log_id(module), log_id(cell));
-
- string name(stringf("%s.w%d", m.name.c_str(), i));
- bool clk_enable = true;
- bool clk_parity = true;
- bool transparency = false;
- SigSpec addr_sig =cell->getPort(ID::WR_ADDR).extract(i*abits, abits);
- string addr_expr = make_expr(addr_sig);
- SigSpec data_sig =cell->getPort(ID::WR_DATA).extract(i*width, width);
- string data_expr = make_expr(data_sig);
- SigSpec clk_sig = cell->getPort(ID::WR_CLK).extract(i);
- string clk_expr = make_expr(clk_sig);
-
- SigSpec wen_sig = cell->getPort(ID::WR_EN).extract(i*width, width);
- string wen_expr = make_expr(wen_sig[0]);
-
- for (int i = 1; i < GetSize(wen_sig); i++)
- if (wen_sig[0] != wen_sig[i])
- log_error("Complex write enable on port %d on memory %s.%s.\n", i, log_id(module), log_id(cell));
-
- SigSpec mask_sig = RTLIL::SigSpec(RTLIL::State::S1, 1);
- write_port wp(name, clk_enable, clk_parity, transparency, clk_sig, wen_sig[0], addr_sig, mask_sig);
- m.add_memory_write_port(wp);
- cell_exprs.push_back(stringf("%s%s.data <= %s\n", indent.c_str(), name.c_str(), data_expr.c_str()));
- cell_exprs.push_back(wp.gen_write(indent.c_str()));
- }
- register_memory(m);
- continue;
- }
-
- if (cell->type.in(ID($memwr), ID($memrd), ID($meminit)))
- {
- std::string cell_type = fid(cell->type);
- std::string mem_id = make_id(cell->parameters[ID::MEMID].decode_string());
- int abits = cell->parameters.at(ID::ABITS).as_int();
- int width = cell->parameters.at(ID::WIDTH).as_int();
- memory *mp = nullptr;
- if (cell->type == ID($meminit) ) {
- log_error("$meminit (%s.%s.%s) currently unsupported\n", log_id(module), log_id(cell), mem_id.c_str());
- } else {
- // It's a $memwr or $memrd. Remember the read/write port parameters for the eventual FIRRTL memory definition.
- auto addrSig = cell->getPort(ID::ADDR);
- auto dataSig = cell->getPort(ID::DATA);
- auto enableSig = cell->getPort(ID::EN);
- auto clockSig = cell->getPort(ID::CLK);
- Const clk_enable = cell->parameters.at(ID::CLK_ENABLE);
- Const clk_polarity = cell->parameters.at(ID::CLK_POLARITY);
-
- // Do we already have an entry for this memory?
- if (memories.count(mem_id) == 0) {
- memory m(cell, mem_id, abits, 0, width);
- register_memory(m);
- }
- mp = &memories.at(mem_id);
- int portNum = 0;
- bool transparency = false;
- string data_expr = make_expr(dataSig);
- if (cell->type.in(ID($memwr))) {
- portNum = (int) mp->write_ports.size();
- write_port wp(stringf("%s.w%d", mem_id.c_str(), portNum), clk_enable.as_bool(), clk_polarity.as_bool(), transparency, clockSig, enableSig, addrSig, dataSig);
- mp->add_memory_write_port(wp);
- cell_exprs.push_back(stringf("%s%s.data <= %s\n", indent.c_str(), wp.name.c_str(), data_expr.c_str()));
- cell_exprs.push_back(wp.gen_write(indent.c_str()));
- } else if (cell->type.in(ID($memrd))) {
- portNum = (int) mp->read_ports.size();
- read_port rp(stringf("%s.r%d", mem_id.c_str(), portNum), clk_enable.as_bool(), clk_polarity.as_bool(), transparency, clockSig, enableSig, addrSig);
- mp->add_memory_read_port(rp);
- cell_exprs.push_back(rp.gen_read(indent.c_str()));
- register_reverse_wire_map(stringf("%s.data", rp.name.c_str()), dataSig);
- }
- }
+ // Will be handled below, as part of a Mem.
continue;
}
@@ -902,20 +906,14 @@ struct FirrtlWorker
string expr = make_expr(cell->getPort(ID::D));
string clk_expr = "asClock(" + make_expr(cell->getPort(ID::CLK)) + ")";
- wire_decls.push_back(stringf(" reg %s: UInt<%d>, %s %s\n", y_id.c_str(), width, clk_expr.c_str(), cellFileinfo.c_str()));
+ wire_decls.push_back(stringf("%sreg %s: UInt<%d>, %s %s\n", indent.c_str(), y_id.c_str(), width, clk_expr.c_str(), cellFileinfo.c_str()));
- cell_exprs.push_back(stringf(" %s <= %s %s\n", y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
+ cell_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), y_id.c_str(), expr.c_str(), cellFileinfo.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Q));
continue;
}
- // This may be a parameterized module - paramod.
- if (cell->type.begins_with("$paramod"))
- {
- process_instance(cell, wire_exprs);
- continue;
- }
if (cell->type == ID($shiftx)) {
// assign y = a[b +: y_width];
// We'll extract the correct bits as part of the primop.
@@ -923,7 +921,7 @@ struct FirrtlWorker
string a_expr = make_expr(cell->getPort(ID::A));
// Get the initial bit selector
string b_expr = make_expr(cell->getPort(ID::B));
- wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
if (cell->getParam(ID::B_SIGNED).as_bool()) {
// Use validif to constrain the selection (test the sign bit)
@@ -933,7 +931,7 @@ struct FirrtlWorker
}
string expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_expr.c_str());
- cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str()));
+ cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
@@ -945,7 +943,7 @@ struct FirrtlWorker
string b_expr = make_expr(cell->getPort(ID::B));
auto b_string = b_expr.c_str();
string expr;
- wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
if (cell->getParam(ID::B_SIGNED).as_bool()) {
// We generate a left or right shift based on the sign of b.
@@ -959,7 +957,7 @@ struct FirrtlWorker
} else {
expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string);
}
- cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str()));
+ cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
@@ -972,22 +970,98 @@ struct FirrtlWorker
if (a_width < y_width) {
a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width);
}
- wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width));
- cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), a_expr.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
+ cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), a_expr.c_str()));
register_reverse_wire_map(y_id, cell->getPort(ID::Y));
continue;
}
log_error("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell));
}
+ for (auto &mem : memories) {
+ string mem_id = make_id(mem.memid);
+
+ Const init_data = mem.get_init_data();
+ if (!init_data.is_fully_undef())
+ log_error("Memory with initialization data: %s.%s\n", log_id(module), log_id(mem.memid));
+
+ if (mem.start_offset != 0)
+ log_error("Memory with nonzero offset: %s.%s\n", log_id(module), log_id(mem.memid));
+
+ for (int i = 0; i < GetSize(mem.rd_ports); i++)
+ {
+ auto &port = mem.rd_ports[i];
+ string port_name(stringf("%s.r%d", mem_id.c_str(), i));
+
+ if (port.clk_enable)
+ log_error("Clocked read port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid));
+
+ std::ostringstream rpe;
+
+ string addr_expr = make_expr(port.addr);
+ string ena_expr = make_expr(State::S1);
+ string clk_expr = make_expr(State::S0);
+
+ rpe << stringf("%s%s.addr <= %s\n", indent.c_str(), port_name.c_str(), addr_expr.c_str());
+ rpe << stringf("%s%s.en <= %s\n", indent.c_str(), port_name.c_str(), ena_expr.c_str());
+ rpe << stringf("%s%s.clk <= asClock(%s)\n", indent.c_str(), port_name.c_str(), clk_expr.c_str());
+ cell_exprs.push_back(rpe.str());
+ register_reverse_wire_map(stringf("%s.data", port_name.c_str()), port.data);
+ }
+
+ for (int i = 0; i < GetSize(mem.wr_ports); i++)
+ {
+ auto &port = mem.wr_ports[i];
+ string port_name(stringf("%s.w%d", mem_id.c_str(), i));
+
+ if (!port.clk_enable)
+ log_error("Unclocked write port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid));
+ if (!port.clk_polarity)
+ log_error("Negedge write port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid));
+ for (int i = 1; i < GetSize(port.en); i++)
+ if (port.en[0] != port.en[i])
+ log_error("Complex write enable on port %d on memory %s.%s.\n", i, log_id(module), log_id(mem.memid));
+
+ std::ostringstream wpe;
+
+ string data_expr = make_expr(port.data);
+ string addr_expr = make_expr(port.addr);
+ string ena_expr = make_expr(port.en[0]);
+ string clk_expr = make_expr(port.clk);
+ string mask_expr = make_expr(State::S1);
+ wpe << stringf("%s%s.data <= %s\n", indent.c_str(), port_name.c_str(), data_expr.c_str());
+ wpe << stringf("%s%s.addr <= %s\n", indent.c_str(), port_name.c_str(), addr_expr.c_str());
+ wpe << stringf("%s%s.en <= %s\n", indent.c_str(), port_name.c_str(), ena_expr.c_str());
+ wpe << stringf("%s%s.clk <= asClock(%s)\n", indent.c_str(), port_name.c_str(), clk_expr.c_str());
+ wpe << stringf("%s%s.mask <= %s\n", indent.c_str(), port_name.c_str(), mask_expr.c_str());
+
+ cell_exprs.push_back(wpe.str());
+ }
+
+ std::ostringstream me;
+
+ me << stringf(" mem %s:\n", mem_id.c_str());
+ me << stringf(" data-type => UInt<%d>\n", mem.width);
+ me << stringf(" depth => %d\n", mem.size);
+ for (int i = 0; i < GetSize(mem.rd_ports); i++)
+ me << stringf(" reader => r%d\n", i);
+ for (int i = 0; i < GetSize(mem.wr_ports); i++)
+ me << stringf(" writer => w%d\n", i);
+ me << stringf(" read-latency => %d\n", 0);
+ me << stringf(" write-latency => %d\n", 1);
+ me << stringf(" read-under-write => undefined\n");
+
+ mem_exprs.push_back(me.str());
+ }
+
for (auto conn : module->connections())
{
string y_id = next_id();
int y_width = GetSize(conn.first);
string expr = make_expr(conn.second);
- wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width));
- cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<%d>\n", indent.c_str(), y_id.c_str(), y_width));
+ cell_exprs.push_back(stringf("%s%s <= %s\n", indent.c_str(), y_id.c_str(), expr.c_str()));
register_reverse_wire_map(y_id, conn.first);
}
@@ -1053,13 +1127,13 @@ struct FirrtlWorker
if (is_valid) {
if (make_unconn_id) {
- wire_decls.push_back(stringf(" wire %s: UInt<1> %s\n", unconn_id.c_str(), wireFileinfo.c_str()));
+ wire_decls.push_back(stringf("%swire %s: UInt<1> %s\n", indent.c_str(), unconn_id.c_str(), wireFileinfo.c_str()));
// `invalid` is a firrtl construction for simulation so we will not
// tag it with a @[fileinfo] tag as it doesn't directly correspond to
// a specific line of verilog code.
- wire_decls.push_back(stringf(" %s is invalid\n", unconn_id.c_str()));
+ wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), unconn_id.c_str()));
}
- wire_exprs.push_back(stringf(" %s <= %s %s\n", make_id(wire->name), expr.c_str(), wireFileinfo.c_str()));
+ wire_exprs.push_back(stringf("%s%s <= %s %s\n", indent.c_str(), make_id(wire->name), expr.c_str(), wireFileinfo.c_str()));
} else {
if (make_unconn_id) {
unconn_id.clear();
@@ -1067,7 +1141,7 @@ struct FirrtlWorker
// `invalid` is a firrtl construction for simulation so we will not
// tag it with a @[fileinfo] tag as it doesn't directly correspond to
// a specific line of verilog code.
- wire_decls.push_back(stringf(" %s is invalid\n", make_id(wire->name)));
+ wire_decls.push_back(stringf("%s%s is invalid\n", indent.c_str(), make_id(wire->name)));
}
}
@@ -1081,22 +1155,9 @@ struct FirrtlWorker
f << stringf("\n");
- // If we have any memory definitions, output them.
- for (auto kv : memories) {
- memory &m = kv.second;
- f << stringf(" mem %s:\n", m.name.c_str());
- f << stringf(" data-type => UInt<%d>\n", m.width);
- f << stringf(" depth => %d\n", m.size);
- for (int i = 0; i < (int) m.read_ports.size(); i += 1) {
- f << stringf(" reader => r%d\n", i);
- }
- for (int i = 0; i < (int) m.write_ports.size(); i += 1) {
- f << stringf(" writer => w%d\n", i);
- }
- f << stringf(" read-latency => %d\n", m.read_latency);
- f << stringf(" write-latency => %d\n", m.write_latency);
- f << stringf(" read-under-write => undefined\n");
- }
+ for (auto str : mem_exprs)
+ f << str;
+
f << stringf("\n");
for (auto str : cell_exprs)
@@ -1112,12 +1173,7 @@ struct FirrtlWorker
void run()
{
- // Blackboxes should be emitted as `extmodule`s in firrtl. Only ports are
- // emitted in such a case.
- if (module->get_blackbox_attribute())
- emit_extmodule();
- else
- emit_module();
+ emit_module();
}
};
@@ -1132,6 +1188,8 @@ struct FirrtlBackend : public Backend {
log("Write a FIRRTL netlist of the current design.\n");
log("The following commands are executed by this command:\n");
log(" pmuxtree\n");
+ log(" bmuxmap\n");
+ log(" demuxmap\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
@@ -1154,7 +1212,9 @@ struct FirrtlBackend : public Backend {
log_header(design, "Executing FIRRTL backend.\n");
log_push();
- Pass::call(design, stringf("pmuxtree"));
+ Pass::call(design, "pmuxtree");
+ Pass::call(design, "bmuxmap");
+ Pass::call(design, "demuxmap");
namecache.clear();
autoid_counter = 0;
@@ -1180,10 +1240,16 @@ struct FirrtlBackend : public Backend {
std::string circuitFileinfo = getFileinfo(top);
*f << stringf("circuit %s: %s\n", make_id(top->name), circuitFileinfo.c_str());
+ emit_elaborated_extmodules(design, *f);
+
+ // Emit non-blackbox modules.
for (auto module : design->modules())
{
- FirrtlWorker worker(module, *f, design);
- worker.run();
+ if (!module->get_blackbox_attribute())
+ {
+ FirrtlWorker worker(module, *f, design);
+ worker.run();
+ }
}
namecache.clear();
diff --git a/backends/ilang/Makefile.inc b/backends/ilang/Makefile.inc
deleted file mode 100644
index 52fc2b891..000000000
--- a/backends/ilang/Makefile.inc
+++ /dev/null
@@ -1,3 +0,0 @@
-
-OBJS += backends/ilang/ilang_backend.o
-
diff --git a/backends/intersynth/intersynth.cc b/backends/intersynth/intersynth.cc
index 98a14173b..59173c4a2 100644
--- a/backends/intersynth/intersynth.cc
+++ b/backends/intersynth/intersynth.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -59,7 +59,7 @@ struct IntersynthBackend : public Backend {
log(" do not generate celltypes and conntypes commands. i.e. just output\n");
log(" the netlists. this is used for postsilicon synthesis.\n");
log("\n");
- log(" -lib <verilog_or_ilang_file>\n");
+ log(" -lib <verilog_or_rtlil_file>\n");
log(" Use the specified library file for determining whether cell ports are\n");
log(" inputs or outputs. This option can be used multiple times to specify\n");
log(" more than one library.\n");
@@ -68,7 +68,7 @@ struct IntersynthBackend : public Backend {
log(" only write selected modules. modules must be selected entirely or\n");
log(" not at all.\n");
log("\n");
- log("http://www.clifford.at/intersynth/\n");
+ log("http://bygone.clairexen.net/intersynth/\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
@@ -108,7 +108,7 @@ struct IntersynthBackend : public Backend {
if (f.fail())
log_error("Can't open lib file `%s'.\n", filename.c_str());
RTLIL::Design *lib = new RTLIL::Design;
- Frontend::frontend_call(lib, &f, filename, (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0 ? "ilang" : "verilog"));
+ Frontend::frontend_call(lib, &f, filename, (filename.size() > 3 && filename.compare(filename.size()-3, std::string::npos, ".il") == 0 ? "rtlil" : "verilog"));
libs.push_back(lib);
}
diff --git a/backends/json/json.cc b/backends/json/json.cc
index eeadc1b89..4aa8046d6 100644
--- a/backends/json/json.cc
+++ b/backends/json/json.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -135,6 +135,10 @@ struct JsonWriter
// reserve 0 and 1 to avoid confusion with "0" and "1"
sigidcounter = 2;
+ if (module->has_processes()) {
+ log_error("Module %s contains processes, which are not supported by JSON backend (run `proc` first).\n", log_id(module));
+ }
+
f << stringf(" %s: {\n", get_name(module->name).c_str());
f << stringf(" \"attributes\": {");
@@ -216,6 +220,27 @@ struct JsonWriter
}
f << stringf("\n },\n");
+ if (!module->memories.empty()) {
+ f << stringf(" \"memories\": {");
+ first = true;
+ for (auto &it : module->memories) {
+ if (use_selection && !module->selected(it.second))
+ continue;
+ f << stringf("%s\n", first ? "" : ",");
+ f << stringf(" %s: {\n", get_name(it.second->name).c_str());
+ f << stringf(" \"hide_name\": %s,\n", it.second->name[0] == '$' ? "1" : "0");
+ f << stringf(" \"attributes\": {");
+ write_parameters(it.second->attributes);
+ f << stringf("\n },\n");
+ f << stringf(" \"width\": %d,\n", it.second->width);
+ f << stringf(" \"start_offset\": %d,\n", it.second->start_offset);
+ f << stringf(" \"size\": %d\n", it.second->size);
+ f << stringf(" }");
+ first = false;
+ }
+ f << stringf("\n },\n");
+ }
+
f << stringf(" \"netnames\": {");
first = true;
for (auto w : module->wires()) {
@@ -332,6 +357,10 @@ struct JsonBackend : public Backend {
log(" <cell_name>: <cell_details>,\n");
log(" ...\n");
log(" },\n");
+ log(" \"memories\": {\n");
+ log(" <memory_name>: <memory_details>,\n");
+ log(" ...\n");
+ log(" },\n");
log(" \"netnames\": {\n");
log(" <net_name>: <net_details>,\n");
log(" ...\n");
@@ -379,6 +408,19 @@ struct JsonBackend : public Backend {
log(" },\n");
log(" }\n");
log("\n");
+ log("And <memory_details> is:\n");
+ log("\n");
+ log(" {\n");
+ log(" \"hide_name\": <1 | 0>,\n");
+ log(" \"attributes\": {\n");
+ log(" <attribute_name>: <attribute_value>,\n");
+ log(" ...\n");
+ log(" },\n");
+ log(" \"width\": <memory width>\n");
+ log(" \"start_offset\": <the lowest valid memory address>\n");
+ log(" \"size\": <memory size>\n");
+ log(" }\n");
+ log("\n");
log("And <net_details> is:\n");
log("\n");
log(" {\n");
diff --git a/backends/protobuf/Makefile.inc b/backends/protobuf/Makefile.inc
index 834cad42c..9cac9dcaa 100644
--- a/backends/protobuf/Makefile.inc
+++ b/backends/protobuf/Makefile.inc
@@ -3,6 +3,8 @@ ifeq ($(ENABLE_PROTOBUF),1)
backends/protobuf/yosys.pb.cc backends/protobuf/yosys.pb.h: misc/yosys.proto
$(Q) cd misc && protoc --cpp_out "../backends/protobuf" yosys.proto
+backends/protobuf/protobuf.cc: backends/protobuf/yosys.pb.h
+
OBJS += backends/protobuf/protobuf.o backends/protobuf/yosys.pb.o
endif
diff --git a/backends/protobuf/protobuf.cc b/backends/protobuf/protobuf.cc
index f6623a382..384ce2e8e 100644
--- a/backends/protobuf/protobuf.cc
+++ b/backends/protobuf/protobuf.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* Copyright (C) 2018 Serge Bazanski <q3k@symbioticeda.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
diff --git a/backends/rtlil/Makefile.inc b/backends/rtlil/Makefile.inc
new file mode 100644
index 000000000..f691282ca
--- /dev/null
+++ b/backends/rtlil/Makefile.inc
@@ -0,0 +1,3 @@
+
+OBJS += backends/rtlil/rtlil_backend.o
+
diff --git a/backends/ilang/ilang_backend.cc b/backends/rtlil/rtlil_backend.cc
index aa5a175ca..1b11de5ec 100644
--- a/backends/ilang/ilang_backend.cc
+++ b/backends/rtlil/rtlil_backend.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -18,19 +18,19 @@
* ---
*
* A very simple and straightforward backend for the RTLIL text
- * representation (as understood by the 'ilang' frontend).
+ * representation.
*
*/
-#include "ilang_backend.h"
+#include "rtlil_backend.h"
#include "kernel/yosys.h"
#include <errno.h>
USING_YOSYS_NAMESPACE
-using namespace ILANG_BACKEND;
+using namespace RTLIL_BACKEND;
YOSYS_NAMESPACE_BEGIN
-void ILANG_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int width, int offset, bool autoint)
+void RTLIL_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int width, int offset, bool autoint)
{
if (width < 0)
width = data.bits.size() - offset;
@@ -51,15 +51,19 @@ void ILANG_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi
}
}
f << stringf("%d'", width);
- for (int i = offset+width-1; i >= offset; i--) {
- log_assert(i < (int)data.bits.size());
- switch (data.bits[i]) {
- case State::S0: f << stringf("0"); break;
- case State::S1: f << stringf("1"); break;
- case RTLIL::Sx: f << stringf("x"); break;
- case RTLIL::Sz: f << stringf("z"); break;
- case RTLIL::Sa: f << stringf("-"); break;
- case RTLIL::Sm: f << stringf("m"); break;
+ if (data.is_fully_undef()) {
+ f << "x";
+ } else {
+ for (int i = offset+width-1; i >= offset; i--) {
+ log_assert(i < (int)data.bits.size());
+ switch (data.bits[i]) {
+ case State::S0: f << stringf("0"); break;
+ case State::S1: f << stringf("1"); break;
+ case RTLIL::Sx: f << stringf("x"); break;
+ case RTLIL::Sz: f << stringf("z"); break;
+ case RTLIL::Sa: f << stringf("-"); break;
+ case RTLIL::Sm: f << stringf("m"); break;
+ }
}
}
} else {
@@ -83,7 +87,7 @@ void ILANG_BACKEND::dump_const(std::ostream &f, const RTLIL::Const &data, int wi
}
}
-void ILANG_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint)
+void RTLIL_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint)
{
if (chunk.wire == NULL) {
dump_const(f, chunk.data, chunk.width, chunk.offset, autoint);
@@ -97,7 +101,7 @@ void ILANG_BACKEND::dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk,
}
}
-void ILANG_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint)
+void RTLIL_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint)
{
if (sig.is_chunk()) {
dump_sigchunk(f, sig.as_chunk(), autoint);
@@ -111,7 +115,7 @@ void ILANG_BACKEND::dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, boo
}
}
-void ILANG_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire)
+void RTLIL_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::Wire *wire)
{
for (auto &it : wire->attributes) {
f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str());
@@ -136,7 +140,7 @@ void ILANG_BACKEND::dump_wire(std::ostream &f, std::string indent, const RTLIL::
f << stringf("%s\n", wire->name.c_str());
}
-void ILANG_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory)
+void RTLIL_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL::Memory *memory)
{
for (auto &it : memory->attributes) {
f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str());
@@ -153,7 +157,7 @@ void ILANG_BACKEND::dump_memory(std::ostream &f, std::string indent, const RTLIL
f << stringf("%s\n", memory->name.c_str());
}
-void ILANG_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell)
+void RTLIL_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::Cell *cell)
{
for (auto &it : cell->attributes) {
f << stringf("%s" "attribute %s ", indent.c_str(), it.first.c_str());
@@ -177,7 +181,7 @@ void ILANG_BACKEND::dump_cell(std::ostream &f, std::string indent, const RTLIL::
f << stringf("%s" "end\n", indent.c_str());
}
-void ILANG_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs)
+void RTLIL_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, const RTLIL::CaseRule *cs)
{
for (auto it = cs->actions.begin(); it != cs->actions.end(); ++it)
{
@@ -192,7 +196,7 @@ void ILANG_BACKEND::dump_proc_case_body(std::ostream &f, std::string indent, con
dump_proc_switch(f, indent, *it);
}
-void ILANG_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw)
+void RTLIL_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const RTLIL::SwitchRule *sw)
{
for (auto it = sw->attributes.begin(); it != sw->attributes.end(); ++it) {
f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str());
@@ -225,7 +229,7 @@ void ILANG_BACKEND::dump_proc_switch(std::ostream &f, std::string indent, const
f << stringf("%s" "end\n", indent.c_str());
}
-void ILANG_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy)
+void RTLIL_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RTLIL::SyncRule *sy)
{
f << stringf("%s" "sync ", indent.c_str());
switch (sy->type) {
@@ -242,16 +246,33 @@ void ILANG_BACKEND::dump_proc_sync(std::ostream &f, std::string indent, const RT
case RTLIL::STi: f << stringf("init\n"); break;
}
- for (auto it = sy->actions.begin(); it != sy->actions.end(); ++it) {
+ for (auto &it: sy->actions) {
f << stringf("%s update ", indent.c_str());
- dump_sigspec(f, it->first);
+ dump_sigspec(f, it.first);
f << stringf(" ");
- dump_sigspec(f, it->second);
+ dump_sigspec(f, it.second);
+ f << stringf("\n");
+ }
+
+ for (auto &it: sy->mem_write_actions) {
+ for (auto it2 = it.attributes.begin(); it2 != it.attributes.end(); ++it2) {
+ f << stringf("%s attribute %s ", indent.c_str(), it2->first.c_str());
+ dump_const(f, it2->second);
+ f << stringf("\n");
+ }
+ f << stringf("%s memwr %s ", indent.c_str(), it.memid.c_str());
+ dump_sigspec(f, it.address);
+ f << stringf(" ");
+ dump_sigspec(f, it.data);
+ f << stringf(" ");
+ dump_sigspec(f, it.enable);
+ f << stringf(" ");
+ dump_const(f, it.priority_mask);
f << stringf("\n");
}
}
-void ILANG_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc)
+void RTLIL_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::Process *proc)
{
for (auto it = proc->attributes.begin(); it != proc->attributes.end(); ++it) {
f << stringf("%s" "attribute %s ", indent.c_str(), it->first.c_str());
@@ -265,7 +286,7 @@ void ILANG_BACKEND::dump_proc(std::ostream &f, std::string indent, const RTLIL::
f << stringf("%s" "end\n", indent.c_str());
}
-void ILANG_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right)
+void RTLIL_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right)
{
f << stringf("%s" "connect ", indent.c_str());
dump_sigspec(f, left);
@@ -274,7 +295,7 @@ void ILANG_BACKEND::dump_conn(std::ostream &f, std::string indent, const RTLIL::
f << stringf("\n");
}
-void ILANG_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n)
+void RTLIL_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Module *module, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n)
{
bool print_header = flag_m || design->selected_whole_module(module->name);
bool print_body = !flag_n || !design->selected_whole_module(module->name);
@@ -337,8 +358,8 @@ void ILANG_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu
bool first_conn_line = true;
for (auto it = module->connections().begin(); it != module->connections().end(); ++it) {
- bool show_conn = !only_selected;
- if (only_selected) {
+ bool show_conn = !only_selected || design->selected_whole_module(module->name);
+ if (!show_conn) {
RTLIL::SigSpec sigs = it->first;
sigs.append(it->second);
for (auto &c : sigs.chunks()) {
@@ -360,7 +381,7 @@ void ILANG_BACKEND::dump_module(std::ostream &f, std::string indent, RTLIL::Modu
f << stringf("%s" "end\n", indent.c_str());
}
-void ILANG_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n)
+void RTLIL_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool only_selected, bool flag_m, bool flag_n)
{
int init_autoidx = autoidx;
@@ -396,15 +417,15 @@ void ILANG_BACKEND::dump_design(std::ostream &f, RTLIL::Design *design, bool onl
YOSYS_NAMESPACE_END
PRIVATE_NAMESPACE_BEGIN
-struct IlangBackend : public Backend {
- IlangBackend() : Backend("ilang", "write design to ilang file") { }
+struct RTLILBackend : public Backend {
+ RTLILBackend() : Backend("rtlil", "write design to RTLIL file") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
- log(" write_ilang [filename]\n");
+ log(" write_rtlil [filename]\n");
log("\n");
- log("Write the current design to an 'ilang' file. (ilang is a text representation\n");
+ log("Write the current design to an RTLIL file. (RTLIL is a text representation\n");
log("of a design in yosys's internal format.)\n");
log("\n");
log(" -selected\n");
@@ -415,7 +436,7 @@ struct IlangBackend : public Backend {
{
bool selected = false;
- log_header(design, "Executing ILANG backend.\n");
+ log_header(design, "Executing RTLIL backend.\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
@@ -432,12 +453,27 @@ struct IlangBackend : public Backend {
log("Output filename: %s\n", filename.c_str());
*f << stringf("# Generated by %s\n", yosys_version_str);
- ILANG_BACKEND::dump_design(*f, design, selected, true, false);
+ RTLIL_BACKEND::dump_design(*f, design, selected, true, false);
+ }
+} RTLILBackend;
+
+struct IlangBackend : public Backend {
+ IlangBackend() : Backend("ilang", "(deprecated) alias of write_rtlil") { }
+ void help() override
+ {
+ // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
+ log("\n");
+ log("See `help write_rtlil`.\n");
+ log("\n");
+ }
+ void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
+ {
+ RTLILBackend.execute(f, filename, args, design);
}
} IlangBackend;
struct DumpPass : public Pass {
- DumpPass() : Pass("dump", "print parts of the design in ilang format") { }
+ DumpPass() : Pass("dump", "print parts of the design in RTLIL format") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
@@ -445,7 +481,7 @@ struct DumpPass : public Pass {
log(" dump [options] [selection]\n");
log("\n");
log("Write the selected parts of the design to the console or specified file in\n");
- log("ilang format.\n");
+ log("RTLIL format.\n");
log("\n");
log(" -m\n");
log(" also dump the module headers, even if only parts of a single\n");
@@ -508,7 +544,7 @@ struct DumpPass : public Pass {
f = &buf;
}
- ILANG_BACKEND::dump_design(*f, design, true, flag_m, flag_n);
+ RTLIL_BACKEND::dump_design(*f, design, true, flag_m, flag_n);
if (!filename.empty()) {
delete f;
diff --git a/backends/ilang/ilang_backend.h b/backends/rtlil/rtlil_backend.h
index 97dcbb628..35829729c 100644
--- a/backends/ilang/ilang_backend.h
+++ b/backends/rtlil/rtlil_backend.h
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -18,19 +18,19 @@
* ---
*
* A very simple and straightforward backend for the RTLIL text
- * representation (as understood by the 'ilang' frontend).
+ * representation.
*
*/
-#ifndef ILANG_BACKEND_H
-#define ILANG_BACKEND_H
+#ifndef RTLIL_BACKEND_H
+#define RTLIL_BACKEND_H
#include "kernel/yosys.h"
#include <stdio.h>
YOSYS_NAMESPACE_BEGIN
-namespace ILANG_BACKEND {
+namespace RTLIL_BACKEND {
void dump_const(std::ostream &f, const RTLIL::Const &data, int width = -1, int offset = 0, bool autoint = true);
void dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool autoint = true);
void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig, bool autoint = true);
diff --git a/backends/simplec/simplec.cc b/backends/simplec/simplec.cc
index 3adeaa6c0..e283dcf7c 100644
--- a/backends/simplec/simplec.cc
+++ b/backends/simplec/simplec.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/backends/smt2/smt2.cc b/backends/smt2/smt2.cc
index 526b36352..a928419a1 100644
--- a/backends/smt2/smt2.cc
+++ b/backends/smt2/smt2.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,7 @@
#include "kernel/sigtools.h"
#include "kernel/celltypes.h"
#include "kernel/log.h"
+#include "kernel/mem.h"
#include <string>
USING_YOSYS_NAMESPACE
@@ -40,12 +41,15 @@ struct Smt2Worker
std::map<RTLIL::SigBit, RTLIL::Cell*> bit_driver;
std::set<RTLIL::Cell*> exported_cells, hiercells, hiercells_queue;
pool<Cell*> recursive_cells, registers;
+ std::vector<Mem> memories;
+ dict<Cell*, Mem*> mem_cells;
+ std::set<Mem*> memory_queue;
pool<SigBit> clock_posedge, clock_negedge;
vector<string> ex_state_eq, ex_input_eq;
std::map<RTLIL::SigBit, std::pair<int, int>> fcache;
- std::map<Cell*, int> memarrays;
+ std::map<Mem*, int> memarrays;
std::map<int, int> bvsizes;
dict<IdString, char*> ids;
@@ -116,12 +120,74 @@ struct Smt2Worker
makebits(stringf("%s_is", get_id(module)));
+ dict<IdString, Mem*> mem_dict;
+ memories = Mem::get_all_memories(module);
+ for (auto &mem : memories)
+ {
+ mem.narrow();
+ mem_dict[mem.memid] = &mem;
+ for (auto &port : mem.wr_ports)
+ {
+ if (port.clk_enable) {
+ SigSpec clk = sigmap(port.clk);
+ for (int i = 0; i < GetSize(clk); i++)
+ {
+ if (clk[i].wire == nullptr)
+ continue;
+ if (port.clk_polarity)
+ clock_posedge.insert(clk[i]);
+ else
+ clock_negedge.insert(clk[i]);
+ }
+ }
+ for (auto bit : sigmap(port.en))
+ noclock.insert(bit);
+ for (auto bit : sigmap(port.addr))
+ noclock.insert(bit);
+ for (auto bit : sigmap(port.data))
+ noclock.insert(bit);
+ }
+ for (auto &port : mem.rd_ports)
+ {
+ if (port.clk_enable) {
+ SigSpec clk = sigmap(port.clk);
+ for (int i = 0; i < GetSize(clk); i++)
+ {
+ if (clk[i].wire == nullptr)
+ continue;
+ if (port.clk_polarity)
+ clock_posedge.insert(clk[i]);
+ else
+ clock_negedge.insert(clk[i]);
+ }
+ }
+ for (auto bit : sigmap(port.en))
+ noclock.insert(bit);
+ for (auto bit : sigmap(port.addr))
+ noclock.insert(bit);
+ for (auto bit : sigmap(port.data))
+ noclock.insert(bit);
+ Cell *driver = port.cell ? port.cell : mem.cell;
+ for (auto bit : sigmap(port.data)) {
+ if (bit_driver.count(bit))
+ log_error("Found multiple drivers for %s.\n", log_signal(bit));
+ bit_driver[bit] = driver;
+ }
+ }
+ }
+
for (auto cell : module->cells())
for (auto &conn : cell->connections())
{
if (GetSize(conn.second) == 0)
continue;
+ // Handled above.
+ if (cell->is_mem_cell()) {
+ mem_cells[cell] = mem_dict[cell->parameters.at(ID::MEMID).decode_string()];
+ continue;
+ }
+
bool is_input = ct.cell_input(cell->type, conn.first);
bool is_output = ct.cell_output(cell->type, conn.first);
@@ -135,24 +201,6 @@ struct Smt2Worker
log_error("Unsupported or unknown directionality on port %s of cell %s.%s (%s).\n",
log_id(conn.first), log_id(module), log_id(cell), log_id(cell->type));
- if (cell->type.in(ID($mem)) && conn.first.in(ID::RD_CLK, ID::WR_CLK))
- {
- SigSpec clk = sigmap(conn.second);
- for (int i = 0; i < GetSize(clk); i++)
- {
- if (clk[i].wire == nullptr)
- continue;
-
- if (cell->getParam(conn.first == ID::RD_CLK ? ID::RD_CLK_ENABLE : ID::WR_CLK_ENABLE)[i] != State::S1)
- continue;
-
- if (cell->getParam(conn.first == ID::RD_CLK ? ID::RD_CLK_POLARITY : ID::WR_CLK_POLARITY)[i] == State::S1)
- clock_posedge.insert(clk[i]);
- else
- clock_negedge.insert(clk[i]);
- }
- }
- else
if (cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_)) && conn.first.in(ID::CLK, ID::C))
{
bool posedge = (cell->type == ID($_DFF_N_)) || (cell->type == ID($dff) && cell->getParam(ID::CLK_POLARITY).as_bool());
@@ -647,27 +695,35 @@ struct Smt2Worker
// FIXME: $slice $concat
}
- if (memmode && cell->type == ID($mem))
+ if (memmode && cell->is_mem_cell())
{
+ Mem *mem = mem_cells[cell];
+
+ if (memarrays.count(mem)) {
+ recursive_cells.erase(cell);
+ return;
+ }
+
int arrayid = idcounter++;
- memarrays[cell] = arrayid;
-
- int abits = cell->getParam(ID::ABITS).as_int();
- int width = cell->getParam(ID::WIDTH).as_int();
- int rd_ports = cell->getParam(ID::RD_PORTS).as_int();
- int wr_ports = cell->getParam(ID::WR_PORTS).as_int();
-
- bool async_read = false;
- if (!cell->getParam(ID::WR_CLK_ENABLE).is_fully_ones()) {
- if (!cell->getParam(ID::WR_CLK_ENABLE).is_fully_zero())
- log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module));
- async_read = true;
+ memarrays[mem] = arrayid;
+
+ int abits = ceil_log2(mem->size);
+
+ bool has_sync_wr = false;
+ bool has_async_wr = false;
+ for (auto &port : mem->wr_ports) {
+ if (port.clk_enable)
+ has_sync_wr = true;
+ else
+ has_async_wr = true;
}
+ if (has_async_wr && has_sync_wr)
+ log_error("Memory %s.%s has mixed clocked/nonclocked write ports. This is not supported by \"write_smt2\".\n", log_id(cell), log_id(module));
- decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(cell), abits, width, rd_ports, wr_ports, async_read ? "async" : "sync"));
+ decls.push_back(stringf("; yosys-smt2-memory %s %d %d %d %d %s\n", get_id(mem->memid), abits, mem->width, GetSize(mem->rd_ports), GetSize(mem->wr_ports), has_async_wr ? "async" : "sync"));
string memstate;
- if (async_read) {
+ if (has_async_wr) {
memstate = stringf("%s#%d#final", get_id(module), arrayid);
} else {
memstate = stringf("%s#%d#0", get_id(module), arrayid);
@@ -675,80 +731,79 @@ struct Smt2Worker
if (statebv)
{
- int mem_size = cell->getParam(ID::SIZE).as_int();
- int mem_offset = cell->getParam(ID::OFFSET).as_int();
-
- makebits(memstate, width*mem_size, get_id(cell));
+ makebits(memstate, mem->width*mem->size, get_id(mem->memid));
decls.push_back(stringf("(define-fun |%s_m %s| ((state |%s_s|)) (_ BitVec %d) (|%s| state))\n",
- get_id(module), get_id(cell), get_id(module), width*mem_size, memstate.c_str()));
+ get_id(module), get_id(mem->memid), get_id(module), mem->width*mem->size, memstate.c_str()));
- for (int i = 0; i < rd_ports; i++)
+ for (int i = 0; i < GetSize(mem->rd_ports); i++)
{
- SigSpec addr_sig = cell->getPort(ID::RD_ADDR).extract(abits*i, abits);
- SigSpec data_sig = cell->getPort(ID::RD_DATA).extract(width*i, width);
+ auto &port = mem->rd_ports[i];
+ SigSpec addr_sig = port.addr;
+ addr_sig.extend_u0(abits);
std::string addr = get_bv(addr_sig);
- if (cell->getParam(ID::RD_CLK_ENABLE).extract(i).as_bool())
+ if (port.clk_enable)
log_error("Read port %d (%s) of memory %s.%s is clocked. This is not supported by \"write_smt2\"! "
- "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(data_sig), log_id(cell), log_id(module));
+ "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(port.data), log_id(mem->memid), log_id(module));
decls.push_back(stringf("(define-fun |%s_m:R%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
+ get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
std::string read_expr = "#b";
- for (int k = 0; k < width; k++)
+ for (int k = 0; k < mem->width; k++)
read_expr += "0";
- for (int k = 0; k < mem_size; k++)
+ for (int k = 0; k < mem->size; k++)
read_expr = stringf("(ite (= (|%s_m:R%dA %s| state) #b%s) ((_ extract %d %d) (|%s| state))\n %s)",
- get_id(module), i, get_id(cell), Const(k+mem_offset, abits).as_string().c_str(),
- width*(k+1)-1, width*k, memstate.c_str(), read_expr.c_str());
+ get_id(module), i, get_id(mem->memid), Const(k+mem->start_offset, abits).as_string().c_str(),
+ mem->width*(k+1)-1, mem->width*k, memstate.c_str(), read_expr.c_str());
decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d)\n %s) ; %s\n",
- get_id(module), idcounter, get_id(module), width, read_expr.c_str(), log_signal(data_sig)));
+ get_id(module), idcounter, get_id(module), mem->width, read_expr.c_str(), log_signal(port.data)));
decls.push_back(stringf("(define-fun |%s_m:R%dD %s| ((state |%s_s|)) (_ BitVec %d) (|%s#%d| state))\n",
- get_id(module), i, get_id(cell), get_id(module), width, get_id(module), idcounter));
+ get_id(module), i, get_id(mem->memid), get_id(module), mem->width, get_id(module), idcounter));
- register_bv(data_sig, idcounter++);
+ register_bv(port.data, idcounter++);
}
}
else
{
if (statedt)
dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n",
- memstate.c_str(), abits, width, get_id(cell)));
+ memstate.c_str(), abits, mem->width, get_id(mem->memid)));
else
decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n",
- memstate.c_str(), get_id(module), abits, width, get_id(cell)));
+ memstate.c_str(), get_id(module), abits, mem->width, get_id(mem->memid)));
decls.push_back(stringf("(define-fun |%s_m %s| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) (|%s| state))\n",
- get_id(module), get_id(cell), get_id(module), abits, width, memstate.c_str()));
+ get_id(module), get_id(mem->memid), get_id(module), abits, mem->width, memstate.c_str()));
- for (int i = 0; i < rd_ports; i++)
+ for (int i = 0; i < GetSize(mem->rd_ports); i++)
{
- SigSpec addr_sig = cell->getPort(ID::RD_ADDR).extract(abits*i, abits);
- SigSpec data_sig = cell->getPort(ID::RD_DATA).extract(width*i, width);
+ auto &port = mem->rd_ports[i];
+ SigSpec addr_sig = port.addr;
+ addr_sig.extend_u0(abits);
std::string addr = get_bv(addr_sig);
- if (cell->getParam(ID::RD_CLK_ENABLE).extract(i).as_bool())
+ if (port.clk_enable)
log_error("Read port %d (%s) of memory %s.%s is clocked. This is not supported by \"write_smt2\"! "
- "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(data_sig), log_id(cell), log_id(module));
+ "Call \"memory\" with -nordff to avoid this error.\n", i, log_signal(port.data), log_id(mem->memid), log_id(module));
decls.push_back(stringf("(define-fun |%s_m:R%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
+ get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
decls.push_back(stringf("(define-fun |%s#%d| ((state |%s_s|)) (_ BitVec %d) (select (|%s| state) (|%s_m:R%dA %s| state))) ; %s\n",
- get_id(module), idcounter, get_id(module), width, memstate.c_str(), get_id(module), i, get_id(cell), log_signal(data_sig)));
+ get_id(module), idcounter, get_id(module), mem->width, memstate.c_str(), get_id(module), i, get_id(mem->memid), log_signal(port.data)));
decls.push_back(stringf("(define-fun |%s_m:R%dD %s| ((state |%s_s|)) (_ BitVec %d) (|%s#%d| state))\n",
- get_id(module), i, get_id(cell), get_id(module), width, get_id(module), idcounter));
+ get_id(module), i, get_id(mem->memid), get_id(module), mem->width, get_id(module), idcounter));
- register_bv(data_sig, idcounter++);
+ register_bv(port.data, idcounter++);
}
}
- registers.insert(cell);
+ memory_queue.insert(mem);
recursive_cells.erase(cell);
return;
}
@@ -801,6 +856,18 @@ struct Smt2Worker
return;
}
+ if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_smt2`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ if (cell->type.in(ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre)) || cell->type.str().substr(0, 5) == "$_DFF" || cell->type.str().substr(0, 7) == "$_ALDFF") {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `async2sync; dffunmap` or `clk2fflogic` before `write_smt2`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ if (cell->type.in(ID($sr), ID($dlatch), ID($adlatch), ID($dlatchsr)) || cell->type.str().substr(0, 8) == "$_DLATCH" || cell->type.str().substr(0, 5) == "$_SR_") {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `clk2fflogic` before `write_smt2`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
log_error("Unsupported cell type %s for cell %s.%s.\n",
log_id(cell->type), log_id(module), log_id(cell));
}
@@ -822,40 +889,51 @@ struct Smt2Worker
for (auto bit : SigSpec(wire))
if (reg_bits.count(bit))
is_register = true;
- if (wire->port_id || is_register || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name[0] == '\\')) {
+ if (wire->port_id || is_register || wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic())) {
RTLIL::SigSpec sig = sigmap(wire);
+ std::vector<std::string> comments;
if (wire->port_input)
- decls.push_back(stringf("; yosys-smt2-input %s %d\n", get_id(wire), wire->width));
+ comments.push_back(stringf("; yosys-smt2-input %s %d\n", get_id(wire), wire->width));
if (wire->port_output)
- decls.push_back(stringf("; yosys-smt2-output %s %d\n", get_id(wire), wire->width));
+ comments.push_back(stringf("; yosys-smt2-output %s %d\n", get_id(wire), wire->width));
if (is_register)
- decls.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width));
- if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name[0] == '\\'))
- decls.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width));
+ comments.push_back(stringf("; yosys-smt2-register %s %d\n", get_id(wire), wire->width));
+ if (wire->get_bool_attribute(ID::keep) || (wiresmode && wire->name.isPublic()))
+ comments.push_back(stringf("; yosys-smt2-wire %s %d\n", get_id(wire), wire->width));
if (GetSize(wire) == 1 && (clock_posedge.count(sig) || clock_negedge.count(sig)))
- decls.push_back(stringf("; yosys-smt2-clock %s%s%s\n", get_id(wire),
+ comments.push_back(stringf("; yosys-smt2-clock %s%s%s\n", get_id(wire),
clock_posedge.count(sig) ? " posedge" : "", clock_negedge.count(sig) ? " negedge" : ""));
if (bvmode && GetSize(sig) > 1) {
+ std::string sig_bv = get_bv(sig);
+ if (!comments.empty())
+ decls.insert(decls.end(), comments.begin(), comments.end());
decls.push_back(stringf("(define-fun |%s_n %s| ((state |%s_s|)) (_ BitVec %d) %s)\n",
- get_id(module), get_id(wire), get_id(module), GetSize(sig), get_bv(sig).c_str()));
+ get_id(module), get_id(wire), get_id(module), GetSize(sig), sig_bv.c_str()));
if (wire->port_input)
ex_input_eq.push_back(stringf(" (= (|%s_n %s| state) (|%s_n %s| other_state))",
get_id(module), get_id(wire), get_id(module), get_id(wire)));
} else {
- for (int i = 0; i < GetSize(sig); i++)
+ std::vector<std::string> sig_bool;
+ for (int i = 0; i < GetSize(sig); i++) {
+ sig_bool.push_back(get_bool(sig[i]));
+ }
+ if (!comments.empty())
+ decls.insert(decls.end(), comments.begin(), comments.end());
+ for (int i = 0; i < GetSize(sig); i++) {
if (GetSize(sig) > 1) {
decls.push_back(stringf("(define-fun |%s_n %s %d| ((state |%s_s|)) Bool %s)\n",
- get_id(module), get_id(wire), i, get_id(module), get_bool(sig[i]).c_str()));
+ get_id(module), get_id(wire), i, get_id(module), sig_bool[i].c_str()));
if (wire->port_input)
ex_input_eq.push_back(stringf(" (= (|%s_n %s %d| state) (|%s_n %s %d| other_state))",
get_id(module), get_id(wire), i, get_id(module), get_id(wire), i));
} else {
decls.push_back(stringf("(define-fun |%s_n %s| ((state |%s_s|)) Bool %s)\n",
- get_id(module), get_id(wire), get_id(module), get_bool(sig[i]).c_str()));
+ get_id(module), get_id(wire), get_id(module), sig_bool[i].c_str()));
if (wire->port_input)
ex_input_eq.push_back(stringf(" (= (|%s_n %s| state) (|%s_n %s| other_state))",
get_id(module), get_id(wire), get_id(module), get_id(wire)));
}
+ }
}
}
}
@@ -966,7 +1044,7 @@ struct Smt2Worker
}
}
- for (int iter = 1; !registers.empty(); iter++)
+ for (int iter = 1; !registers.empty() || !memory_queue.empty(); iter++)
{
pool<Cell*> this_regs;
this_regs.swap(registers);
@@ -999,152 +1077,156 @@ struct Smt2Worker
if (cell->type == ID($anyconst))
ex_state_eq.push_back(stringf("(= %s %s)", get_bv(cell->getPort(ID::Y)).c_str(), get_bv(cell->getPort(ID::Y), "other_state").c_str()));
}
+ }
- if (cell->type == ID($mem))
- {
- int arrayid = memarrays.at(cell);
+ std::set<Mem*> this_mems;
+ this_mems.swap(memory_queue);
+
+ for (auto mem : this_mems)
+ {
+ int arrayid = memarrays.at(mem);
- int abits = cell->getParam(ID::ABITS).as_int();
- int width = cell->getParam(ID::WIDTH).as_int();
- int wr_ports = cell->getParam(ID::WR_PORTS).as_int();
+ int abits = ceil_log2(mem->size);;
- bool async_read = false;
- string initial_memstate, final_memstate;
+ bool has_sync_wr = false;
+ bool has_async_wr = false;
+ for (auto &port : mem->wr_ports) {
+ if (port.clk_enable)
+ has_sync_wr = true;
+ else
+ has_async_wr = true;
+ }
- if (!cell->getParam(ID::WR_CLK_ENABLE).is_fully_ones()) {
- log_assert(cell->getParam(ID::WR_CLK_ENABLE).is_fully_zero());
- async_read = true;
- initial_memstate = stringf("%s#%d#0", get_id(module), arrayid);
- final_memstate = stringf("%s#%d#final", get_id(module), arrayid);
+ string initial_memstate, final_memstate;
+
+ if (has_async_wr) {
+ log_assert(!has_sync_wr);
+ initial_memstate = stringf("%s#%d#0", get_id(module), arrayid);
+ final_memstate = stringf("%s#%d#final", get_id(module), arrayid);
+ }
+
+ if (statebv)
+ {
+ if (has_async_wr) {
+ makebits(final_memstate, mem->width*mem->size, get_id(mem->memid));
}
- if (statebv)
+ for (int i = 0; i < GetSize(mem->wr_ports); i++)
{
- int mem_size = cell->getParam(ID::SIZE).as_int();
- int mem_offset = cell->getParam(ID::OFFSET).as_int();
-
- if (async_read) {
- makebits(final_memstate, width*mem_size, get_id(cell));
+ auto &port = mem->wr_ports[i];
+ SigSpec addr_sig = port.addr;
+ addr_sig.extend_u0(abits);
+
+ std::string addr = get_bv(addr_sig);
+ std::string data = get_bv(port.data);
+ std::string mask = get_bv(port.en);
+
+ decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
+ get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
+ addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(mem->memid));
+
+ decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
+ get_id(module), i, get_id(mem->memid), get_id(module), mem->width, data.c_str(), log_signal(port.data)));
+ data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(mem->memid));
+
+ decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
+ get_id(module), i, get_id(mem->memid), get_id(module), mem->width, mask.c_str(), log_signal(port.en)));
+ mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(mem->memid));
+
+ std::string data_expr;
+
+ for (int k = mem->size-1; k >= 0; k--) {
+ std::string new_data = stringf("(bvor (bvand %s %s) (bvand ((_ extract %d %d) (|%s#%d#%d| state)) (bvnot %s)))",
+ data.c_str(), mask.c_str(), mem->width*(k+1)-1, mem->width*k, get_id(module), arrayid, i, mask.c_str());
+ data_expr += stringf("\n (ite (= %s #b%s) %s ((_ extract %d %d) (|%s#%d#%d| state)))",
+ addr.c_str(), Const(k+mem->start_offset, abits).as_string().c_str(), new_data.c_str(),
+ mem->width*(k+1)-1, mem->width*k, get_id(module), arrayid, i);
}
- for (int i = 0; i < wr_ports; i++)
- {
- SigSpec addr_sig = cell->getPort(ID::WR_ADDR).extract(abits*i, abits);
- SigSpec data_sig = cell->getPort(ID::WR_DATA).extract(width*i, width);
- SigSpec mask_sig = cell->getPort(ID::WR_EN).extract(width*i, width);
+ decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (_ BitVec %d) (concat%s)) ; %s\n",
+ get_id(module), arrayid, i+1, get_id(module), mem->width*mem->size, data_expr.c_str(), get_id(mem->memid)));
+ }
+ }
+ else
+ {
+ if (has_async_wr) {
+ if (statedt)
+ dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n",
+ initial_memstate.c_str(), abits, mem->width, get_id(mem->memid)));
+ else
+ decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n",
+ initial_memstate.c_str(), get_id(module), abits, mem->width, get_id(mem->memid)));
+ }
- std::string addr = get_bv(addr_sig);
- std::string data = get_bv(data_sig);
- std::string mask = get_bv(mask_sig);
+ for (int i = 0; i < GetSize(mem->wr_ports); i++)
+ {
+ auto &port = mem->wr_ports[i];
+ SigSpec addr_sig = port.addr;
+ addr_sig.extend_u0(abits);
- decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
- addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(cell));
+ std::string addr = get_bv(addr_sig);
+ std::string data = get_bv(port.data);
+ std::string mask = get_bv(port.en);
- decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), width, data.c_str(), log_signal(data_sig)));
- data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(cell));
+ decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
+ get_id(module), i, get_id(mem->memid), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
+ addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(mem->memid));
- decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), width, mask.c_str(), log_signal(mask_sig)));
- mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(cell));
+ decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
+ get_id(module), i, get_id(mem->memid), get_id(module), mem->width, data.c_str(), log_signal(port.data)));
+ data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(mem->memid));
- std::string data_expr;
+ decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
+ get_id(module), i, get_id(mem->memid), get_id(module), mem->width, mask.c_str(), log_signal(port.en)));
+ mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(mem->memid));
- for (int k = mem_size-1; k >= 0; k--) {
- std::string new_data = stringf("(bvor (bvand %s %s) (bvand ((_ extract %d %d) (|%s#%d#%d| state)) (bvnot %s)))",
- data.c_str(), mask.c_str(), width*(k+1)-1, width*k, get_id(module), arrayid, i, mask.c_str());
- data_expr += stringf("\n (ite (= %s #b%s) %s ((_ extract %d %d) (|%s#%d#%d| state)))",
- addr.c_str(), Const(k+mem_offset, abits).as_string().c_str(), new_data.c_str(),
- width*(k+1)-1, width*k, get_id(module), arrayid, i);
- }
+ data = stringf("(bvor (bvand %s %s) (bvand (select (|%s#%d#%d| state) %s) (bvnot %s)))",
+ data.c_str(), mask.c_str(), get_id(module), arrayid, i, addr.c_str(), mask.c_str());
- decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (_ BitVec %d) (concat%s)) ; %s\n",
- get_id(module), arrayid, i+1, get_id(module), width*mem_size, data_expr.c_str(), get_id(cell)));
- }
+ decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) "
+ "(store (|%s#%d#%d| state) %s %s)) ; %s\n",
+ get_id(module), arrayid, i+1, get_id(module), abits, mem->width,
+ get_id(module), arrayid, i, addr.c_str(), data.c_str(), get_id(mem->memid)));
}
- else
- {
- if (async_read) {
- if (statedt)
- dtmembers.push_back(stringf(" (|%s| (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n",
- initial_memstate.c_str(), abits, width, get_id(cell)));
- else
- decls.push_back(stringf("(declare-fun |%s| (|%s_s|) (Array (_ BitVec %d) (_ BitVec %d))) ; %s\n",
- initial_memstate.c_str(), get_id(module), abits, width, get_id(cell)));
- }
-
- for (int i = 0; i < wr_ports; i++)
- {
- SigSpec addr_sig = cell->getPort(ID::WR_ADDR).extract(abits*i, abits);
- SigSpec data_sig = cell->getPort(ID::WR_DATA).extract(width*i, width);
- SigSpec mask_sig = cell->getPort(ID::WR_EN).extract(width*i, width);
+ }
- std::string addr = get_bv(addr_sig);
- std::string data = get_bv(data_sig);
- std::string mask = get_bv(mask_sig);
+ std::string expr_d = stringf("(|%s#%d#%d| state)", get_id(module), arrayid, GetSize(mem->wr_ports));
+ std::string expr_q = stringf("(|%s#%d#0| next_state)", get_id(module), arrayid);
+ trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d.c_str(), expr_q.c_str(), get_id(mem->memid)));
+ ex_state_eq.push_back(stringf("(= (|%s#%d#0| state) (|%s#%d#0| other_state))", get_id(module), arrayid, get_id(module), arrayid));
- decls.push_back(stringf("(define-fun |%s_m:W%dA %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), abits, addr.c_str(), log_signal(addr_sig)));
- addr = stringf("(|%s_m:W%dA %s| state)", get_id(module), i, get_id(cell));
+ if (has_async_wr)
+ hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d.c_str(), final_memstate.c_str(), get_id(mem->memid)));
- decls.push_back(stringf("(define-fun |%s_m:W%dD %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), width, data.c_str(), log_signal(data_sig)));
- data = stringf("(|%s_m:W%dD %s| state)", get_id(module), i, get_id(cell));
+ Const init_data = mem->get_init_data();
- decls.push_back(stringf("(define-fun |%s_m:W%dM %s| ((state |%s_s|)) (_ BitVec %d) %s) ; %s\n",
- get_id(module), i, get_id(cell), get_id(module), width, mask.c_str(), log_signal(mask_sig)));
- mask = stringf("(|%s_m:W%dM %s| state)", get_id(module), i, get_id(cell));
+ for (int i = 0; i < mem->size; i++)
+ {
+ if (i*mem->width >= GetSize(init_data))
+ break;
- data = stringf("(bvor (bvand %s %s) (bvand (select (|%s#%d#%d| state) %s) (bvnot %s)))",
- data.c_str(), mask.c_str(), get_id(module), arrayid, i, addr.c_str(), mask.c_str());
+ Const initword = init_data.extract(i*mem->width, mem->width, State::Sx);
+ Const initmask = initword;
+ bool gen_init_constr = false;
- decls.push_back(stringf("(define-fun |%s#%d#%d| ((state |%s_s|)) (Array (_ BitVec %d) (_ BitVec %d)) "
- "(store (|%s#%d#%d| state) %s %s)) ; %s\n",
- get_id(module), arrayid, i+1, get_id(module), abits, width,
- get_id(module), arrayid, i, addr.c_str(), data.c_str(), get_id(cell)));
+ for (int k = 0; k < GetSize(initword); k++) {
+ if (initword[k] == State::S0 || initword[k] == State::S1) {
+ gen_init_constr = true;
+ initmask[k] = State::S1;
+ } else {
+ initmask[k] = State::S0;
+ initword[k] = State::S0;
}
}
- std::string expr_d = stringf("(|%s#%d#%d| state)", get_id(module), arrayid, wr_ports);
- std::string expr_q = stringf("(|%s#%d#0| next_state)", get_id(module), arrayid);
- trans.push_back(stringf(" (= %s %s) ; %s\n", expr_d.c_str(), expr_q.c_str(), get_id(cell)));
- ex_state_eq.push_back(stringf("(= (|%s#%d#0| state) (|%s#%d#0| other_state))", get_id(module), arrayid, get_id(module), arrayid));
-
- if (async_read)
- hier.push_back(stringf(" (= %s (|%s| state)) ; %s\n", expr_d.c_str(), final_memstate.c_str(), get_id(cell)));
-
- Const init_data = cell->getParam(ID::INIT);
- int memsize = cell->getParam(ID::SIZE).as_int();
-
- for (int i = 0; i < memsize; i++)
+ if (gen_init_constr)
{
- if (i*width >= GetSize(init_data))
- break;
-
- Const initword = init_data.extract(i*width, width, State::Sx);
- Const initmask = initword;
- bool gen_init_constr = false;
-
- for (int k = 0; k < GetSize(initword); k++) {
- if (initword[k] == State::S0 || initword[k] == State::S1) {
- gen_init_constr = true;
- initmask[k] = State::S1;
- } else {
- initmask[k] = State::S0;
- initword[k] = State::S0;
- }
- }
-
- if (gen_init_constr)
- {
- if (statebv)
- /* FIXME */;
- else
- init_list.push_back(stringf("(= (bvand (select (|%s#%d#0| state) #b%s) #b%s) #b%s) ; %s[%d]",
- get_id(module), arrayid, Const(i, abits).as_string().c_str(),
- initmask.as_string().c_str(), initword.as_string().c_str(), get_id(cell), i));
- }
+ if (statebv)
+ /* FIXME */;
+ else
+ init_list.push_back(stringf("(= (bvand (select (|%s#%d#0| state) #b%s) #b%s) #b%s) ; %s[%d]",
+ get_id(module), arrayid, Const(i, abits).as_string().c_str(),
+ initmask.as_string().c_str(), initword.as_string().c_str(), get_id(mem->memid), i));
}
}
}
@@ -1387,8 +1469,12 @@ struct Smt2Backend : public Backend {
log(" use the given template file. the line containing only the token '%%%%'\n");
log(" is replaced with the regular output of this command.\n");
log("\n");
+ log(" -solver-option <option> <value>\n");
+ log(" emit a `; yosys-smt2-solver-option` directive for yosys-smtbmc to write\n");
+ log(" the given option as a `(set-option ...)` command in the SMT-LIBv2.\n");
+ log("\n");
log("[1] For more information on SMT-LIBv2 visit http://smt-lib.org/ or read David\n");
- log("R. Cok's tutorial: http://www.grammatech.com/resources/smt/SMTLIBTutorial.pdf\n");
+ log("R. Cok's tutorial: https://smtlib.github.io/jSMTLIB/SMTLIBTutorial.pdf\n");
log("\n");
log("---------------------------------------------------------------------------\n");
log("\n");
@@ -1441,9 +1527,15 @@ struct Smt2Backend : public Backend {
std::ifstream template_f;
bool bvmode = true, memmode = true, wiresmode = false, verbose = false, statebv = false, statedt = false;
bool forallmode = false;
+ dict<std::string, std::string> solver_options;
log_header(design, "Executing SMT2 backend.\n");
+ log_push();
+ Pass::call(design, "bmuxmap");
+ Pass::call(design, "demuxmap");
+ log_pop();
+
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
@@ -1484,6 +1576,11 @@ struct Smt2Backend : public Backend {
verbose = true;
continue;
}
+ if (args[argidx] == "-solver-option" && argidx+2 < args.size()) {
+ solver_options.emplace(args[argidx+1], args[argidx+2]);
+ argidx += 2;
+ continue;
+ }
break;
}
extra_args(f, filename, args, argidx);
@@ -1514,6 +1611,9 @@ struct Smt2Backend : public Backend {
if (statedt)
*f << stringf("; yosys-smt2-stdt\n");
+ for (auto &it : solver_options)
+ *f << stringf("; yosys-smt2-solver-option %s %s\n", it.first.c_str(), it.second.c_str());
+
std::vector<RTLIL::Module*> sorted_modules;
// extract module dependencies
@@ -1562,7 +1662,7 @@ struct Smt2Backend : public Backend {
for (auto module : sorted_modules)
{
- if (module->get_blackbox_attribute() || module->has_memories_warn() || module->has_processes_warn())
+ if (module->get_blackbox_attribute() || module->has_processes_warn())
continue;
log("Creating SMT-LIBv2 representation of module %s.\n", log_id(module));
diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py
index 03f001bfd..e5cfcdc08 100644
--- a/backends/smt2/smtbmc.py
+++ b/backends/smt2/smtbmc.py
@@ -2,7 +2,7 @@
#
# yosys -- Yosys Open SYnthesis Suite
#
-# Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+# Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -771,12 +771,12 @@ def write_vcd_trace(steps_start, steps_stop, index):
if gotread:
buf = data[:]
- for i in reversed(range(len(tdata))):
+ for ii in reversed(range(len(tdata))):
for k in range(width):
- if tdata[i][k] == "x":
- tdata[i][k] = buf[k]
+ if tdata[ii][k] == "x":
+ tdata[ii][k] = buf[k]
else:
- buf[k] = tdata[i][k]
+ buf[k] = tdata[ii][k]
if not asyncwr:
tdata.append(data[:])
@@ -817,6 +817,24 @@ def write_vcd_trace(steps_start, steps_stop, index):
vcd.set_time(steps_stop)
+def char_ok_in_verilog(c,i):
+ if ('A' <= c <= 'Z'): return True
+ if ('a' <= c <= 'z'): return True
+ if ('0' <= c <= '9' and i>0): return True
+ if (c == '_'): return True
+ if (c == '$'): return True
+ return False
+
+def escape_identifier(identifier):
+ if type(identifier) is list:
+ return map(escape_identifier, identifier)
+ if "." in identifier:
+ return ".".join(escape_identifier(identifier.split(".")))
+ if (all(char_ok_in_verilog(identifier[i],i) for i in range(0, len(identifier)))):
+ return identifier
+ return "\\"+identifier+" "
+
+
def write_vlogtb_trace(steps_start, steps_stop, index):
filename = vlogtbfile.replace("%", index)
@@ -858,12 +876,12 @@ def write_vlogtb_trace(steps_start, steps_stop, index):
for name, width in primary_inputs:
if name in clock_inputs:
- print(" wire [%d:0] PI_%s = clock;" % (width-1, name), file=f)
+ print(" wire [%d:0] %s = clock;" % (width-1, escape_identifier("PI_"+name)), file=f)
else:
- print(" reg [%d:0] PI_%s;" % (width-1, name), file=f)
+ print(" reg [%d:0] %s;" % (width-1, escape_identifier("PI_"+name)), file=f)
- print(" %s UUT (" % vlogtb_topmod, file=f)
- print(",\n".join(" .{name}(PI_{name})".format(name=name) for name, _ in primary_inputs), file=f)
+ print(" %s UUT (" % escape_identifier(vlogtb_topmod), file=f)
+ print(",\n".join(" .%s(%s)" % (escape_identifier(name), escape_identifier("PI_"+name)) for name, _ in primary_inputs), file=f)
print(" );", file=f)
print("`ifndef VERILATOR", file=f)
@@ -893,14 +911,14 @@ def write_vlogtb_trace(steps_start, steps_stop, index):
for n in reg:
if n.startswith("$"):
hidden_net = True
- print(" %sUUT.%s = %d'b%s;" % ("// " if hidden_net else "", ".".join(reg), len(val), val), file=f)
+ print(" %sUUT.%s = %d'b%s;" % ("// " if hidden_net else "", ".".join(escape_identifier(reg)), len(val), val), file=f)
anyconsts = sorted(smt.hieranyconsts(vlogtb_topmod))
for info in anyconsts:
if info[3] is not None:
modstate = smt.net_expr(vlogtb_topmod, vlogtb_state.replace("@@step_idx@@", str(steps_start)), info[0])
value = smt.bv2bin(smt.get("(|%s| %s)" % (info[1], modstate)))
- print(" UUT.%s = %d'b%s;" % (".".join(info[0] + [info[3]]), len(value), value), file=f);
+ print(" UUT.%s = %d'b%s;" % (".".join(escape_identifier(info[0] + [info[3]])), len(value), value), file=f);
mems = sorted(smt.hiermems(vlogtb_topmod))
for mempath in mems:
@@ -924,7 +942,7 @@ def write_vlogtb_trace(steps_start, steps_stop, index):
addr_data[addr] = data
for addr, data in addr_data.items():
- print(" UUT.%s[%d'b%s] = %d'b%s;" % (".".join(mempath), len(addr), addr, len(data), data), file=f)
+ print(" UUT.%s[%d'b%s] = %d'b%s;" % (".".join(escape_identifier(mempath)), len(addr), addr, len(data), data), file=f)
print("", file=f)
anyseqs = sorted(smt.hieranyseqs(vlogtb_topmod))
@@ -940,18 +958,18 @@ def write_vlogtb_trace(steps_start, steps_stop, index):
for name, val in zip(pi_names, pi_values):
if i > 0:
- print(" PI_%s <= %d'b%s;" % (".".join(name), len(val), val), file=f)
+ print(" %s <= %d'b%s;" % (escape_identifier("PI_"+".".join(name)), len(val), val), file=f)
else:
- print(" PI_%s = %d'b%s;" % (".".join(name), len(val), val), file=f)
+ print(" %s = %d'b%s;" % (escape_identifier("PI_"+".".join(name)), len(val), val), file=f)
for info in anyseqs:
if info[3] is not None:
modstate = smt.net_expr(vlogtb_topmod, vlogtb_state.replace("@@step_idx@@", str(i)), info[0])
value = smt.bv2bin(smt.get("(|%s| %s)" % (info[1], modstate)))
if i > 0:
- print(" UUT.%s <= %d'b%s;" % (".".join(info[0] + [info[3]]), len(value), value), file=f);
+ print(" UUT.%s <= %d'b%s;" % (".".join(escape_identifier(info[0] + [info[3]])), len(value), value), file=f);
else:
- print(" UUT.%s = %d'b%s;" % (".".join(info[0] + [info[3]]), len(value), value), file=f);
+ print(" UUT.%s = %d'b%s;" % (".".join(escape_identifier(info[0] + [info[3]])), len(value), value), file=f);
if i > 0:
print(" end", file=f)
@@ -1275,10 +1293,10 @@ def smt_pop():
asserts_consequent_cache.pop()
smt.write("(pop 1)")
-def smt_check_sat():
+def smt_check_sat(expected=["sat", "unsat"]):
if asserts_cache_dirty:
smt_forall_assert()
- return smt.check_sat()
+ return smt.check_sat(expected=expected)
if tempind:
retstatus = "FAILED"
diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py
index 72ab39d39..d73a875ba 100644
--- a/backends/smt2/smtio.py
+++ b/backends/smt2/smtio.py
@@ -1,7 +1,7 @@
#
# yosys -- Yosys Open SYnthesis Suite
#
-# Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+# Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -124,6 +124,7 @@ class SmtIo:
self.timeout = 0
self.produce_models = True
self.smt2cache = [list()]
+ self.smt2_options = dict()
self.p = None
self.p_index = solvers_index
solvers_index += 1
@@ -202,14 +203,14 @@ class SmtIo:
print('timeout option is not supported for mathsat.')
sys.exit(1)
- if self.solver == "boolector":
+ if self.solver in ["boolector", "bitwuzla"]:
if self.noincr:
- self.popen_vargs = ['boolector', '--smt2'] + self.solver_opts
+ self.popen_vargs = [self.solver, '--smt2'] + self.solver_opts
else:
- self.popen_vargs = ['boolector', '--smt2', '-i'] + self.solver_opts
+ self.popen_vargs = [self.solver, '--smt2', '-i'] + self.solver_opts
self.unroll = True
if self.timeout != 0:
- print('timeout option is not supported for boolector.')
+ print('timeout option is not supported for %s.' % self.solver)
sys.exit(1)
if self.solver == "abc":
@@ -258,14 +259,24 @@ class SmtIo:
for stmt in self.info_stmts:
self.write(stmt)
- if self.forall and self.solver == "yices":
- self.write("(set-option :yices-ef-max-iters 1000000000)")
-
if self.produce_models:
self.write("(set-option :produce-models true)")
+ #See the SMT-LIB Standard, Section 4.1.7
+ modestart_options = [":global-declarations", ":interactive-mode", ":produce-assertions", ":produce-assignments", ":produce-models", ":produce-proofs", ":produce-unsat-assumptions", ":produce-unsat-cores", ":random-seed"]
+ for key, val in self.smt2_options.items():
+ if key in modestart_options:
+ self.write("(set-option {} {})".format(key, val))
+
self.write("(set-logic %s)" % self.logic)
+ if self.forall and self.solver == "yices":
+ self.write("(set-option :yices-ef-max-iters 1000000000)")
+
+ for key, val in self.smt2_options.items():
+ if key not in modestart_options:
+ self.write("(set-option {} {})".format(key, val))
+
def timestamp(self):
secs = int(time() - self.start_time)
return "## %3d:%02d:%02d " % (secs // (60*60), (secs // 60) % 60, secs % 60)
@@ -468,6 +479,9 @@ class SmtIo:
fields = stmt.split()
+ if fields[1] == "yosys-smt2-solver-option":
+ self.smt2_options[fields[2]] = fields[3]
+
if fields[1] == "yosys-smt2-nomem":
if self.logic is None:
self.logic_ax = False
@@ -653,7 +667,7 @@ class SmtIo:
return stmt
- def check_sat(self):
+ def check_sat(self, expected=["sat", "unsat", "unknown", "timeout", "interrupted"]):
if self.debug_print:
print("> (check-sat)")
if self.debug_file and not self.nocomments:
@@ -740,7 +754,7 @@ class SmtIo:
print("(check-sat)", file=self.debug_file)
self.debug_file.flush()
- if result not in ["sat", "unsat", "unknown", "timeout", "interrupted"]:
+ if result not in expected:
if result == "":
print("%s Unexpected EOF response from solver." % (self.timestamp()), flush=True)
else:
@@ -996,7 +1010,7 @@ class SmtOpts:
def helpmsg(self):
return """
-s <solver>
- set SMT solver: z3, yices, boolector, cvc4, mathsat, dummy
+ set SMT solver: z3, yices, boolector, bitwuzla, cvc4, mathsat, dummy
default: yices
-S <opt>
diff --git a/backends/smv/.gitignore b/backends/smv/.gitignore
new file mode 100644
index 000000000..d23d492d7
--- /dev/null
+++ b/backends/smv/.gitignore
@@ -0,0 +1 @@
+/test_cells.tmp/
diff --git a/backends/smv/smv.cc b/backends/smv/smv.cc
index 4e5c6050d..7d4f94adc 100644
--- a/backends/smv/smv.cc
+++ b/backends/smv/smv.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -573,8 +573,22 @@ struct SmvWorker
continue;
}
- if (cell->type[0] == '$')
- log_error("Found currently unsupported cell type %s (%s.%s).\n", log_id(cell->type), log_id(module), log_id(cell));
+ if (cell->type[0] == '$') {
+ if (cell->type.in(ID($dffe), ID($sdff), ID($sdffe), ID($sdffce)) || cell->type.str().substr(0, 6) == "$_SDFF" || (cell->type.str().substr(0, 6) == "$_DFFE" && cell->type.str().size() == 10)) {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `dffunmap` before `write_smv`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ if (cell->type.in(ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($dffsr), ID($dffsre)) || cell->type.str().substr(0, 5) == "$_DFF" || cell->type.str().substr(0, 7) == "$_ALDFF") {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `async2sync; dffunmap` or `clk2fflogic` before `write_smv`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ if (cell->type.in(ID($sr), ID($dlatch), ID($adlatch), ID($dlatchsr)) || cell->type.str().substr(0, 8) == "$_DLATCH" || cell->type.str().substr(0, 5) == "$_SR_") {
+ log_error("Unsupported cell type %s for cell %s.%s -- please run `clk2fflogic` before `write_smv`.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
+ log_error("Unsupported cell type %s for cell %s.%s.\n",
+ log_id(cell->type), log_id(module), log_id(cell));
+ }
// f << stringf(" %s : %s;\n", cid(cell->name), cid(cell->type));
@@ -727,6 +741,11 @@ struct SmvBackend : public Backend {
log_header(design, "Executing SMV backend.\n");
+ log_push();
+ Pass::call(design, "bmuxmap");
+ Pass::call(design, "demuxmap");
+ log_pop();
+
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
diff --git a/backends/spice/spice.cc b/backends/spice/spice.cc
index aa20f106a..f260276eb 100644
--- a/backends/spice/spice.cc
+++ b/backends/spice/spice.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -64,7 +64,7 @@ static void print_spice_net(std::ostream &f, RTLIL::SigBit s, std::string &neg,
}
}
-static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::Design *design, std::string &neg, std::string &pos, std::string &ncpf, bool big_endian, bool use_inames)
+static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::Design *design, std::string &neg, std::string &pos, std::string &buf, std::string &ncpf, bool big_endian, bool use_inames)
{
SigMap sigmap(module);
idict<IdString, 1> inums;
@@ -121,10 +121,10 @@ static void print_spice_module(std::ostream &f, RTLIL::Module *module, RTLIL::De
for (auto &conn : module->connections())
for (int i = 0; i < conn.first.size(); i++) {
- f << stringf("V%d", conn_counter++);
- print_spice_net(f, conn.first.extract(i, 1), neg, pos, ncpf, nc_counter, use_inames, inums);
+ f << (buf == "DC" ? stringf("V%d", conn_counter++) : stringf("X%d", cell_counter++));
print_spice_net(f, conn.second.extract(i, 1), neg, pos, ncpf, nc_counter, use_inames, inums);
- f << stringf(" DC 0\n");
+ print_spice_net(f, conn.first.extract(i, 1), neg, pos, ncpf, nc_counter, use_inames, inums);
+ f << (buf == "DC" ? " DC 0\n" : stringf(" %s\n", buf.c_str()));
}
}
@@ -148,6 +148,10 @@ struct SpiceBackend : public Backend {
log(" -pos net_name\n");
log(" set the net name for constant 1 (default: Vdd)\n");
log("\n");
+ log(" -buf DC|subckt_name\n");
+ log(" set the name for jumper element (default: DC)\n");
+ log(" (used to connect different nets)\n");
+ log("\n");
log(" -nc_prefix\n");
log(" prefix for not-connected nets (default: _NC)\n");
log("\n");
@@ -164,7 +168,7 @@ struct SpiceBackend : public Backend {
std::string top_module_name;
RTLIL::Module *top_module = NULL;
bool big_endian = false, use_inames = false;
- std::string neg = "Vss", pos = "Vdd", ncpf = "_NC";
+ std::string neg = "Vss", pos = "Vdd", ncpf = "_NC", buf = "DC";
log_header(design, "Executing SPICE backend.\n");
@@ -187,6 +191,10 @@ struct SpiceBackend : public Backend {
pos = args[++argidx];
continue;
}
+ if (args[argidx] == "-buf" && argidx+1 < args.size()) {
+ buf = args[++argidx];
+ continue;
+ }
if (args[argidx] == "-nc_prefix" && argidx+1 < args.size()) {
ncpf = args[++argidx];
continue;
@@ -241,14 +249,14 @@ struct SpiceBackend : public Backend {
*f << stringf(" %s", spice_id2str(wire->name).c_str());
}
*f << stringf("\n");
- print_spice_module(*f, module, design, neg, pos, ncpf, big_endian, use_inames);
+ print_spice_module(*f, module, design, neg, pos, buf, ncpf, big_endian, use_inames);
*f << stringf(".ENDS %s\n\n", spice_id2str(module->name).c_str());
}
if (!top_module_name.empty()) {
if (top_module == NULL)
log_error("Can't find top module `%s'!\n", top_module_name.c_str());
- print_spice_module(*f, top_module, design, neg, pos, ncpf, big_endian, use_inames);
+ print_spice_module(*f, top_module, design, neg, pos, buf, ncpf, big_endian, use_inames);
*f << stringf("\n");
}
diff --git a/backends/table/table.cc b/backends/table/table.cc
index 77642ccbd..2bf64e7b1 100644
--- a/backends/table/table.cc
+++ b/backends/table/table.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc
index cef1dd9df..aa1d4558c 100644
--- a/backends/verilog/verilog_backend.cc
+++ b/backends/verilog/verilog_backend.cc
@@ -1,7 +1,7 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
- * Copyright (C) 2012 Clifford Wolf <clifford@clifford.at>
+ * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
@@ -25,6 +25,8 @@
#include "kernel/celltypes.h"
#include "kernel/log.h"
#include "kernel/sigtools.h"
+#include "kernel/ff.h"
+#include "kernel/mem.h"
#include <string>
#include <sstream>
#include <set>
@@ -33,15 +35,16 @@
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
-bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit;
+bool verbose, norename, noattr, attr2comment, noexpr, nodec, nohex, nostr, extmem, defparam, decimal, siminit, systemverilog, simple_lhs;
int auto_name_counter, auto_name_offset, auto_name_digits, extmem_counter;
std::map<RTLIL::IdString, int> auto_name_map;
-std::set<RTLIL::IdString> reg_wires, reg_ct;
+std::set<RTLIL::IdString> reg_wires;
std::string auto_prefix, extmem_prefix;
RTLIL::Module *active_module;
dict<RTLIL::SigBit, RTLIL::State> active_initdata;
SigMap active_sigmap;
+IdString initial_id;
void reset_auto_counter_id(RTLIL::IdString id, bool may_rename)
{
@@ -355,7 +358,8 @@ void dump_sigchunk(std::ostream &f, const RTLIL::SigChunk &chunk, bool no_decima
void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig)
{
if (GetSize(sig) == 0) {
- f << "\"\"";
+ // See IEEE 1364-2005 Clause 5.1.14.
+ f << "{0{1'b0}}";
return;
}
if (sig.is_chunk()) {
@@ -428,15 +432,509 @@ void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire)
dump_const(f, wire->attributes.at(ID::init));
}
f << stringf(";\n");
- } else if (!wire->port_input && !wire->port_output)
+ } else
f << stringf("%s" "wire%s %s;\n", indent.c_str(), range.c_str(), id(wire->name).c_str());
#endif
}
-void dump_memory(std::ostream &f, std::string indent, RTLIL::Memory *memory)
+void dump_memory(std::ostream &f, std::string indent, Mem &mem)
{
- dump_attributes(f, indent, memory->attributes);
- f << stringf("%s" "reg [%d:0] %s [%d:%d];\n", indent.c_str(), memory->width-1, id(memory->name).c_str(), memory->size+memory->start_offset-1, memory->start_offset);
+ std::string mem_id = id(mem.memid);
+
+ dump_attributes(f, indent, mem.attributes);
+ f << stringf("%s" "reg [%d:0] %s [%d:%d];\n", indent.c_str(), mem.width-1, mem_id.c_str(), mem.size+mem.start_offset-1, mem.start_offset);
+
+ // for memory block make something like:
+ // reg [7:0] memid [3:0];
+ // initial begin
+ // memid[0] = ...
+ // end
+ if (!mem.inits.empty())
+ {
+ if (extmem)
+ {
+ std::string extmem_filename = stringf("%s-%d.mem", extmem_prefix.c_str(), extmem_counter++);
+
+ std::string extmem_filename_esc;
+ for (auto c : extmem_filename)
+ {
+ if (c == '\n')
+ extmem_filename_esc += "\\n";
+ else if (c == '\t')
+ extmem_filename_esc += "\\t";
+ else if (c < 32)
+ extmem_filename_esc += stringf("\\%03o", c);
+ else if (c == '"')
+ extmem_filename_esc += "\\\"";
+ else if (c == '\\')
+ extmem_filename_esc += "\\\\";
+ else
+ extmem_filename_esc += c;
+ }
+ f << stringf("%s" "initial $readmemb(\"%s\", %s);\n", indent.c_str(), extmem_filename_esc.c_str(), mem_id.c_str());
+
+ std::ofstream extmem_f(extmem_filename, std::ofstream::trunc);
+ if (extmem_f.fail())
+ log_error("Can't open file `%s' for writing: %s\n", extmem_filename.c_str(), strerror(errno));
+ else
+ {
+ Const data = mem.get_init_data();
+ for (int i=0; i<mem.size; i++)
+ {
+ RTLIL::Const element = data.extract(i*mem.width, mem.width);
+ for (int j=0; j<element.size(); j++)
+ {
+ switch (element[element.size()-j-1])
+ {
+ case State::S0: extmem_f << '0'; break;
+ case State::S1: extmem_f << '1'; break;
+ case State::Sx: extmem_f << 'x'; break;
+ case State::Sz: extmem_f << 'z'; break;
+ case State::Sa: extmem_f << '_'; break;
+ case State::Sm: log_error("Found marker state in final netlist.");
+ }
+ }
+ extmem_f << '\n';
+ }
+ }
+ }
+ else
+ {
+ f << stringf("%s" "initial begin\n", indent.c_str());
+ for (auto &init : mem.inits) {
+ int words = GetSize(init.data) / mem.width;
+ int start = init.addr.as_int();
+ for (int i=0; i<words; i++)
+ {
+ for (int j = 0; j < mem.width; j++)
+ {
+ if (init.en[j] != State::S1)
+ continue;
+
+ int start_j = j, width = 1;
+
+ while (j+1 < mem.width && init.en[j+1] == State::S1)
+ j++, width++;
+
+ if (width == mem.width) {
+ f << stringf("%s" " %s[%d] = ", indent.c_str(), mem_id.c_str(), i + start);
+ } else {
+ f << stringf("%s" " %s[%d][%d:%d] = ", indent.c_str(), mem_id.c_str(), i + start, j, start_j);
+ }
+ dump_const(f, init.data.extract(i*mem.width+start_j, width));
+ f << stringf(";\n");
+ }
+ }
+ }
+ f << stringf("%s" "end\n", indent.c_str());
+ }
+ }
+
+ // create a map : "edge clk" -> expressions within that clock domain
+ dict<std::string, std::vector<std::string>> clk_to_lof_body;
+ dict<std::string, std::string> clk_to_arst_cond;
+ dict<std::string, std::vector<std::string>> clk_to_arst_body;
+ clk_to_lof_body[""] = std::vector<std::string>();
+ std::string clk_domain_str;
+ // create a list of reg declarations
+ std::vector<std::string> lof_reg_declarations;
+
+ // read ports
+ for (auto &port : mem.rd_ports)
+ {
+ if (port.clk_enable)
+ {
+ {
+ std::ostringstream os;
+ dump_sigspec(os, port.clk);
+ clk_domain_str = stringf("%sedge %s", port.clk_polarity ? "pos" : "neg", os.str().c_str());
+ if (port.arst != State::S0) {
+ std::ostringstream os2;
+ dump_sigspec(os2, port.arst);
+ clk_domain_str += stringf(", posedge %s", os2.str().c_str());
+ clk_to_arst_cond[clk_domain_str] = os2.str();
+ }
+ }
+
+ // Decide how to represent the transparency; same idea as Mem::extract_rdff.
+ bool trans_use_addr = true;
+ for (auto bit : port.transparency_mask)
+ if (!bit)
+ trans_use_addr = false;
+
+ if (GetSize(mem.wr_ports) == 0)
+ trans_use_addr = false;
+
+ if (port.en != State::S1 || port.srst != State::S0 || port.arst != State::S0 || !port.init_value.is_fully_undef())
+ trans_use_addr = false;
+
+ if (!trans_use_addr)
+ {
+ // for clocked read ports make something like:
+ // reg [..] temp_id;
+ // always @(posedge clk)
+ // if (rd_en) temp_id <= array_reg[r_addr];
+ // assign r_data = temp_id;
+ std::string temp_id = next_auto_id();
+ lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", port.data.size() - 1, temp_id.c_str()) );
+
+ bool has_indent = false;
+
+ if (port.arst != State::S0) {
+ std::ostringstream os;
+ os << stringf("%s <= ", temp_id.c_str());
+ dump_sigspec(os, port.arst_value);
+ os << ";\n";
+ clk_to_arst_body[clk_domain_str].push_back(os.str());
+ }
+
+ if (port.srst != State::S0 && !port.ce_over_srst) {
+ std::ostringstream os;
+ os << stringf("if (");
+ dump_sigspec(os, port.srst);
+ os << stringf(")\n");
+ clk_to_lof_body[clk_domain_str].push_back(os.str());
+ std::ostringstream os2;
+ os2 << stringf("%s" "%s <= ", indent.c_str(), temp_id.c_str());
+ dump_sigspec(os2, port.srst_value);
+ os2 << ";\n";
+ clk_to_lof_body[clk_domain_str].push_back(os2.str());
+ std::ostringstream os3;
+ if (port.en == State::S1) {
+ os3 << "else begin\n";
+ } else {
+ os3 << "else if (";
+ dump_sigspec(os3, port.en);
+ os3 << ") begin\n";
+ }
+ clk_to_lof_body[clk_domain_str].push_back(os3.str());
+ has_indent = true;
+ } else if (port.en != State::S1) {
+ std::ostringstream os;
+ os << stringf("if (");
+ dump_sigspec(os, port.en);
+ os << stringf(") begin\n");
+ clk_to_lof_body[clk_domain_str].push_back(os.str());
+ has_indent = true;
+ }
+
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++)
+ {
+ SigSpec addr = port.sub_addr(sub);
+ std::ostringstream os;
+ if (has_indent)
+ os << indent;
+ os << temp_id;
+ if (port.wide_log2)
+ os << stringf("[%d:%d]", (sub + 1) * mem.width - 1, sub * mem.width);
+ os << stringf(" <= %s[", mem_id.c_str());
+ dump_sigspec(os, addr);
+ os << stringf("];\n");
+ clk_to_lof_body[clk_domain_str].push_back(os.str());
+ }
+
+ for (int i = 0; i < GetSize(mem.wr_ports); i++) {
+ auto &wport = mem.wr_ports[i];
+ if (!port.transparency_mask[i] && !port.collision_x_mask[i])
+ continue;
+ int min_wide_log2 = std::min(port.wide_log2, wport.wide_log2);
+ int max_wide_log2 = std::max(port.wide_log2, wport.wide_log2);
+ bool wide_write = wport.wide_log2 > port.wide_log2;
+ for (int sub = 0; sub < (1 << max_wide_log2); sub += (1 << min_wide_log2)) {
+ SigSpec raddr = port.addr;
+ SigSpec waddr = wport.addr;
+ if (wide_write)
+ waddr = wport.sub_addr(sub);
+ else
+ raddr = port.sub_addr(sub);
+ int pos = 0;
+ int ewidth = mem.width << min_wide_log2;
+ int wsub = wide_write ? sub : 0;
+ int rsub = wide_write ? 0 : sub;
+ while (pos < ewidth) {
+ int epos = pos;
+ while (epos < ewidth && wport.en[epos + wsub * mem.width] == wport.en[pos + wsub * mem.width])
+ epos++;
+
+ std::ostringstream os;
+ if (has_indent)
+ os << indent;
+ os << "if (";
+ dump_sigspec(os, wport.en[pos + wsub * mem.width]);
+ if (raddr != waddr) {
+ os << " && ";
+ dump_sigspec(os, raddr);
+ os << " == ";
+ dump_sigspec(os, waddr);
+ }
+ os << ")\n";
+ clk_to_lof_body[clk_domain_str].push_back(os.str());
+
+ std::ostringstream os2;
+ if (has_indent)
+ os2 << indent;
+ os2 << indent;
+ os2 << temp_id;
+ if (epos-pos != GetSize(port.data))
+ os2 << stringf("[%d:%d]", rsub * mem.width + epos-1, rsub * mem.width + pos);
+ os2 << " <= ";
+ if (port.transparency_mask[i])
+ dump_sigspec(os2, wport.data.extract(wsub * mem.width + pos, epos-pos));
+ else
+ dump_sigspec(os2, Const(State::Sx, epos - pos));
+ os2 << ";\n";
+ clk_to_lof_body[clk_domain_str].push_back(os2.str());
+
+ pos = epos;
+ }
+ }
+ }
+
+ if (port.srst != State::S0 && port.ce_over_srst)
+ {
+ std::ostringstream os;
+ if (has_indent)
+ os << indent;
+ os << stringf("if (");
+ dump_sigspec(os, port.srst);
+ os << stringf(")\n");
+ clk_to_lof_body[clk_domain_str].push_back(os.str());
+ std::ostringstream os2;
+ if (has_indent)
+ os2 << indent;
+ os2 << stringf("%s" "%s <= ", indent.c_str(), temp_id.c_str());
+ dump_sigspec(os2, port.srst_value);
+ os2 << ";\n";
+ clk_to_lof_body[clk_domain_str].push_back(os2.str());
+ }
+
+ if (has_indent)
+ clk_to_lof_body[clk_domain_str].push_back("end\n");
+
+ if (!port.init_value.is_fully_undef())
+ {
+ std::ostringstream os;
+ dump_sigspec(os, port.init_value);
+ std::string line = stringf("initial %s = %s;\n", temp_id.c_str(), os.str().c_str());
+ clk_to_lof_body[""].push_back(line);
+ }
+
+ {
+ std::ostringstream os;
+ dump_sigspec(os, port.data);
+ std::string line = stringf("assign %s = %s;\n", os.str().c_str(), temp_id.c_str());
+ clk_to_lof_body[""].push_back(line);
+ }
+ }
+ else
+ {
+ // for rd-transparent read-ports make something like:
+ // reg [..] temp_id;
+ // always @(posedge clk)
+ // temp_id <= r_addr;
+ // assign r_data = array_reg[temp_id];
+ std::string temp_id = next_auto_id();
+ lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", port.addr.size() - 1 - port.wide_log2, temp_id.c_str()) );
+ {
+ std::ostringstream os;
+ dump_sigspec(os, port.addr.extract_end(port.wide_log2));
+ std::string line = stringf("%s <= %s;\n", temp_id.c_str(), os.str().c_str());
+ clk_to_lof_body[clk_domain_str].push_back(line);
+ }
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++)
+ {
+ std::ostringstream os;
+ os << "assign ";
+ dump_sigspec(os, port.data.extract(sub * mem.width, mem.width));
+ os << stringf(" = %s[", mem_id.c_str());;
+ if (port.wide_log2) {
+ Const addr_lo;
+ for (int i = 0; i < port.wide_log2; i++)
+ addr_lo.bits.push_back(State(sub >> i & 1));
+ os << "{";
+ os << temp_id;
+ os << ", ";
+ dump_const(os, addr_lo);
+ os << "}";
+ } else {
+ os << temp_id;
+ }
+ os << "];\n";
+ clk_to_lof_body[""].push_back(os.str());
+ }
+ }
+ } else {
+ // for non-clocked read-ports make something like:
+ // assign r_data = array_reg[r_addr];
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++)
+ {
+ SigSpec addr = port.sub_addr(sub);
+
+ std::ostringstream os, os2;
+ dump_sigspec(os, port.data.extract(sub * mem.width, mem.width));
+ dump_sigspec(os2, addr);
+ std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), os2.str().c_str());
+ clk_to_lof_body[""].push_back(line);
+ }
+ }
+ }
+
+ // Write ports. Those are messy because we try to preserve priority, as much as we can:
+ //
+ // 1. We split all ports into several disjoint processes.
+ // 2. If a port has priority over another port, the two ports need to share
+ // a process, so that priority can be reconstructed on the other end.
+ // 3. We want each process to be as small as possible, to avoid extra
+ // priorities inferred on the other end.
+ pool<int> wr_ports_done;
+ for (int ridx = 0; ridx < GetSize(mem.wr_ports); ridx++)
+ {
+ if (wr_ports_done.count(ridx))
+ continue;
+
+ auto &root = mem.wr_ports[ridx];
+
+ // Start from a root.
+ pool<int> wr_ports_now;
+ wr_ports_now.insert(ridx);
+
+ // Transitively fill list of ports in this process by following priority edges.
+ while (true)
+ {
+ bool changed = false;
+
+ for (int i = 0; i < GetSize(mem.wr_ports); i++)
+ for (int j = 0; j < i; j++)
+ if (mem.wr_ports[i].priority_mask[j])
+ {
+ if (wr_ports_now.count(i) && !wr_ports_now.count(j)) {
+ wr_ports_now.insert(j);
+ changed = true;
+ }
+ if (!wr_ports_now.count(i) && wr_ports_now.count(j)) {
+ wr_ports_now.insert(i);
+ changed = true;
+ }
+ }
+
+ if (!changed)
+ break;
+ }
+
+ if (root.clk_enable) {
+ f << stringf("%s" "always%s @(%sedge ", indent.c_str(), systemverilog ? "_ff" : "", root.clk_polarity ? "pos" : "neg");
+ dump_sigspec(f, root.clk);
+ f << ") begin\n";
+ } else {
+ f << stringf("%s" "always%s begin\n", indent.c_str(), systemverilog ? "_latch" : " @*");
+ }
+
+ for (int pidx = 0; pidx < GetSize(mem.wr_ports); pidx++)
+ {
+ if (!wr_ports_now.count(pidx))
+ continue;
+ wr_ports_done.insert(pidx);
+
+ auto &port = mem.wr_ports[pidx];
+ log_assert(port.clk_enable == root.clk_enable);
+ if (port.clk_enable) {
+ log_assert(port.clk == root.clk);
+ log_assert(port.clk_polarity == root.clk_polarity);
+ }
+
+ // make something like:
+ // always @(posedge clk)
+ // if (wr_en_bit) memid[w_addr][??] <= w_data[??];
+ // ...
+ for (int sub = 0; sub < (1 << port.wide_log2); sub++)
+ {
+ SigSpec addr = port.sub_addr(sub);
+ for (int i = 0; i < mem.width; i++)
+ {
+ int start_i = i, width = 1;
+ SigBit wen_bit = port.en[sub * mem.width + i];
+
+ while (i+1 < mem.width && active_sigmap(port.en[sub * mem.width + i+1]) == active_sigmap(wen_bit))
+ i++, width++;
+
+ if (wen_bit == State::S0)
+ continue;
+
+ f << stringf("%s%s", indent.c_str(), indent.c_str());
+ if (wen_bit != State::S1)
+ {
+ f << stringf("if (");
+ dump_sigspec(f, wen_bit);
+ f << stringf(")\n");
+ f << stringf("%s%s%s", indent.c_str(), indent.c_str(), indent.c_str());
+ }
+ f << stringf("%s[", mem_id.c_str());
+ dump_sigspec(f, addr);
+ if (width == GetSize(port.en))
+ f << stringf("] <= ");
+ else
+ f << stringf("][%d:%d] <= ", i, start_i);
+ dump_sigspec(f, port.data.extract(sub * mem.width + start_i, width));
+ f << stringf(";\n");
+ }
+ }
+ }
+
+ f << stringf("%s" "end\n", indent.c_str());
+ }
+ // Output Verilog that looks something like this:
+ // reg [..] _3_;
+ // always @(posedge CLK2) begin
+ // _3_ <= memory[D1ADDR];
+ // if (A1EN)
+ // memory[A1ADDR] <= A1DATA;
+ // if (A2EN)
+ // memory[A2ADDR] <= A2DATA;
+ // ...
+ // end
+ // always @(negedge CLK1) begin
+ // if (C1EN)
+ // memory[C1ADDR] <= C1DATA;
+ // end
+ // ...
+ // assign D1DATA = _3_;
+ // assign D2DATA <= memory[D2ADDR];
+
+ // the reg ... definitions
+ for(auto &reg : lof_reg_declarations)
+ {
+ f << stringf("%s" "%s", indent.c_str(), reg.c_str());
+ }
+ // the block of expressions by clock domain
+ for(auto &pair : clk_to_lof_body)
+ {
+ std::string clk_domain = pair.first;
+ std::vector<std::string> lof_lines = pair.second;
+ if( clk_domain != "")
+ {
+ f << stringf("%s" "always%s @(%s) begin\n", indent.c_str(), systemverilog ? "_ff" : "", clk_domain.c_str());
+ bool has_arst = clk_to_arst_cond.count(clk_domain) != 0;
+ if (has_arst) {
+ f << stringf("%s%s" "if (%s) begin\n", indent.c_str(), indent.c_str(), clk_to_arst_cond[clk_domain].c_str());
+ for(auto &line : clk_to_arst_body[clk_domain])
+ f << stringf("%s%s%s" "%s", indent.c_str(), indent.c_str(), indent.c_str(), line.c_str());
+ f << stringf("%s%s" "end else begin\n", indent.c_str(), indent.c_str());
+ for(auto &line : lof_lines)
+ f << stringf("%s%s%s" "%s", indent.c_str(), indent.c_str(), indent.c_str(), line.c_str());
+ f << stringf("%s%s" "end\n", indent.c_str(), indent.c_str());
+ } else {
+ for(auto &line : lof_lines)
+ f << stringf("%s%s" "%s", indent.c_str(), indent.c_str(), line.c_str());
+ }
+ f << stringf("%s" "end\n", indent.c_str());
+ }
+ else
+ {
+ // the non-clocked assignments
+ for(auto &line : lof_lines)
+ f << stringf("%s" "%s", indent.c_str(), line.c_str());
+ }
+ }
}
void dump_cell_expr_port(std::ostream &f, RTLIL::Cell *cell, std::string port, bool gen_signed = true)
@@ -451,7 +949,7 @@ void dump_cell_expr_port(std::ostream &f, RTLIL::Cell *cell, std::string port, b
std::string cellname(RTLIL::Cell *cell)
{
- if (!norename && cell->name[0] == '$' && reg_ct.count(cell->type) && cell->hasPort(ID::Q))
+ if (!norename && cell->name[0] == '$' && RTLIL::builtin_ff_cell_types().count(cell->type) && cell->hasPort(ID::Q) && !cell->type.in(ID($ff), ID($_FF_)))
{
RTLIL::SigSpec sig = cell->getPort(ID::Q);
if (GetSize(sig) != 1 || sig.is_fully_const())
@@ -605,93 +1103,6 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
return true;
}
- if (cell->type.begins_with("$_DFF_"))
- {
- std::string reg_name = cellname(cell);
- bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name);
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "reg %s", indent.c_str(), reg_name.c_str());
- dump_reg_init(f, cell->getPort(ID::Q));
- f << ";\n";
- }
-
- dump_attributes(f, indent, cell->attributes);
- f << stringf("%s" "always @(%sedge ", indent.c_str(), cell->type[6] == 'P' ? "pos" : "neg");
- dump_sigspec(f, cell->getPort(ID::C));
- if (cell->type[7] != '_') {
- f << stringf(" or %sedge ", cell->type[7] == 'P' ? "pos" : "neg");
- dump_sigspec(f, cell->getPort(ID::R));
- }
- f << stringf(")\n");
-
- if (cell->type[7] != '_') {
- f << stringf("%s" " if (%s", indent.c_str(), cell->type[7] == 'P' ? "" : "!");
- dump_sigspec(f, cell->getPort(ID::R));
- f << stringf(")\n");
- f << stringf("%s" " %s <= %c;\n", indent.c_str(), reg_name.c_str(), cell->type[8]);
- f << stringf("%s" " else\n", indent.c_str());
- }
-
- f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str());
- dump_cell_expr_port(f, cell, "D", false);
- f << stringf(";\n");
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "assign ", indent.c_str());
- dump_sigspec(f, cell->getPort(ID::Q));
- f << stringf(" = %s;\n", reg_name.c_str());
- }
-
- return true;
- }
-
- if (cell->type.begins_with("$_DFFSR_"))
- {
- char pol_c = cell->type[8], pol_s = cell->type[9], pol_r = cell->type[10];
-
- std::string reg_name = cellname(cell);
- bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name);
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "reg %s", indent.c_str(), reg_name.c_str());
- dump_reg_init(f, cell->getPort(ID::Q));
- f << ";\n";
- }
-
- dump_attributes(f, indent, cell->attributes);
- f << stringf("%s" "always @(%sedge ", indent.c_str(), pol_c == 'P' ? "pos" : "neg");
- dump_sigspec(f, cell->getPort(ID::C));
- f << stringf(" or %sedge ", pol_s == 'P' ? "pos" : "neg");
- dump_sigspec(f, cell->getPort(ID::S));
- f << stringf(" or %sedge ", pol_r == 'P' ? "pos" : "neg");
- dump_sigspec(f, cell->getPort(ID::R));
- f << stringf(")\n");
-
- f << stringf("%s" " if (%s", indent.c_str(), pol_r == 'P' ? "" : "!");
- dump_sigspec(f, cell->getPort(ID::R));
- f << stringf(")\n");
- f << stringf("%s" " %s <= 0;\n", indent.c_str(), reg_name.c_str());
-
- f << stringf("%s" " else if (%s", indent.c_str(), pol_s == 'P' ? "" : "!");
- dump_sigspec(f, cell->getPort(ID::S));
- f << stringf(")\n");
- f << stringf("%s" " %s <= 1;\n", indent.c_str(), reg_name.c_str());
-
- f << stringf("%s" " else\n", indent.c_str());
- f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str());
- dump_cell_expr_port(f, cell, "D", false);
- f << stringf(";\n");
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "assign ", indent.c_str());
- dump_sigspec(f, cell->getPort(ID::Q));
- f << stringf(" = %s;\n", reg_name.c_str());
- }
-
- return true;
- }
-
#define HANDLE_UNIOP(_type, _operator) \
if (cell->type ==_type) { dump_cell_expr_uniop(f, indent, cell, _operator); return true; }
#define HANDLE_BINOP(_type, _operator) \
@@ -836,21 +1247,19 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
f << stringf(" = ");
if (cell->getParam(ID::B_SIGNED).as_bool())
{
- f << stringf("$signed(");
- dump_sigspec(f, cell->getPort(ID::B));
- f << stringf(")");
+ dump_cell_expr_port(f, cell, "B", true);
f << stringf(" < 0 ? ");
- dump_sigspec(f, cell->getPort(ID::A));
+ dump_cell_expr_port(f, cell, "A", true);
f << stringf(" << - ");
dump_sigspec(f, cell->getPort(ID::B));
f << stringf(" : ");
- dump_sigspec(f, cell->getPort(ID::A));
+ dump_cell_expr_port(f, cell, "A", true);
f << stringf(" >> ");
dump_sigspec(f, cell->getPort(ID::B));
}
else
{
- dump_sigspec(f, cell->getPort(ID::A));
+ dump_cell_expr_port(f, cell, "A", true);
f << stringf(" >> ");
dump_sigspec(f, cell->getPort(ID::B));
}
@@ -986,431 +1395,233 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
return true;
}
- if (cell->type == ID($dffsr))
+ if (RTLIL::builtin_ff_cell_types().count(cell->type))
{
- SigSpec sig_clk = cell->getPort(ID::CLK);
- SigSpec sig_set = cell->getPort(ID::SET);
- SigSpec sig_clr = cell->getPort(ID::CLR);
- SigSpec sig_d = cell->getPort(ID::D);
- SigSpec sig_q = cell->getPort(ID::Q);
+ FfData ff(nullptr, cell);
- int width = cell->parameters[ID::WIDTH].as_int();
- bool pol_clk = cell->parameters[ID::CLK_POLARITY].as_bool();
- bool pol_set = cell->parameters[ID::SET_POLARITY].as_bool();
- bool pol_clr = cell->parameters[ID::CLR_POLARITY].as_bool();
+ // $ff / $_FF_ cell: not supported.
+ if (ff.has_gclk)
+ return false;
std::string reg_name = cellname(cell);
- bool out_is_reg_wire = is_reg_wire(sig_q, reg_name);
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "reg [%d:0] %s", indent.c_str(), width-1, reg_name.c_str());
- dump_reg_init(f, sig_q);
- f << ";\n";
- }
-
- for (int i = 0; i < width; i++) {
- f << stringf("%s" "always @(%sedge ", indent.c_str(), pol_clk ? "pos" : "neg");
- dump_sigspec(f, sig_clk);
- f << stringf(", %sedge ", pol_set ? "pos" : "neg");
- dump_sigspec(f, sig_set);
- f << stringf(", %sedge ", pol_clr ? "pos" : "neg");
- dump_sigspec(f, sig_clr);
- f << stringf(")\n");
-
- f << stringf("%s" " if (%s", indent.c_str(), pol_clr ? "" : "!");
- dump_sigspec(f, sig_clr);
- f << stringf(") %s[%d] <= 1'b0;\n", reg_name.c_str(), i);
-
- f << stringf("%s" " else if (%s", indent.c_str(), pol_set ? "" : "!");
- dump_sigspec(f, sig_set);
- f << stringf(") %s[%d] <= 1'b1;\n", reg_name.c_str(), i);
-
- f << stringf("%s" " else %s[%d] <= ", indent.c_str(), reg_name.c_str(), i);
- dump_sigspec(f, sig_d[i]);
- f << stringf(";\n");
- }
+ bool out_is_reg_wire = is_reg_wire(ff.sig_q, reg_name);
if (!out_is_reg_wire) {
- f << stringf("%s" "assign ", indent.c_str());
- dump_sigspec(f, sig_q);
- f << stringf(" = %s;\n", reg_name.c_str());
- }
-
- return true;
- }
-
- if (cell->type.in(ID($dff), ID($adff), ID($dffe)))
- {
- RTLIL::SigSpec sig_clk, sig_arst, sig_en, val_arst;
- bool pol_clk, pol_arst = false, pol_en = false;
-
- sig_clk = cell->getPort(ID::CLK);
- pol_clk = cell->parameters[ID::CLK_POLARITY].as_bool();
-
- if (cell->type == ID($adff)) {
- sig_arst = cell->getPort(ID::ARST);
- pol_arst = cell->parameters[ID::ARST_POLARITY].as_bool();
- val_arst = RTLIL::SigSpec(cell->parameters[ID::ARST_VALUE]);
- }
-
- if (cell->type == ID($dffe)) {
- sig_en = cell->getPort(ID::EN);
- pol_en = cell->parameters[ID::EN_POLARITY].as_bool();
- }
-
- std::string reg_name = cellname(cell);
- bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name);
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "reg [%d:0] %s", indent.c_str(), cell->parameters[ID::WIDTH].as_int()-1, reg_name.c_str());
- dump_reg_init(f, cell->getPort(ID::Q));
- f << ";\n";
- }
-
- f << stringf("%s" "always @(%sedge ", indent.c_str(), pol_clk ? "pos" : "neg");
- dump_sigspec(f, sig_clk);
- if (cell->type == ID($adff)) {
- f << stringf(" or %sedge ", pol_arst ? "pos" : "neg");
- dump_sigspec(f, sig_arst);
- }
- f << stringf(")\n");
-
- if (cell->type == ID($adff)) {
- f << stringf("%s" " if (%s", indent.c_str(), pol_arst ? "" : "!");
- dump_sigspec(f, sig_arst);
- f << stringf(")\n");
- f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str());
- dump_sigspec(f, val_arst);
- f << stringf(";\n");
- f << stringf("%s" " else\n", indent.c_str());
- }
-
- if (cell->type == ID($dffe)) {
- f << stringf("%s" " if (%s", indent.c_str(), pol_en ? "" : "!");
- dump_sigspec(f, sig_en);
- f << stringf(")\n");
- }
-
- f << stringf("%s" " %s <= ", indent.c_str(), reg_name.c_str());
- dump_cell_expr_port(f, cell, "D", false);
- f << stringf(";\n");
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "assign ", indent.c_str());
- dump_sigspec(f, cell->getPort(ID::Q));
- f << stringf(" = %s;\n", reg_name.c_str());
- }
-
- return true;
- }
-
- if (cell->type == ID($dlatch))
- {
- RTLIL::SigSpec sig_en;
- bool pol_en = false;
-
- sig_en = cell->getPort(ID::EN);
- pol_en = cell->parameters[ID::EN_POLARITY].as_bool();
-
- std::string reg_name = cellname(cell);
- bool out_is_reg_wire = is_reg_wire(cell->getPort(ID::Q), reg_name);
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "reg [%d:0] %s", indent.c_str(), cell->parameters[ID::WIDTH].as_int()-1, reg_name.c_str());
- dump_reg_init(f, cell->getPort(ID::Q));
+ if (ff.width == 1)
+ f << stringf("%s" "reg %s", indent.c_str(), reg_name.c_str());
+ else
+ f << stringf("%s" "reg [%d:0] %s", indent.c_str(), ff.width-1, reg_name.c_str());
+ dump_reg_init(f, ff.sig_q);
f << ";\n";
}
- f << stringf("%s" "always @*\n", indent.c_str());
-
- f << stringf("%s" " if (%s", indent.c_str(), pol_en ? "" : "!");
- dump_sigspec(f, sig_en);
- f << stringf(")\n");
-
- f << stringf("%s" " %s = ", indent.c_str(), reg_name.c_str());
- dump_cell_expr_port(f, cell, "D", false);
- f << stringf(";\n");
-
- if (!out_is_reg_wire) {
- f << stringf("%s" "assign ", indent.c_str());
- dump_sigspec(f, cell->getPort(ID::Q));
- f << stringf(" = %s;\n", reg_name.c_str());
- }
-
- return true;
- }
+ // If the FF has CLR/SET inputs, emit every bit slice separately.
+ int chunks = ff.has_sr ? ff.width : 1;
+ bool chunky = ff.has_sr && ff.width != 1;
- if (cell->type == ID($mem))
- {
- RTLIL::IdString memid = cell->parameters[ID::MEMID].decode_string();
- std::string mem_id = id(cell->parameters[ID::MEMID].decode_string());
- int abits = cell->parameters[ID::ABITS].as_int();
- int size = cell->parameters[ID::SIZE].as_int();
- int offset = cell->parameters[ID::OFFSET].as_int();
- int width = cell->parameters[ID::WIDTH].as_int();
- bool use_init = !(RTLIL::SigSpec(cell->parameters[ID::INIT]).is_fully_undef());
-
- // for memory block make something like:
- // reg [7:0] memid [3:0];
- // initial begin
- // memid[0] = ...
- // end
- dump_attributes(f, indent.c_str(), cell->attributes);
- f << stringf("%s" "reg [%d:%d] %s [%d:%d];\n", indent.c_str(), width-1, 0, mem_id.c_str(), size+offset-1, offset);
- if (use_init)
+ for (int i = 0; i < chunks; i++)
{
- if (extmem)
- {
- std::string extmem_filename = stringf("%s-%d.mem", extmem_prefix.c_str(), extmem_counter++);
-
- std::string extmem_filename_esc;
- for (auto c : extmem_filename)
- {
- if (c == '\n')
- extmem_filename_esc += "\\n";
- else if (c == '\t')
- extmem_filename_esc += "\\t";
- else if (c < 32)
- extmem_filename_esc += stringf("\\%03o", c);
- else if (c == '"')
- extmem_filename_esc += "\\\"";
- else if (c == '\\')
- extmem_filename_esc += "\\\\";
- else
- extmem_filename_esc += c;
- }
- f << stringf("%s" "initial $readmemb(\"%s\", %s);\n", indent.c_str(), extmem_filename_esc.c_str(), mem_id.c_str());
-
- std::ofstream extmem_f(extmem_filename, std::ofstream::trunc);
- if (extmem_f.fail())
- log_error("Can't open file `%s' for writing: %s\n", extmem_filename.c_str(), strerror(errno));
- else
- {
- for (int i=0; i<size; i++)
- {
- RTLIL::Const element = cell->parameters[ID::INIT].extract(i*width, width);
- for (int j=0; j<element.size(); j++)
- {
- switch (element[element.size()-j-1])
- {
- case State::S0: extmem_f << '0'; break;
- case State::S1: extmem_f << '1'; break;
- case State::Sx: extmem_f << 'x'; break;
- case State::Sz: extmem_f << 'z'; break;
- case State::Sa: extmem_f << '_'; break;
- case State::Sm: log_error("Found marker state in final netlist.");
- }
- }
- extmem_f << '\n';
- }
- }
-
- }
- else
- {
- f << stringf("%s" "initial begin\n", indent.c_str());
- for (int i=0; i<size; i++)
- {
- f << stringf("%s" " %s[%d] = ", indent.c_str(), mem_id.c_str(), i);
- dump_const(f, cell->parameters[ID::INIT].extract(i*width, width));
- f << stringf(";\n");
- }
- f << stringf("%s" "end\n", indent.c_str());
+ SigSpec sig_d, sig_ad;
+ Const val_arst, val_srst;
+ std::string reg_bit_name, sig_set_name, sig_clr_name, sig_arst_name, sig_aload_name;
+ if (chunky) {
+ reg_bit_name = stringf("%s[%d]", reg_name.c_str(), i);
+ if (ff.has_gclk || ff.has_clk)
+ sig_d = ff.sig_d[i];
+ if (ff.has_aload)
+ sig_ad = ff.sig_ad[i];
+ } else {
+ reg_bit_name = reg_name;
+ sig_d = ff.sig_d;
+ sig_ad = ff.sig_ad;
}
- }
-
- // create a map : "edge clk" -> expressions within that clock domain
- dict<std::string, std::vector<std::string>> clk_to_lof_body;
- clk_to_lof_body[""] = std::vector<std::string>();
- std::string clk_domain_str;
- // create a list of reg declarations
- std::vector<std::string> lof_reg_declarations;
-
- int nread_ports = cell->parameters[ID::RD_PORTS].as_int();
- RTLIL::SigSpec sig_rd_clk, sig_rd_en, sig_rd_data, sig_rd_addr;
- bool use_rd_clk, rd_clk_posedge, rd_transparent;
- // read ports
- for (int i=0; i < nread_ports; i++)
- {
- sig_rd_clk = cell->getPort(ID::RD_CLK).extract(i);
- sig_rd_en = cell->getPort(ID::RD_EN).extract(i);
- sig_rd_data = cell->getPort(ID::RD_DATA).extract(i*width, width);
- sig_rd_addr = cell->getPort(ID::RD_ADDR).extract(i*abits, abits);
- use_rd_clk = cell->parameters[ID::RD_CLK_ENABLE].extract(i).as_bool();
- rd_clk_posedge = cell->parameters[ID::RD_CLK_POLARITY].extract(i).as_bool();
- rd_transparent = cell->parameters[ID::RD_TRANSPARENT].extract(i).as_bool();
- if (use_rd_clk)
- {
- {
- std::ostringstream os;
- dump_sigspec(os, sig_rd_clk);
- clk_domain_str = stringf("%sedge %s", rd_clk_posedge ? "pos" : "neg", os.str().c_str());
- if( clk_to_lof_body.count(clk_domain_str) == 0 )
- clk_to_lof_body[clk_domain_str] = std::vector<std::string>();
- }
- if (!rd_transparent)
- {
- // for clocked read ports make something like:
- // reg [..] temp_id;
- // always @(posedge clk)
- // if (rd_en) temp_id <= array_reg[r_addr];
- // assign r_data = temp_id;
- std::string temp_id = next_auto_id();
- lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", sig_rd_data.size() - 1, temp_id.c_str()) );
+ if (ff.has_arst)
+ val_arst = chunky ? ff.val_arst[i] : ff.val_arst;
+ if (ff.has_srst)
+ val_srst = chunky ? ff.val_srst[i] : ff.val_srst;
+
+ // If there are constants in the sensitivity list, replace them with an intermediate wire
+ if (ff.has_clk) {
+ if (ff.has_sr) {
+ if (ff.sig_set[i].wire == NULL)
{
- std::ostringstream os;
- if (sig_rd_en != RTLIL::SigBit(true))
- {
- os << stringf("if (");
- dump_sigspec(os, sig_rd_en);
- os << stringf(") ");
- }
- os << stringf("%s <= %s[", temp_id.c_str(), mem_id.c_str());
- dump_sigspec(os, sig_rd_addr);
- os << stringf("];\n");
- clk_to_lof_body[clk_domain_str].push_back(os.str());
+ sig_set_name = next_auto_id();
+ f << stringf("%s" "wire %s = ", indent.c_str(), sig_set_name.c_str());
+ dump_const(f, ff.sig_set[i].data);
+ f << stringf(";\n");
}
+ if (ff.sig_clr[i].wire == NULL)
{
- std::ostringstream os;
- dump_sigspec(os, sig_rd_data);
- std::string line = stringf("assign %s = %s;\n", os.str().c_str(), temp_id.c_str());
- clk_to_lof_body[""].push_back(line);
+ sig_clr_name = next_auto_id();
+ f << stringf("%s" "wire %s = ", indent.c_str(), sig_clr_name.c_str());
+ dump_const(f, ff.sig_clr[i].data);
+ f << stringf(";\n");
}
- }
- else
- {
- // for rd-transparent read-ports make something like:
- // reg [..] temp_id;
- // always @(posedge clk)
- // temp_id <= r_addr;
- // assign r_data = array_reg[temp_id];
- std::string temp_id = next_auto_id();
- lof_reg_declarations.push_back( stringf("reg [%d:0] %s;\n", sig_rd_addr.size() - 1, temp_id.c_str()) );
+ } else if (ff.has_arst) {
+ if (ff.sig_arst[0].wire == NULL)
{
- std::ostringstream os;
- dump_sigspec(os, sig_rd_addr);
- std::string line = stringf("%s <= %s;\n", temp_id.c_str(), os.str().c_str());
- clk_to_lof_body[clk_domain_str].push_back(line);
+ sig_arst_name = next_auto_id();
+ f << stringf("%s" "wire %s = ", indent.c_str(), sig_arst_name.c_str());
+ dump_const(f, ff.sig_arst[0].data);
+ f << stringf(";\n");
}
+ } else if (ff.has_aload) {
+ if (ff.sig_aload[0].wire == NULL)
{
- std::ostringstream os;
- dump_sigspec(os, sig_rd_data);
- std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), temp_id.c_str());
- clk_to_lof_body[""].push_back(line);
+ sig_aload_name = next_auto_id();
+ f << stringf("%s" "wire %s = ", indent.c_str(), sig_aload_name.c_str());
+ dump_const(f, ff.sig_aload[0].data);
+ f << stringf(";\n");
}
}
- } else {
- // for non-clocked read-ports make something like:
- // assign r_data = array_reg[r_addr];
- std::ostringstream os, os2;
- dump_sigspec(os, sig_rd_data);
- dump_sigspec(os2, sig_rd_addr);
- std::string line = stringf("assign %s = %s[%s];\n", os.str().c_str(), mem_id.c_str(), os2.str().c_str());
- clk_to_lof_body[""].push_back(line);
}
- }
-
- int nwrite_ports = cell->parameters[ID::WR_PORTS].as_int();
- RTLIL::SigSpec sig_wr_clk, sig_wr_data, sig_wr_addr, sig_wr_en;
- bool wr_clk_posedge;
- // write ports
- for (int i=0; i < nwrite_ports; i++)
- {
- sig_wr_clk = cell->getPort(ID::WR_CLK).extract(i);
- sig_wr_data = cell->getPort(ID::WR_DATA).extract(i*width, width);
- sig_wr_addr = cell->getPort(ID::WR_ADDR).extract(i*abits, abits);
- sig_wr_en = cell->getPort(ID::WR_EN).extract(i*width, width);
- wr_clk_posedge = cell->parameters[ID::WR_CLK_POLARITY].extract(i).as_bool();
- {
- std::ostringstream os;
- dump_sigspec(os, sig_wr_clk);
- clk_domain_str = stringf("%sedge %s", wr_clk_posedge ? "pos" : "neg", os.str().c_str());
- if( clk_to_lof_body.count(clk_domain_str) == 0 )
- clk_to_lof_body[clk_domain_str] = std::vector<std::string>();
- }
- // make something like:
- // always @(posedge clk)
- // if (wr_en_bit) memid[w_addr][??] <= w_data[??];
- // ...
- for (int i = 0; i < GetSize(sig_wr_en); i++)
+ dump_attributes(f, indent, cell->attributes);
+ if (ff.has_clk)
{
- int start_i = i, width = 1;
- SigBit wen_bit = sig_wr_en[i];
+ // FFs.
+ f << stringf("%s" "always%s @(%sedge ", indent.c_str(), systemverilog ? "_ff" : "", ff.pol_clk ? "pos" : "neg");
+ dump_sigspec(f, ff.sig_clk);
+ if (ff.has_sr) {
+ f << stringf(", %sedge ", ff.pol_set ? "pos" : "neg");
+ if (ff.sig_set[i].wire == NULL)
+ f << stringf("%s", sig_set_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_set[i]);
- while (i+1 < GetSize(sig_wr_en) && active_sigmap(sig_wr_en[i+1]) == active_sigmap(wen_bit))
- i++, width++;
+ f << stringf(", %sedge ", ff.pol_clr ? "pos" : "neg");
+ if (ff.sig_clr[i].wire == NULL)
+ f << stringf("%s", sig_clr_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_clr[i]);
+ } else if (ff.has_arst) {
+ f << stringf(", %sedge ", ff.pol_arst ? "pos" : "neg");
+ if (ff.sig_arst[0].wire == NULL)
+ f << stringf("%s", sig_arst_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_arst);
+ } else if (ff.has_aload) {
+ f << stringf(", %sedge ", ff.pol_aload ? "pos" : "neg");
+ if (ff.sig_aload[0].wire == NULL)
+ f << stringf("%s", sig_aload_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_aload);
+ }
+ f << stringf(")\n");
- if (wen_bit == State::S0)
- continue;
+ f << stringf("%s" " ", indent.c_str());
+ if (ff.has_sr) {
+ f << stringf("if (%s", ff.pol_clr ? "" : "!");
+ if (ff.sig_clr[i].wire == NULL)
+ f << stringf("%s", sig_clr_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_clr[i]);
+ f << stringf(") %s <= 1'b0;\n", reg_bit_name.c_str());
+ f << stringf("%s" " else if (%s", indent.c_str(), ff.pol_set ? "" : "!");
+ if (ff.sig_set[i].wire == NULL)
+ f << stringf("%s", sig_set_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_set[i]);
+ f << stringf(") %s <= 1'b1;\n", reg_bit_name.c_str());
+ f << stringf("%s" " else ", indent.c_str());
+ } else if (ff.has_arst) {
+ f << stringf("if (%s", ff.pol_arst ? "" : "!");
+ if (ff.sig_arst[0].wire == NULL)
+ f << stringf("%s", sig_arst_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_arst);
+ f << stringf(") %s <= ", reg_bit_name.c_str());
+ dump_sigspec(f, val_arst);
+ f << stringf(";\n");
+ f << stringf("%s" " else ", indent.c_str());
+ } else if (ff.has_aload) {
+ f << stringf("if (%s", ff.pol_aload ? "" : "!");
+ if (ff.sig_aload[0].wire == NULL)
+ f << stringf("%s", sig_aload_name.c_str());
+ else
+ dump_sigspec(f, ff.sig_aload);
+ f << stringf(") %s <= ", reg_bit_name.c_str());
+ dump_sigspec(f, sig_ad);
+ f << stringf(";\n");
+ f << stringf("%s" " else ", indent.c_str());
+ }
- std::ostringstream os;
- if (wen_bit != State::S1)
- {
- os << stringf("if (");
- dump_sigspec(os, wen_bit);
- os << stringf(") ");
+ if (ff.has_srst && ff.has_ce && ff.ce_over_srst) {
+ f << stringf("if (%s", ff.pol_ce ? "" : "!");
+ dump_sigspec(f, ff.sig_ce);
+ f << stringf(")\n");
+ f << stringf("%s" " if (%s", indent.c_str(), ff.pol_srst ? "" : "!");
+ dump_sigspec(f, ff.sig_srst);
+ f << stringf(") %s <= ", reg_bit_name.c_str());
+ dump_sigspec(f, val_srst);
+ f << stringf(";\n");
+ f << stringf("%s" " else ", indent.c_str());
+ } else {
+ if (ff.has_srst) {
+ f << stringf("if (%s", ff.pol_srst ? "" : "!");
+ dump_sigspec(f, ff.sig_srst);
+ f << stringf(") %s <= ", reg_bit_name.c_str());
+ dump_sigspec(f, val_srst);
+ f << stringf(";\n");
+ f << stringf("%s" " else ", indent.c_str());
+ }
+ if (ff.has_ce) {
+ f << stringf("if (%s", ff.pol_ce ? "" : "!");
+ dump_sigspec(f, ff.sig_ce);
+ f << stringf(") ");
+ }
}
- os << stringf("%s[", mem_id.c_str());
- dump_sigspec(os, sig_wr_addr);
- if (width == GetSize(sig_wr_en))
- os << stringf("] <= ");
- else
- os << stringf("][%d:%d] <= ", i, start_i);
- dump_sigspec(os, sig_wr_data.extract(start_i, width));
- os << stringf(";\n");
- clk_to_lof_body[clk_domain_str].push_back(os.str());
- }
- }
- // Output Verilog that looks something like this:
- // reg [..] _3_;
- // always @(posedge CLK2) begin
- // _3_ <= memory[D1ADDR];
- // if (A1EN)
- // memory[A1ADDR] <= A1DATA;
- // if (A2EN)
- // memory[A2ADDR] <= A2DATA;
- // ...
- // end
- // always @(negedge CLK1) begin
- // if (C1EN)
- // memory[C1ADDR] <= C1DATA;
- // end
- // ...
- // assign D1DATA = _3_;
- // assign D2DATA <= memory[D2ADDR];
-
- // the reg ... definitions
- for(auto &reg : lof_reg_declarations)
- {
- f << stringf("%s" "%s", indent.c_str(), reg.c_str());
- }
- // the block of expressions by clock domain
- for(auto &pair : clk_to_lof_body)
- {
- std::string clk_domain = pair.first;
- std::vector<std::string> lof_lines = pair.second;
- if( clk_domain != "")
- {
- f << stringf("%s" "always @(%s) begin\n", indent.c_str(), clk_domain.c_str());
- for(auto &line : lof_lines)
- f << stringf("%s%s" "%s", indent.c_str(), indent.c_str(), line.c_str());
- f << stringf("%s" "end\n", indent.c_str());
+
+ f << stringf("%s <= ", reg_bit_name.c_str());
+ dump_sigspec(f, sig_d);
+ f << stringf(";\n");
}
else
{
- // the non-clocked assignments
- for(auto &line : lof_lines)
- f << stringf("%s" "%s", indent.c_str(), line.c_str());
+ // Latches.
+ f << stringf("%s" "always%s\n", indent.c_str(), systemverilog ? "_latch" : " @*");
+
+ f << stringf("%s" " ", indent.c_str());
+ if (ff.has_sr) {
+ f << stringf("if (%s", ff.pol_clr ? "" : "!");
+ dump_sigspec(f, ff.sig_clr[i]);
+ f << stringf(") %s = 1'b0;\n", reg_bit_name.c_str());
+ f << stringf("%s" " else if (%s", indent.c_str(), ff.pol_set ? "" : "!");
+ dump_sigspec(f, ff.sig_set[i]);
+ f << stringf(") %s = 1'b1;\n", reg_bit_name.c_str());
+ if (ff.has_aload)
+ f << stringf("%s" " else ", indent.c_str());
+ } else if (ff.has_arst) {
+ f << stringf("if (%s", ff.pol_arst ? "" : "!");
+ dump_sigspec(f, ff.sig_arst);
+ f << stringf(") %s = ", reg_bit_name.c_str());
+ dump_sigspec(f, val_arst);
+ f << stringf(";\n");
+ if (ff.has_aload)
+ f << stringf("%s" " else ", indent.c_str());
+ }
+ if (ff.has_aload) {
+ f << stringf("if (%s", ff.pol_aload ? "" : "!");
+ dump_sigspec(f, ff.sig_aload);
+ f << stringf(") %s = ", reg_bit_name.c_str());
+ dump_sigspec(f, sig_ad);
+ f << stringf(";\n");
+ }
}
}
+ if (!out_is_reg_wire) {
+ f << stringf("%s" "assign ", indent.c_str());
+ dump_sigspec(f, ff.sig_q);
+ f << stringf(" = %s;\n", reg_name.c_str());
+ }
+
return true;
}
if (cell->type.in(ID($assert), ID($assume), ID($cover)))
{
- f << stringf("%s" "always @* if (", indent.c_str());
+ f << stringf("%s" "always%s if (", indent.c_str(), systemverilog ? "_comb" : " @*");
dump_sigspec(f, cell->getPort(ID::EN));
f << stringf(") %s(", cell->type.c_str()+1);
dump_sigspec(f, cell->getPort(ID::A));
@@ -1528,14 +1739,17 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell)
return true;
}
- // FIXME: $_SR_[PN][PN]_, $_DLATCH_[PN]_, $_DLATCHSR_[PN][PN][PN]_
- // FIXME: $sr, $dlatch, $memrd, $memwr, $fsm
+ // FIXME: $fsm
return false;
}
void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
{
+ // Handled by dump_memory
+ if (cell->is_mem_cell())
+ return;
+
if (cell->type[0] == '$' && !noexpr) {
if (dump_cell_expr(f, indent, cell))
return;
@@ -1602,7 +1816,7 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
}
}
- if (siminit && reg_ct.count(cell->type) && cell->hasPort(ID::Q)) {
+ if (siminit && RTLIL::builtin_ff_cell_types().count(cell->type) && cell->hasPort(ID::Q) && !cell->type.in(ID($ff), ID($_FF_))) {
std::stringstream ss;
dump_reg_init(ss, cell->getPort(ID::Q));
if (!ss.str().empty()) {
@@ -1615,11 +1829,23 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
void dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right)
{
- f << stringf("%s" "assign ", indent.c_str());
- dump_sigspec(f, left);
- f << stringf(" = ");
- dump_sigspec(f, right);
- f << stringf(";\n");
+ if (simple_lhs) {
+ int offset = 0;
+ for (auto &chunk : left.chunks()) {
+ f << stringf("%s" "assign ", indent.c_str());
+ dump_sigspec(f, chunk);
+ f << stringf(" = ");
+ dump_sigspec(f, right.extract(offset, GetSize(chunk)));
+ f << stringf(";\n");
+ offset += GetSize(chunk);
+ }
+ } else {
+ f << stringf("%s" "assign ", indent.c_str());
+ dump_sigspec(f, left);
+ f << stringf(" = ");
+ dump_sigspec(f, right);
+ f << stringf(";\n");
+ }
}
void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw);
@@ -1717,7 +1943,9 @@ void dump_process(std::ostream &f, std::string indent, RTLIL::Process *proc, boo
return;
}
- f << stringf("%s" "always @* begin\n", indent.c_str());
+ f << stringf("%s" "always%s begin\n", indent.c_str(), systemverilog ? "_comb" : " @*");
+ if (!systemverilog)
+ f << indent + " " << "if (" << id(initial_id) << ") begin end\n";
dump_case_body(f, indent, &proc->root_case, true);
std::string backup_indent = indent;
@@ -1728,11 +1956,11 @@ void dump_process(std::ostream &f, std::string indent, RTLIL::Process *proc, boo
indent = backup_indent;
if (sync->type == RTLIL::STa) {
- f << stringf("%s" "always @* begin\n", indent.c_str());
+ f << stringf("%s" "always%s begin\n", indent.c_str(), systemverilog ? "_comb" : " @*");
} else if (sync->type == RTLIL::STi) {
f << stringf("%s" "initial begin\n", indent.c_str());
} else {
- f << stringf("%s" "always @(", indent.c_str());
+ f << stringf("%s" "always%s @(", indent.c_str(), systemverilog ? "_ff" : "");
if (sync->type == RTLIL::STp || sync->type == RTLIL::ST1)
f << stringf("posedge ");
if (sync->type == RTLIL::STn || sync->type == RTLIL::ST0)
@@ -1810,7 +2038,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module)
std::set<std::pair<RTLIL::Wire*,int>> reg_bits;
for (auto cell : module->cells())
{
- if (!reg_ct.count(cell->type) || !cell->hasPort(ID::Q))
+ if (!RTLIL::builtin_ff_cell_types().count(cell->type) || !cell->hasPort(ID::Q) || cell->type.in(ID($ff), ID($_FF_)))
continue;
RTLIL::SigSpec sig = cell->getPort(ID::Q);
@@ -1836,6 +2064,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module)
dump_attributes(f, indent, module->attributes, '\n', /*modattr=*/true);
f << stringf("%s" "module %s(", indent.c_str(), id(module->name, false).c_str());
bool keep_running = true;
+ int cnt = 0;
for (int port_id = 1; keep_running; port_id++) {
keep_running = false;
for (auto wire : module->wires()) {
@@ -1844,17 +2073,22 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module)
f << stringf(", ");
f << stringf("%s", id(wire->name).c_str());
keep_running = true;
+ if (cnt==20) { f << stringf("\n"); cnt = 0; } else cnt++;
continue;
}
}
}
f << stringf(");\n");
+ if (!systemverilog && !module->processes.empty()) {
+ initial_id = NEW_ID;
+ f << indent + " " << "reg " << id(initial_id) << " = 0;\n";
+ }
for (auto w : module->wires())
dump_wire(f, indent + " ", w);
- for (auto it = module->memories.begin(); it != module->memories.end(); ++it)
- dump_memory(f, indent + " ", it->second);
+ for (auto &mem : Mem::get_all_memories(module))
+ dump_memory(f, indent + " ", mem);
for (auto cell : module->cells())
dump_cell(f, indent + " ", cell);
@@ -1881,6 +2115,9 @@ struct VerilogBackend : public Backend {
log("\n");
log("Write the current design to a Verilog file.\n");
log("\n");
+ log(" -sv\n");
+ log(" with this option, SystemVerilog constructs like always_comb are used\n");
+ log("\n");
log(" -norename\n");
log(" without this option all internal object names (the ones with a dollar\n");
log(" instead of a backslash prefix) are changed to short names in the\n");
@@ -1922,6 +2159,9 @@ struct VerilogBackend : public Backend {
log(" deactivates this feature and instead will write string constants\n");
log(" as binary numbers.\n");
log("\n");
+ log(" -simple-lhs\n");
+ log(" Connection assignments with simple left hand side without concatenations.\n");
+ log("\n");
log(" -extmem\n");
log(" instead of initializing memories using assignments to individual\n");
log(" elements, use the '$readmemh' function to read initialization data\n");
@@ -1969,6 +2209,7 @@ struct VerilogBackend : public Backend {
defparam = false;
decimal = false;
siminit = false;
+ simple_lhs = false;
auto_prefix = "";
bool blackboxes = false;
@@ -1976,37 +2217,14 @@ struct VerilogBackend : public Backend {
auto_name_map.clear();
reg_wires.clear();
- reg_ct.clear();
-
- reg_ct.insert(ID($dff));
- reg_ct.insert(ID($adff));
- reg_ct.insert(ID($dffe));
- reg_ct.insert(ID($dlatch));
-
- reg_ct.insert(ID($_DFF_N_));
- reg_ct.insert(ID($_DFF_P_));
-
- reg_ct.insert(ID($_DFF_NN0_));
- reg_ct.insert(ID($_DFF_NN1_));
- reg_ct.insert(ID($_DFF_NP0_));
- reg_ct.insert(ID($_DFF_NP1_));
- reg_ct.insert(ID($_DFF_PN0_));
- reg_ct.insert(ID($_DFF_PN1_));
- reg_ct.insert(ID($_DFF_PP0_));
- reg_ct.insert(ID($_DFF_PP1_));
-
- reg_ct.insert(ID($_DFFSR_NNN_));
- reg_ct.insert(ID($_DFFSR_NNP_));
- reg_ct.insert(ID($_DFFSR_NPN_));
- reg_ct.insert(ID($_DFFSR_NPP_));
- reg_ct.insert(ID($_DFFSR_PNN_));
- reg_ct.insert(ID($_DFFSR_PNP_));
- reg_ct.insert(ID($_DFFSR_PPN_));
- reg_ct.insert(ID($_DFFSR_PPP_));
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
std::string arg = args[argidx];
+ if (arg == "-sv") {
+ systemverilog = true;
+ continue;
+ }
if (arg == "-norename") {
norename = true;
continue;
@@ -2064,6 +2282,10 @@ struct VerilogBackend : public Backend {
selected = true;
continue;
}
+ if (arg == "-simple-lhs") {
+ simple_lhs = true;
+ continue;
+ }
if (arg == "-v") {
verbose = true;
continue;
@@ -2078,6 +2300,12 @@ struct VerilogBackend : public Backend {
extmem_prefix = filename.substr(0, filename.rfind('.'));
}
+ log_push();
+ Pass::call(design, "bmuxmap");
+ Pass::call(design, "demuxmap");
+ Pass::call(design, "clean_zerowidth");
+ log_pop();
+
design->sort();
*f << stringf("/* Generated by %s */\n", yosys_version_str);
@@ -2095,7 +2323,6 @@ struct VerilogBackend : public Backend {
auto_name_map.clear();
reg_wires.clear();
- reg_ct.clear();
}
} VerilogBackend;