return namecache.at(id).c_str();
 }
 
+std::string dump_const(const RTLIL::Const &data)
+{
+       std::string dataStr;
+
+       dataStr += "\"";
+
+       if ((data.flags & RTLIL::CONST_FLAG_STRING) == 0)
+       {
+               // Emit binary prefix for string.
+               dataStr += "b";
+
+               // Emit bits.
+               int width = data.bits.size();
+               for (int i = width - 1; i >= 0; i--)
+               {
+                       log_assert(i < width);
+                       switch (data.bits[i])
+                       {
+                               case State::S0: dataStr += stringf("0"); break;
+                               case State::S1: dataStr += stringf("1"); break;
+                               case State::Sx: dataStr += stringf("x"); break;
+                               case State::Sz: dataStr += stringf("z"); break;
+                               case State::Sa: dataStr += stringf("-"); break;
+                               case State::Sm: dataStr += stringf("m"); break;
+                       }
+               }
+       }
+       else
+       {
+               std::string str = data.decode_string();
+               for (size_t i = 0; i < str.size(); i++)
+               {
+                       if (str[i] == '\n')
+                               dataStr += "\\n";
+                       else if (str[i] == '\t')
+                               dataStr += "\\t";
+                       else if (str[i] < 32)
+                               dataStr += stringf("\\%03o", str[i]);
+                       else if (str[i] == '"')
+                               dataStr += "\\\"";
+                       else if (str[i] == '\\')
+                               dataStr += "\\\\";
+                       else
+                               dataStr += str[i];
+               }
+       }
+
+       dataStr += "\"";
+
+       return dataStr;
+}
+
+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)
+       {
+               std::string param_name(p.first.c_str());
+               const std::string param_value = dump_const(p.second);
+
+               // 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;
                        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) {
                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);
                        {
                                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()));
                        }
                }
 
                        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 + ")";
                                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;
                                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 + ")";
                                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;
                                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;
                                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;
                                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)
                                }
                                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;
                        }
                                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.
                                } 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;
                        }
                                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;
                        }
                        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);
                }
 
 
                        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();
                                // `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)));
                        }
                }
 
 
        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();
        }
 };
 
                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();