vpr: add merged verilog netlist writer

Signed-off-by: Paweł Czarnecki <pczarnecki@antmicro.com>
This commit is contained in:
Paweł Czarnecki 2022-06-30 14:17:26 +02:00
parent 721ac99696
commit 46a3b9303f
7 changed files with 314 additions and 70 deletions

View File

@ -524,6 +524,7 @@ static void SetupAnalysisOpts(const t_options& Options, t_analysis_opts& analysi
}
analysis_opts.gen_post_synthesis_netlist = Options.Generate_Post_Synthesis_Netlist;
analysis_opts.gen_post_implementation_merged_netlist = Options.Generate_Post_Implementation_Merged_Netlist;
analysis_opts.timing_report_npaths = Options.timing_report_npaths;
analysis_opts.timing_report_detail = Options.timing_report_detail;

View File

@ -10,6 +10,7 @@
#include <memory>
#include <unordered_set>
#include <cmath>
#include <regex>
#include "vtr_assert.h"
#include "vtr_util.h"
@ -687,12 +688,12 @@ class BlackBoxInst : public Instance {
os << indent(depth + 3) << "(IOPATH ";
os << escape_sdf_identifier(arc.source_name());
if (find_port_size(arc.source_name()) > 1) {
os << "[" << arc.source_ipin() << "]";
os << "\\[" << arc.source_ipin() << "\\]";
}
os << " ";
os << escape_sdf_identifier(arc.sink_name());
if (find_port_size(arc.sink_name()) > 1) {
os << "[" << arc.sink_ipin() << "]";
os << "\\[" << arc.sink_ipin() << "\\]";
}
os << " ";
os << delay_triple.str();
@ -783,6 +784,11 @@ class Assignment {
void print_verilog(std::ostream& os, std::string indent) {
os << indent << "assign " << escape_verilog_identifier(lval_) << " = " << escape_verilog_identifier(rval_) << ";\n";
}
void print_merged_verilog(std::ostream& os, std::string indent) {
os << indent << "assign " << lval_ << " = " << rval_ << ";\n";
}
void print_blif(std::ostream& os, std::string indent) {
os << indent << ".names " << rval_ << " " << lval_ << "\n";
os << indent << "1 1\n";
@ -878,12 +884,8 @@ class NetlistWriterVisitor : public NetlistVisitor {
print_sdf();
}
private: //Internal Helper functions
///@brief Writes out the verilog netlist
void print_verilog(int depth = 0) {
verilog_os_ << indent(depth) << "//Verilog generated by VPR " << vtr::VERSION << " from post-place-and-route implementation\n";
verilog_os_ << indent(depth) << "module " << top_module_name_ << " (\n";
protected:
virtual void print_primary_io(int depth) {
//Primary Inputs
for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) {
verilog_os_ << indent(depth + 1) << "input " << escape_verilog_identifier(*iter);
@ -892,7 +894,6 @@ class NetlistWriterVisitor : public NetlistVisitor {
}
verilog_os_ << "\n";
}
//Primary Outputs
for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) {
verilog_os_ << indent(depth + 1) << "output " << escape_verilog_identifier(*iter);
@ -901,6 +902,22 @@ class NetlistWriterVisitor : public NetlistVisitor {
}
verilog_os_ << "\n";
}
}
virtual void print_assignments(int depth) {
verilog_os_ << "\n";
verilog_os_ << indent(depth + 1) << "//IO assignments\n";
for (auto& assign : assignments_) {
assign.print_verilog(verilog_os_, indent(depth + 1));
}
}
///@brief Writes out the verilog netlist
void print_verilog(int depth = 0) {
verilog_os_ << indent(depth) << "//Verilog generated by VPR " << vtr::VERSION << " from post-place-and-route implementation\n";
verilog_os_ << indent(depth) << "module " << top_module_name_ << " (\n";
print_primary_io(depth);
verilog_os_ << indent(depth) << ");\n";
//Wire declarations
@ -916,11 +933,7 @@ class NetlistWriterVisitor : public NetlistVisitor {
}
//connections between primary I/Os and their internal wires
verilog_os_ << "\n";
verilog_os_ << indent(depth + 1) << "//IO assignments\n";
for (auto& assign : assignments_) {
assign.print_verilog(verilog_os_, indent(depth + 1));
}
print_assignments(depth);
//Interconnect between cell instances
verilog_os_ << "\n";
@ -962,6 +975,7 @@ class NetlistWriterVisitor : public NetlistVisitor {
verilog_os_ << indent(depth) << "endmodule\n";
}
private: //Internal Helper functions
///@brief Writes out the blif netlist
void print_blif(int depth = 0) {
blif_os_ << indent(depth) << "#BLIF generated by VPR " << vtr::VERSION << " from post-place-and-route implementation\n";
@ -1066,47 +1080,6 @@ class NetlistWriterVisitor : public NetlistVisitor {
sdf_os_ << indent(depth) << ")\n";
}
/**
* @brief Returns the name of a wire connecting a primitive and global net.
*
* The wire is recorded and instantiated by the top level output routines.
*/
std::string make_inst_wire(AtomNetId atom_net_id, ///<The id of the net in the atom netlist
tatum::NodeId tnode_id, ///<The tnode associated with the primitive pin
std::string inst_name, ///<The name of the instance associated with the pin
PortType port_type, ///<The port direction
int port_idx, ///<The instance port index
int pin_idx) { ///<The instance pin index
std::string wire_name = inst_name;
if (port_type == PortType::INPUT) {
wire_name = join_identifier(wire_name, "input");
} else if (port_type == PortType::CLOCK) {
wire_name = join_identifier(wire_name, "clock");
} else {
VTR_ASSERT(port_type == PortType::OUTPUT);
wire_name = join_identifier(wire_name, "output");
}
wire_name = join_identifier(wire_name, std::to_string(port_idx));
wire_name = join_identifier(wire_name, std::to_string(pin_idx));
auto value = std::make_pair(wire_name, tnode_id);
if (port_type == PortType::INPUT || port_type == PortType::CLOCK) {
//Add the sink
logical_net_sinks_[atom_net_id].push_back(value);
} else {
//Add the driver
VTR_ASSERT(port_type == PortType::OUTPUT);
auto ret = logical_net_drivers_.insert(std::make_pair(atom_net_id, value));
VTR_ASSERT(ret.second); //Was inserted, drivers are unique
}
return wire_name;
}
/**
* @brief Returns the name of a circuit-level Input/Output
*
@ -1162,6 +1135,48 @@ class NetlistWriterVisitor : public NetlistVisitor {
return io_name;
}
protected:
/**
* @brief Returns the name of a wire connecting a primitive and global net.
*
* The wire is recorded and instantiated by the top level output routines.
*/
std::string make_inst_wire(AtomNetId atom_net_id, ///<The id of the net in the atom netlist
tatum::NodeId tnode_id, ///<The tnode associated with the primitive pin
std::string inst_name, ///<The name of the instance associated with the pin
PortType port_type, ///<The port direction
int port_idx, ///<The instance port index
int pin_idx) { ///<The instance pin index
std::string wire_name = inst_name;
if (port_type == PortType::INPUT) {
wire_name = join_identifier(wire_name, "input");
} else if (port_type == PortType::CLOCK) {
wire_name = join_identifier(wire_name, "clock");
} else {
VTR_ASSERT(port_type == PortType::OUTPUT);
wire_name = join_identifier(wire_name, "output");
}
wire_name = join_identifier(wire_name, std::to_string(port_idx));
wire_name = join_identifier(wire_name, std::to_string(pin_idx));
auto value = std::make_pair(wire_name, tnode_id);
if (port_type == PortType::INPUT || port_type == PortType::CLOCK) {
//Add the sink
logical_net_sinks_[atom_net_id].push_back(value);
} else {
//Add the driver
VTR_ASSERT(port_type == PortType::OUTPUT);
auto ret = logical_net_drivers_.insert(std::make_pair(atom_net_id, value));
VTR_ASSERT(ret.second); //Was inserted, drivers are unique
}
return wire_name;
}
///@brief Returns an Instance object representing the LUT
std::shared_ptr<Instance> make_lut_instance(const t_pb* atom) {
//Determine what size LUT
@ -1777,18 +1792,6 @@ class NetlistWriterVisitor : public NetlistVisitor {
return top_pb->pb_route;
}
///@brief Returns the top complex block which contains the given pb
const t_pb* find_top_cb(const t_pb* curr) {
//Walk up through the pb graph until curr
//has no parent, at which point it will be the top pb
const t_pb* parent = curr->parent_pb;
while (parent != nullptr) {
curr = parent;
parent = curr->parent_pb;
}
return curr;
}
///@brief Returns the tnode ID of the given atom's connected cluster pin
tatum::NodeId find_tnode(const t_pb* atom, int cluster_pin_idx) {
auto& atom_ctx = g_vpr_ctx.atom();
@ -1806,6 +1809,19 @@ class NetlistWriterVisitor : public NetlistVisitor {
return tnode_id;
}
private:
///@brief Returns the top complex block which contains the given pb
const t_pb* find_top_cb(const t_pb* curr) {
//Walk up through the pb graph until curr
//has no parent, at which point it will be the top pb
const t_pb* parent = curr->parent_pb;
while (parent != nullptr) {
curr = parent;
parent = curr->parent_pb;
}
return curr;
}
///@brief Returns a LogicVec representing the LUT mask of the given LUT atom
LogicVec load_lut_mask(size_t num_inputs, //LUT size
const t_pb* atom) { //LUT primitive
@ -2063,13 +2079,15 @@ class NetlistWriterVisitor : public NetlistVisitor {
return ::get_delay_ps(delay_sec); //Class overload hides file-scope by default
}
private: //Data
std::string top_module_name_; ///<Name of the top level module (i.e. the circuit)
private: //Data
std::string top_module_name_; ///<Name of the top level module (i.e. the circuit)
protected:
std::vector<std::string> inputs_; ///<Name of circuit inputs
std::vector<std::string> outputs_; ///<Name of circuit outputs
std::vector<Assignment> assignments_; ///<Set of assignments (i.e. net-to-net connections)
std::vector<std::shared_ptr<Instance>> cell_instances_; ///<Set of cell instances
private:
//Drivers of logical nets.
// Key: logic net id, Value: pair of wire_name and tnode_id
std::map<AtomNetId, std::pair<std::string, tatum::NodeId>> logical_net_drivers_;
@ -2080,7 +2098,10 @@ class NetlistWriterVisitor : public NetlistVisitor {
std::map<std::string, float> logical_net_sink_delays_;
//Output streams
protected:
std::ostream& verilog_os_;
private:
std::ostream& blif_os_;
std::ostream& sdf_os_;
@ -2091,11 +2112,192 @@ class NetlistWriterVisitor : public NetlistVisitor {
struct t_analysis_opts opts_;
};
/**
* @brief A class which writes post-implementation merged netlists (Verilog)
*
* It implements the NetlistVisitor interface used by NetlistWalker (see netlist_walker.h)
*/
class MergedNetlistWriterVisitor : public NetlistWriterVisitor {
public: //Public interface
MergedNetlistWriterVisitor(std::ostream& verilog_os, ///<Output stream for verilog netlist
std::ostream& blif_os, ///<Output stream for blif netlist
std::ostream& sdf_os, ///<Output stream for SDF
std::shared_ptr<const AnalysisDelayCalculator> delay_calc,
struct t_analysis_opts opts)
: NetlistWriterVisitor(verilog_os, blif_os, sdf_os, delay_calc, opts) {}
std::map<std::string, int> portmap;
void visit_atom_impl(const t_pb* atom) override {
auto& atom_ctx = g_vpr_ctx.atom();
auto atom_pb = atom_ctx.lookup.pb_atom(atom);
if (atom_pb == AtomBlockId::INVALID()) {
return;
}
const t_model* model = atom_ctx.nlist.block_model(atom_pb);
if (model->name == std::string(MODEL_INPUT)) {
auto merged_io_name = make_io(atom, PortType::INPUT);
if (merged_io_name != "")
inputs_.emplace_back(merged_io_name);
} else if (model->name == std::string(MODEL_OUTPUT)) {
auto merged_io_name = make_io(atom, PortType::OUTPUT);
if (merged_io_name != "")
outputs_.emplace_back(merged_io_name);
} else if (model->name == std::string(MODEL_NAMES)) {
cell_instances_.push_back(make_lut_instance(atom));
} else if (model->name == std::string(MODEL_LATCH)) {
cell_instances_.push_back(make_latch_instance(atom));
} else if (model->name == std::string("single_port_ram")) {
cell_instances_.push_back(make_ram_instance(atom));
} else if (model->name == std::string("dual_port_ram")) {
cell_instances_.push_back(make_ram_instance(atom));
} else if (model->name == std::string("multiply")) {
cell_instances_.push_back(make_multiply_instance(atom));
} else if (model->name == std::string("adder")) {
cell_instances_.push_back(make_adder_instance(atom));
} else {
cell_instances_.push_back(make_blackbox_instance(atom));
}
}
/**
* @brief Returns the name of circuit-level Input/Output ports with multi-bit
* ports merged into one.
*
* The I/O is recorded and instantiated by the top level output routines
* @param atom The implementation primitive representing the I/O
* @param dir The IO direction
* @param portmap Map for keeping port names and width
*/
std::string make_io(const t_pb* atom,
PortType dir) {
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
std::string io_name;
std::string indexed_io_name;
int cluster_pin_idx = -1;
// regex for matching 3 groups:
// * 'out:' - optional
// * verilog identifier - mandatory
// * index - optional
std::string rgx = "(out:)?([a-zA-Z$_]+[a-zA-Z0-9$_]*)(\\[[0-9]+\\])?$";
std::string name(atom->name);
std::regex regex(rgx);
std::smatch matches;
if (dir == PortType::INPUT) {
VTR_ASSERT(pb_graph_node->num_output_ports == 1); //One output port
VTR_ASSERT(pb_graph_node->num_output_pins[0] == 1); //One output pin
cluster_pin_idx = pb_graph_node->output_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
io_name = "";
indexed_io_name = atom->name;
if (std::regex_match(name, matches, regex)) {
if (std::find(inputs_.begin(), inputs_.end(), matches[2]) == inputs_.end()) { //Skip already existing multi-bit port names
io_name = matches[2];
portmap[matches[2]] = 0;
} else {
portmap[matches[2]]++;
}
}
} else {
VTR_ASSERT(pb_graph_node->num_input_ports == 1); //One input port
VTR_ASSERT(pb_graph_node->num_input_pins[0] == 1); //One input pin
cluster_pin_idx = pb_graph_node->input_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
//Strip off the starting 'out:' that vpr adds to uniqify outputs
//this makes the port names match the input blif file
io_name = "";
indexed_io_name = atom->name + 4;
if (std::regex_search(name, matches, regex)) {
if (std::find(outputs_.begin(), outputs_.end(), matches[2]) == outputs_.end()) { //Skip already existing multi-bit port names
portmap[matches[2]] = 0;
io_name = matches[2];
} else {
portmap[matches[2]]++;
}
}
}
const auto& top_pb_route = find_top_pb_route(atom);
if (top_pb_route.count(cluster_pin_idx)) {
//Net exists
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; //Connected net in atom netlist
//Port direction is inverted (inputs drive internal nets, outputs sink internal nets)
PortType wire_dir = (dir == PortType::INPUT) ? PortType::OUTPUT : PortType::INPUT;
//Look up the tnode associated with this pin (used for delay calculation)
tatum::NodeId tnode_id = find_tnode(atom, cluster_pin_idx);
auto wire_name = make_inst_wire(atom_net_id, tnode_id, indexed_io_name, wire_dir, 0, 0);
//Connect the wires to to I/Os with assign statements
if (wire_dir == PortType::INPUT) {
assignments_.emplace_back(indexed_io_name, escape_verilog_identifier(wire_name));
} else {
assignments_.emplace_back(escape_verilog_identifier(wire_name), indexed_io_name);
}
}
return io_name;
}
void print_primary_io(int depth) {
//Primary Inputs
for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) {
//verilog_os_ << indent(depth + 1) << "input " << escape_verilog_identifier(*iter);
std::string range;
if (portmap[*iter] > 0)
verilog_os_ << indent(depth + 1) << "input [" << portmap[*iter] << ":0] " << *iter;
else
verilog_os_ << indent(depth + 1) << "input " << *iter;
if (iter + 1 != inputs_.end() || outputs_.size() > 0) {
verilog_os_ << ",";
}
verilog_os_ << "\n";
}
//Primary Outputs
for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) {
std::string range;
if (portmap[*iter] > 0)
verilog_os_ << indent(depth + 1) << "output [" << portmap[*iter] << ":0] " << *iter;
else
verilog_os_ << indent(depth + 1) << "output " << *iter;
if (iter + 1 != outputs_.end()) {
verilog_os_ << ",";
}
verilog_os_ << "\n";
}
}
void print_assignments(int depth) {
verilog_os_ << "\n";
verilog_os_ << indent(depth + 1) << "//IO assignments\n";
for (auto& assign : assignments_) {
assign.print_merged_verilog(verilog_os_, indent(depth + 1));
}
}
void finish_impl() override {
// Don't write to blif and sdf streams
print_verilog();
}
};
//
// Externally Accessible Functions
//
///@brief Main routing for this file. See netlist_writer.h for details.
///@brief Main routine for this file. See netlist_writer.h for details.
void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc, struct t_analysis_opts opts) {
std::string verilog_filename = basename + "_post_synthesis.v";
std::string blif_filename = basename + "_post_synthesis.blif";
@ -2116,6 +2318,23 @@ void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDe
nl_walker.walk();
}
///@brief Main routine for this file. See netlist_writer.h for details.
void merged_netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc, struct t_analysis_opts opts) {
std::string verilog_filename = basename + "_merged_post_implementation.v";
VTR_LOG("Writing Implementation Netlist: %s\n", verilog_filename.c_str());
std::ofstream verilog_os(verilog_filename);
// Don't write blif and sdf, pass dummy streams
std::ofstream blif_os;
std::ofstream sdf_os;
MergedNetlistWriterVisitor visitor(verilog_os, blif_os, sdf_os, delay_calc, opts);
NetlistWalker nl_walker(visitor);
nl_walker.walk();
}
//
// File-scope function implementations
//

View File

@ -17,4 +17,14 @@
*/
void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc, struct t_analysis_opts opts);
/**
* @brief Writes out the post implementation netlist in Verilog format.
* It has its top module ports merged into multi-bit ones.
*
* Written filename ends in {basename}_merged_post_implementation.v where {basename} is the
* basename argument.
*/
void merged_netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc, struct t_analysis_opts opts);
#endif

View File

@ -1725,6 +1725,13 @@ argparse::ArgumentParser create_arg_parser(std::string prog_name, t_options& arg
.default_value("off")
.show_in(argparse::ShowIn::HELP_ONLY);
analysis_grp.add_argument<bool, ParseOnOff>(args.Generate_Post_Implementation_Merged_Netlist, "--gen_post_implementation_merged_netlist")
.help(
"Generates the post-implementation netlist with merged top module ports"
" Used for post-implementation simulation and verification")
.default_value("off")
.show_in(argparse::ShowIn::HELP_ONLY);
analysis_grp.add_argument(args.timing_report_npaths, "--timing_report_npaths")
.help("Controls how many timing paths are reported.")
.default_value("100")

View File

@ -156,6 +156,7 @@ struct t_options {
/* Analysis options */
argparse::ArgValue<bool> full_stats;
argparse::ArgValue<bool> Generate_Post_Synthesis_Netlist;
argparse::ArgValue<bool> Generate_Post_Implementation_Merged_Netlist;
argparse::ArgValue<int> timing_report_npaths;
argparse::ArgValue<e_timing_report_detail> timing_report_detail;
argparse::ArgValue<bool> timing_report_skew;

View File

@ -1199,6 +1199,11 @@ void vpr_analysis(t_vpr_setup& vpr_setup, const t_arch& Arch, const RouteStatus&
vpr_setup.AnalysisOpts);
}
//Write the post-implementation merged netlist
if (vpr_setup.AnalysisOpts.gen_post_implementation_merged_netlist) {
merged_netlist_writer(atom_ctx.nlist.netlist_name().c_str(), analysis_delay_calc, vpr_setup.AnalysisOpts);
}
//Do power analysis
if (vpr_setup.PowerOpts.do_power) {
vpr_power_estimation(vpr_setup, Arch, *timing_info, route_status);

View File

@ -966,6 +966,7 @@ struct t_analysis_opts {
e_stage_action doAnalysis;
bool gen_post_synthesis_netlist;
bool gen_post_implementation_merged_netlist;
e_post_synth_netlist_unconn_handling post_synth_netlist_unconn_input_handling;
e_post_synth_netlist_unconn_handling post_synth_netlist_unconn_output_handling;