Merge pull request #2295 from epfl-vlsc/firrtl_blackbox_generic_parameters

Add firrtl backend support for generic parameters in blackbox components
This commit is contained in:
Miodrag Milanović 2020-11-24 07:50:17 +01:00 committed by GitHub
commit b8d3f13307
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 294 additions and 58 deletions

View File

@ -102,6 +102,260 @@ const char *make_id(IdString id)
return namecache.at(id).c_str();
}
std::string dump_const_string(const RTLIL::Const &data)
{
std::string res_str;
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];
}
return res_str;
}
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);
// 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;
}
}
res_str += stringf("%d", int_val);
}
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 += "\"";
}
}
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));
}
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;
}
// 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)
{
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;
@ -328,8 +582,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,33 +654,6 @@ 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);
@ -440,12 +675,12 @@ 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()));
}
}
@ -476,7 +711,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 +751,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 +763,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 +981,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,11 +994,11 @@ 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;
@ -902,9 +1137,9 @@ 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;
@ -923,7 +1158,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 +1168,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 +1180,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 +1194,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,8 +1207,8 @@ 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;
}
@ -986,8 +1221,8 @@ struct FirrtlWorker
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 +1288,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 +1302,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)));
}
}
@ -1112,12 +1347,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();
}
};
@ -1180,10 +1410,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();