blob: 7a968949e205cf4129f1a9cd6cdf223617534cee [file] [log] [blame]
#include <fstream>
#include <iostream>
#include <sstream>
#include <set>
#include <map>
#include <vector>
#include <string>
#include <algorithm>
#include <bitset>
#include <memory>
#include <unordered_set>
#include <cmath>
#include "vtr_assert.h"
#include "vtr_util.h"
#include "vtr_log.h"
#include "vtr_logic.h"
#include "vtr_version.h"
#include "vpr_error.h"
#include "netlist_walker.h"
#include "netlist_writer.h"
#include "globals.h"
#include "atom_netlist.h"
#include "atom_netlist_utils.h"
#include "logic_vec.h"
//Overview
//========
//
//This file implements the netlist writer which generates post-implementation (i.e. post-routing) netlists
//in BLIF and Verilog formats, along with an SDF file which can be used for timing back-annotation (e.g. for
//timing simulation).
//
//The externally facing function from this module is netlist_writer() which will generate the netlist and SDF
//output files. netlist_writer() assumes that a post-routing implementation is available and that there is a
//valid timing graph with real delays annotated (these delays are used to create the SDF).
//
//Implementation
//==============
//
//The netlist writer is primarily driven by the NetlistWriterVisitor class, which walks the netlist using the
//NetlistWalker (see netlist_walker.h). The netlist walker calls NetlistWriterVisitor's visit_atom_impl()
//method for every atom (i.e. primitive circuit element) in VPR's internal data structures.
//
//visit_atom_impl() then dispatches the atom to the appropriate NetlistWriterVisitor member function
//(e.g. LUTs to make_lut_instance(), Latches to make_latch_instance(), etc.). Each of the make_*_instance()
//functions records the external nets the atom connects to (see make_inst_wire()) and constructs a concrete
//'Instance' object to represent the primitive. NetlistWriterVisitor saves these representations for later
//output.
//
//'Instance' is an abstract class representing objects which know how to create thier own representation in
//BLIF, Verilog and SDF formats. Most primitives can be represented by the BlackBoxInst class, but special
//primitives like LUTs and Latchs have thier own implementations (LutInst, LatchInst) to handle some of their
//unique requirements.
//
//Once the entire netlist has been traversed the netlist walker will call NetlistWriterVisitor's finish_impl()
//method which kicks off the generation of the actual netlists and SDF files. NetlistWriterVisitor's print_*()
//methods setting up file-level global information (I/Os, net declarations etc.) and then ask each Instance to
//print itself in the appropriate format to the appropriate file.
//
//Name Escaping
//=============
//One of the challenges in generating netlists is producing consistent naming of netlist elements.
//In particular valid BLIF names, Verilog names and SDF names are not neccessarily the same.
//As a result we must escape invalid characters/identifiers when producing some formats.
//
//All name escaping is handled at the output stage (i.e. in the print_*() functions) since it is
//very format dependant.
//
//VPR stores names internally generally following BLIF conventions. As a result these names need
//to be escaped when generating Verilog or SDF. This is handled with escape_verilog_identifier()
//and escape_sdf_identifier() functions.
//
//Primitives
//==========
//Verilog netlist generation assumes the existance of appropriate primitives for the various
//atom types (i.e. a LUT_K module, a DFF module etc.). These are currently defined the file
//<vtr>/vtr_flow/primitives.v, where <vtr> is the root of the VTR source tree.
//
//You will typically need to link with this file when attempting to simulate a post-implementation netlist.
//Also see the comments in primitives.v for important notes related to performing SDF back-annotated timing
//simulation.
//Enable for extra output while calculating LUT masks
//#define DEBUG_LUT_MASK
//
//File local type declarations
//
/*enum class PortType {
* IN,
* OUT,
* CLOCK
* };*/
//
// File local function declarations
//
std::string indent(size_t depth);
double get_delay_ps(double delay_sec);
void print_blif_port(std::ostream& os, size_t& unconn_count, const std::string& port_name, const std::vector<std::string>& nets, int depth);
void print_verilog_port(std::ostream& os, const std::string& port_name, const std::vector<std::string>& nets, PortType type, int depth);
std::string create_unconn_net(size_t& unconn_count);
std::string escape_verilog_identifier(const std::string id);
std::string escape_sdf_identifier(const std::string id);
bool is_special_sdf_char(char c);
std::string join_identifier(std::string lhs, std::string rhs);
//
//
// File local class and function implementations
//
//
//A combinational timing arc
class Arc {
public:
Arc(std::string src_port, //Source of the arc
int src_ipin, //Source pin index
std::string snk_port, //Sink of the arc
int snk_ipin, //Sink pin index
float del, //Delay on this arc
std::string cond = "") //Condition associated with the arc
: source_name_(src_port)
, source_ipin_(src_ipin)
, sink_name_(snk_port)
, sink_ipin_(snk_ipin)
, delay_(del)
, condition_(cond) {}
//Accessors
std::string source_name() const { return source_name_; }
int source_ipin() const { return source_ipin_; }
std::string sink_name() const { return sink_name_; }
int sink_ipin() const { return sink_ipin_; }
double delay() const { return delay_; }
std::string condition() const { return condition_; }
private:
std::string source_name_;
int source_ipin_;
std::string sink_name_;
int sink_ipin_;
double delay_;
std::string condition_;
};
//Instance is an interface used to represent an element instantiated in a netlist
//
//Instances know how to describe themselves in BLIF, Verilog and SDF
//
//This should be subclassed to implement support for new primitive types (although
//see BlackBoxInst for a general implementation for a black box primitive)
class Instance {
public:
virtual ~Instance() = default;
//Print the current instance in blif format
//
// os - The output stream to print to
// unconn_count - The current count of unconnected nets. BLIF has limitations requiring
// unconnected nets to be used to represent unconnected ports. To allow
// unique naming of these nets unconn_count is used to uniquify these names.
// Whenever creating an unconnected net (and using unconn_count to uniquify
// its name in the file) unconn_count should be incremented.
// depth - Current indentation depth. This is used to figure-out how much indentation
// should be applied. This is purely for cosmetic formatting. Use indent() for
// generating consistent indentation.
virtual void print_blif(std::ostream& os, size_t& unconn_count, int depth = 0) = 0;
//Print the current instanse in Verilog, see print_blif() for argument descriptions
virtual void print_verilog(std::ostream& os, int depth = 0) = 0;
//Print the current instanse in SDF, see print_blif() for argument descriptions
virtual void print_sdf(std::ostream& os, int depth = 0) = 0;
};
//An instance representing a Look-Up Table
class LutInst : public Instance {
public: //Public methods
LutInst(size_t lut_size, //The LUT size
LogicVec lut_mask, //The LUT mask representing the logic function
std::string inst_name, //The name of this instance
std::map<std::string, std::vector<std::string>> port_conns, //The port connections of this instance. Key: port name, Value: connected nets
std::vector<Arc> timing_arc_values) //The timing arcs of this instance
: type_("LUT_K")
, lut_size_(lut_size)
, lut_mask_(lut_mask)
, inst_name_(inst_name)
, port_conns_(port_conns)
, timing_arcs_(timing_arc_values) {
}
//Accessors
const std::vector<Arc>& timing_arcs() { return timing_arcs_; }
std::string instance_name() { return inst_name_; }
std::string type() { return type_; }
public: //Instance interface method implementations
void print_verilog(std::ostream& os, int depth) override {
//Instantiate the lut
os << indent(depth) << type_ << " #(\n";
os << indent(depth + 1) << ".K(" << lut_size_ << "),\n";
std::stringstream param_ss;
param_ss << lut_mask_;
os << indent(depth + 1) << ".LUT_MASK(" << param_ss.str() << ")\n";
os << indent(depth) << ") " << escape_verilog_identifier(inst_name_) << " (\n";
VTR_ASSERT(port_conns_.count("in"));
VTR_ASSERT(port_conns_.count("out"));
VTR_ASSERT(port_conns_.size() == 2);
print_verilog_port(os, "in", port_conns_["in"], PortType::INPUT, depth + 1);
os << ","
<< "\n";
print_verilog_port(os, "out", port_conns_["out"], PortType::OUTPUT, depth + 1);
os << "\n";
os << indent(depth) << ");\n\n";
}
void print_blif(std::ostream& os, size_t& unconn_count, int depth) override {
os << indent(depth) << ".names ";
//Input nets
for (const auto& net : port_conns_["in"]) {
if (net == "") {
//Disconnected
os << create_unconn_net(unconn_count) << " ";
} else {
os << net << " ";
}
}
VTR_ASSERT(port_conns_["out"].size() == 1);
//Output net
auto out_net = port_conns_["out"][0];
if (out_net == "") {
//Disconnected
os << create_unconn_net(unconn_count) << " ";
} else {
os << out_net << " ";
}
os << "\n";
//Write out the truth table.
//
// We can write out the truth table as either a set of minterms (where the function is true),
// or a set of maxterms (where the function is false). We choose the representation which produces
// the fewest terms (and is smallest to represent in BLIF).
//Count the number of set minterms
size_t num_set_minterms = 0;
for (size_t i = 0; i < lut_mask_.size(); ++i) {
if (lut_mask_[i] == vtr::LogicValue::TRUE) {
++num_set_minterms;
}
}
vtr::LogicValue output_value;
if (num_set_minterms < lut_mask_.size() / 2) {
//Less than half the minterms are true, so
//output minterms
output_value = vtr::LogicValue::TRUE;
} else {
//Half-or-more minterms are true, so
//output maxterms
output_value = vtr::LogicValue::FALSE;
}
size_t minterms_set = 0;
size_t maxterms_set = 0;
for (size_t minterm = 0; minterm < lut_mask_.size(); minterm++) {
if (lut_mask_[minterm] == output_value) {
//Convert the minterm to a string of bits
std::string bit_str = std::bitset<64>(minterm).to_string();
//Because BLIF puts the input values in order from LSB (left) to MSB (right), we need
//to reverse the string
std::reverse(bit_str.begin(), bit_str.end());
//Trim to the LUT size
std::string input_values(bit_str.begin(), bit_str.begin() + (lut_size_));
//Add the row as true
os << indent(depth) << input_values;
//Specify whether this is a minterm (on-set) or max-term (off-set)
if (output_value == vtr::LogicValue::TRUE) {
os << " 1\n";
minterms_set++;
} else {
VTR_ASSERT(output_value == vtr::LogicValue::FALSE);
os << " 0\n";
maxterms_set++;
}
}
}
if (minterms_set == 0 && maxterms_set == 0) {
//Handle the always true/false case
for (size_t i = 0; i < port_conns_["in"].size(); ++i) {
os << "-"; //Don't care for all inputs
}
if (output_value == vtr::LogicValue::TRUE) {
//Always false
os << " 0\n";
} else {
VTR_ASSERT(output_value == vtr::LogicValue::FALSE);
//Always true
os << " 1\n";
}
}
os << "\n";
}
void print_sdf(std::ostream& os, int depth) override {
os << indent(depth) << "(CELL\n";
os << indent(depth + 1) << "(CELLTYPE \"" << type() << "\")\n";
os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(instance_name()) << ")\n";
if (!timing_arcs().empty()) {
os << indent(depth + 1) << "(DELAY\n";
os << indent(depth + 2) << "(ABSOLUTE\n";
for (auto& arc : timing_arcs()) {
double delay_ps = arc.delay();
std::stringstream delay_triple;
delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")";
os << indent(depth + 3) << "(IOPATH ";
//Note we do not escape the last index of multi-bit signals since they are used to
//match multi-bit ports
os << escape_sdf_identifier(arc.source_name()) << "[" << arc.source_ipin() << "]"
<< " ";
VTR_ASSERT(arc.sink_ipin() == 0); //Should only be one output
os << escape_sdf_identifier(arc.sink_name()) << " ";
os << delay_triple.str() << " " << delay_triple.str() << ")\n";
}
os << indent(depth + 2) << ")\n";
os << indent(depth + 1) << ")\n";
}
os << indent(depth) << ")\n";
os << indent(depth) << "\n";
}
private:
std::string type_;
size_t lut_size_;
LogicVec lut_mask_;
std::string inst_name_;
std::map<std::string, std::vector<std::string>> port_conns_;
std::vector<Arc> timing_arcs_;
};
class LatchInst : public Instance {
public: //Public types
//Types of latches (defined by BLIF)
enum class Type {
RISING_EDGE,
FALLING_EDGE,
ACTIVE_HIGH,
ACTIVE_LOW,
ASYNCHRONOUS,
};
friend std::ostream& operator<<(std::ostream& os, const Type& type) {
if (type == Type::RISING_EDGE)
os << "re";
else if (type == Type::FALLING_EDGE)
os << "fe";
else if (type == Type::ACTIVE_HIGH)
os << "ah";
else if (type == Type::ACTIVE_LOW)
os << "al";
else if (type == Type::ASYNCHRONOUS)
os << "as";
else
VTR_ASSERT(false);
return os;
}
friend std::istream& operator>>(std::istream& is, Type& type) {
std::string tok;
is >> tok;
if (tok == "re")
type = Type::RISING_EDGE;
else if (tok == "fe")
type = Type::FALLING_EDGE;
else if (tok == "ah")
type = Type::ACTIVE_HIGH;
else if (tok == "al")
type = Type::ACTIVE_LOW;
else if (tok == "as")
type = Type::ASYNCHRONOUS;
else
VTR_ASSERT(false);
return is;
}
public:
LatchInst(std::string inst_name, //Name of this instance
std::map<std::string, std::string> port_conns, //Instance's port-to-net connections
Type type, //Type of this latch
vtr::LogicValue init_value, //Initial value of the latch
double tcq = std::numeric_limits<double>::quiet_NaN(), //Clock-to-Q delay
double tsu = std::numeric_limits<double>::quiet_NaN(), //Setup time
double thld = std::numeric_limits<double>::quiet_NaN()) //Hold time
: instance_name_(inst_name)
, port_connections_(port_conns)
, type_(type)
, initial_value_(init_value)
, tcq_(tcq)
, tsu_(tsu)
, thld_(thld) {}
void print_blif(std::ostream& os, size_t& /*unconn_count*/, int depth = 0) override {
os << indent(depth) << ".latch"
<< " ";
//Input D port
auto d_port_iter = port_connections_.find("D");
VTR_ASSERT(d_port_iter != port_connections_.end());
os << d_port_iter->second << " ";
//Output Q port
auto q_port_iter = port_connections_.find("Q");
VTR_ASSERT(q_port_iter != port_connections_.end());
os << q_port_iter->second << " ";
//Latch type
os << type_ << " "; //Type, i.e. rising-edge
//Control input
auto control_port_iter = port_connections_.find("clock");
VTR_ASSERT(control_port_iter != port_connections_.end());
os << control_port_iter->second << " "; //e.g. clock
os << (int)initial_value_ << " "; //Init value: e.g. 2=don't care
os << "\n";
}
void print_verilog(std::ostream& os, int depth = 0) override {
//Currently assume a standard DFF
VTR_ASSERT(type_ == Type::RISING_EDGE);
os << indent(depth) << "DFF"
<< " #(\n";
os << indent(depth + 1) << ".INITIAL_VALUE(";
if (initial_value_ == vtr::LogicValue::TRUE)
os << "1'b1";
else if (initial_value_ == vtr::LogicValue::FALSE)
os << "1'b0";
else if (initial_value_ == vtr::LogicValue::DONT_CARE)
os << "1'bx";
else if (initial_value_ == vtr::LogicValue::UNKOWN)
os << "1'bx";
else
VTR_ASSERT(false);
os << ")\n";
os << indent(depth) << ") " << escape_verilog_identifier(instance_name_) << " (\n";
for (auto iter = port_connections_.begin(); iter != port_connections_.end(); ++iter) {
os << indent(depth + 1) << "." << iter->first << "(" << escape_verilog_identifier(iter->second) << ")";
if (iter != --port_connections_.end()) {
os << ", ";
}
os << "\n";
}
os << indent(depth) << ");";
os << "\n";
}
void print_sdf(std::ostream& os, int depth = 0) override {
VTR_ASSERT(type_ == Type::RISING_EDGE);
os << indent(depth) << "(CELL\n";
os << indent(depth + 1) << "(CELLTYPE \""
<< "DFF"
<< "\")\n";
os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(instance_name_) << ")\n";
//Clock to Q
if (!std::isnan(tcq_)) {
os << indent(depth + 1) << "(DELAY\n";
os << indent(depth + 2) << "(ABSOLUTE\n";
double delay_ps = get_delay_ps(tcq_);
std::stringstream delay_triple;
delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")";
os << indent(depth + 3) << "(IOPATH "
<< "(posedge clock) Q " << delay_triple.str() << " " << delay_triple.str() << ")\n";
os << indent(depth + 2) << ")\n";
os << indent(depth + 1) << ")\n";
}
//Setup/Hold
if (!std::isnan(tsu_) || !std::isnan(thld_)) {
os << indent(depth + 1) << "(TIMINGCHECK\n";
if (!std::isnan(tsu_)) {
std::stringstream setup_triple;
double setup_ps = get_delay_ps(tsu_);
setup_triple << "(" << setup_ps << ":" << setup_ps << ":" << setup_ps << ")";
os << indent(depth + 2) << "(SETUP D (posedge clock) " << setup_triple.str() << ")\n";
}
if (!std::isnan(thld_)) {
std::stringstream hold_triple;
double hold_ps = get_delay_ps(thld_);
hold_triple << "(" << hold_ps << ":" << hold_ps << ":" << hold_ps << ")";
os << indent(depth + 2) << "(HOLD D (posedge clock) " << hold_triple.str() << ")\n";
}
}
os << indent(depth + 1) << ")\n";
os << indent(depth) << ")\n";
os << indent(depth) << "\n";
}
private:
std::string instance_name_;
std::map<std::string, std::string> port_connections_;
Type type_;
vtr::LogicValue initial_value_;
double tcq_; //Clock delay + tcq
double tsu_; //Setup time
double thld_; //Hold time
};
class BlackBoxInst : public Instance {
public:
BlackBoxInst(std::string type_name, //Instance type
std::string inst_name, //Instance name
std::map<std::string, std::string> params, //Verilog parameters: Dictonary of <param_name,value>
std::map<std::string, std::string> attrs, //Instance attributes: Dictonary of <attr_name,value>
std::map<std::string, std::vector<std::string>> input_port_conns, //Port connections: Dictionary of <port,nets>
std::map<std::string, std::vector<std::string>> output_port_conns, //Port connections: Dictionary of <port,nets>
std::vector<Arc> timing_arcs, //Combinational timing arcs
std::map<std::string, double> ports_tsu, //Port setup checks
std::map<std::string, double> ports_tcq) //Port clock-to-q delays
: type_name_(type_name)
, inst_name_(inst_name)
, params_(params)
, attrs_(attrs)
, input_port_conns_(input_port_conns)
, output_port_conns_(output_port_conns)
, timing_arcs_(timing_arcs)
, ports_tsu_(ports_tsu)
, ports_tcq_(ports_tcq) {}
void print_blif(std::ostream& os, size_t& unconn_count, int depth = 0) override {
os << indent(depth) << ".subckt " << type_name_ << " \\"
<< "\n";
//Input ports
for (auto iter = input_port_conns_.begin(); iter != input_port_conns_.end(); ++iter) {
const auto& port_name = iter->first;
const auto& nets = iter->second;
print_blif_port(os, unconn_count, port_name, nets, depth + 1);
if (!(iter == --input_port_conns_.end() && output_port_conns_.empty())) {
os << " \\";
}
os << "\n";
}
//Output ports
for (auto iter = output_port_conns_.begin(); iter != output_port_conns_.end(); ++iter) {
const auto& port_name = iter->first;
const auto& nets = iter->second;
print_blif_port(os, unconn_count, port_name, nets, depth + 1);
if (!(iter == --output_port_conns_.end())) {
os << " \\";
}
os << "\n";
}
// Params
for (auto iter = params_.begin(); iter != params_.end(); ++iter) {
os << ".param " << iter->first << " " << iter->second << "\n";
}
// Attrs
for (auto iter = attrs_.begin(); iter != attrs_.end(); ++iter) {
os << ".attr " << iter->first << " " << iter->second << "\n";
}
os << "\n";
}
void print_verilog(std::ostream& os, int depth = 0) override {
//Instance type
os << indent(depth) << type_name_ << " #(\n";
//Verilog parameters
for (auto iter = params_.begin(); iter != params_.end(); ++iter) {
os << indent(depth + 1) << "." << iter->first << "(" << iter->second << ")";
if (iter != --params_.end()) {
os << ",";
}
os << "\n";
}
//Instance name
os << indent(depth) << ") " << escape_verilog_identifier(inst_name_) << " (\n";
//Input Port connections
for (auto iter = input_port_conns_.begin(); iter != input_port_conns_.end(); ++iter) {
auto& port_name = iter->first;
auto& nets = iter->second;
print_verilog_port(os, port_name, nets, PortType::INPUT, depth + 1);
if (!(iter == --input_port_conns_.end() && output_port_conns_.empty())) {
os << ",";
}
os << "\n";
}
//Output Port connections
for (auto iter = output_port_conns_.begin(); iter != output_port_conns_.end(); ++iter) {
auto& port_name = iter->first;
auto& nets = iter->second;
print_verilog_port(os, port_name, nets, PortType::OUTPUT, depth + 1);
if (!(iter == --output_port_conns_.end())) {
os << ",";
}
os << "\n";
}
os << indent(depth) << ");\n";
os << "\n";
}
void print_sdf(std::ostream& os, int depth = 0) override {
os << indent(depth) << "(CELL\n";
os << indent(depth + 1) << "(CELLTYPE \"" << type_name_ << "\")\n";
os << indent(depth + 1) << "(INSTANCE " << escape_sdf_identifier(inst_name_) << ")\n";
os << indent(depth + 1) << "(DELAY\n";
if (!timing_arcs_.empty() || !ports_tcq_.empty()) {
os << indent(depth + 2) << "(ABSOLUTE\n";
//Combinational paths
for (const auto& arc : timing_arcs_) {
double delay_ps = get_delay_ps(arc.delay());
std::stringstream delay_triple;
delay_triple << "(" << delay_ps << ":" << delay_ps << ":" << delay_ps << ")";
//Note that we explicitly do not escape the last array indexing so an SDF
//reader will treat the ports as multi-bit
//
//We also only put the last index in if the port has multiple bits
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 << " ";
os << escape_sdf_identifier(arc.sink_name());
if (find_port_size(arc.sink_name()) > 1) {
os << "[" << arc.sink_ipin() << "]";
}
os << " ";
os << delay_triple.str();
os << ")\n";
}
//Clock-to-Q delays
for (auto kv : ports_tcq_) {
double clock_to_q_ps = get_delay_ps(kv.second);
std::stringstream delay_triple;
delay_triple << "(" << clock_to_q_ps << ":" << clock_to_q_ps << ":" << clock_to_q_ps << ")";
os << indent(depth + 3) << "(IOPATH (posedge clock) " << escape_sdf_identifier(kv.first) << " " << delay_triple.str() << " " << delay_triple.str() << ")\n";
}
os << indent(depth + 2) << ")\n"; //ABSOLUTE
}
os << indent(depth + 1) << ")\n"; //DELAY
if (!ports_tsu_.empty() || !ports_thld_.empty()) {
//Setup checks
os << indent(depth + 1) << "(TIMINGCHECK\n";
for (auto kv : ports_tsu_) {
double setup_ps = get_delay_ps(kv.second);
std::stringstream delay_triple;
delay_triple << "(" << setup_ps << ":" << setup_ps << ":" << setup_ps << ")";
os << indent(depth + 2) << "(SETUP " << escape_sdf_identifier(kv.first) << " (posedge clock) " << delay_triple.str() << ")\n";
}
for (auto kv : ports_thld_) {
double hold_ps = get_delay_ps(kv.second);
std::stringstream delay_triple;
delay_triple << "(" << hold_ps << ":" << hold_ps << ":" << hold_ps << ")";
os << indent(depth + 2) << "(HOLD " << escape_sdf_identifier(kv.first) << " (posedge clock) " << delay_triple.str() << ")\n";
}
os << indent(depth + 1) << ")\n"; //TIMINGCHECK
}
os << indent(depth) << ")\n"; //CELL
}
size_t find_port_size(std::string port_name) {
auto iter = input_port_conns_.find(port_name);
if (iter != input_port_conns_.end()) {
return iter->second.size();
}
iter = output_port_conns_.find(port_name);
if (iter != output_port_conns_.end()) {
return iter->second.size();
}
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Could not find port %s on %s of type %s\n", port_name.c_str(), inst_name_.c_str(), type_name_.c_str());
return -1; //Suppress warning
}
private:
std::string type_name_;
std::string inst_name_;
std::map<std::string, std::string> params_;
std::map<std::string, std::string> attrs_;
std::map<std::string, std::vector<std::string>> input_port_conns_;
std::map<std::string, std::vector<std::string>> output_port_conns_;
std::vector<Arc> timing_arcs_;
std::map<std::string, double> ports_tsu_;
std::map<std::string, double> ports_thld_;
std::map<std::string, double> ports_tcq_;
};
//Assignment represents the logical connection between two nets
//
// This is synonomous with verilog's 'assign x = y' which connects
// two nets with logical identity, assigning the value of 'y' to 'x'
class Assignment {
public:
Assignment(std::string lval, //The left value (assigned to)
std::string rval) //The right value (assigned from)
: lval_(lval)
, rval_(rval) {}
void print_verilog(std::ostream& os, std::string indent) {
os << indent << "assign " << escape_verilog_identifier(lval_) << " = " << escape_verilog_identifier(rval_) << ";\n";
}
void print_blif(std::ostream& os, std::string indent) {
os << indent << ".names " << rval_ << " " << lval_ << "\n";
os << indent << "1 1\n";
}
private:
std::string lval_;
std::string rval_;
};
//A class which writes post-synthesis netlists (Verilog and BLIF) and the SDF
//
//It implements the NetlistVisitor interface used by NetlistWalker (see netlist_walker.h)
class NetlistWriterVisitor : public NetlistVisitor {
public: //Public interface
NetlistWriterVisitor(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)
: verilog_os_(verilog_os)
, blif_os_(blif_os)
, sdf_os_(sdf_os)
, delay_calc_(delay_calc) {
auto& atom_ctx = g_vpr_ctx.atom();
//Initialize the pin to tnode look-up
for (AtomPinId pin : atom_ctx.nlist.pins()) {
AtomBlockId blk = atom_ctx.nlist.pin_block(pin);
ClusterBlockId clb_idx = atom_ctx.lookup.atom_clb(blk);
const t_pb_graph_pin* gpin = atom_ctx.lookup.atom_pin_pb_graph_pin(pin);
VTR_ASSERT(gpin);
int pb_pin_idx = gpin->pin_count_in_cluster;
tatum::NodeId tnode_id = atom_ctx.lookup.atom_pin_tnode(pin);
auto key = std::make_pair(clb_idx, pb_pin_idx);
auto value = std::make_pair(key, tnode_id);
auto ret = pin_id_to_tnode_lookup_.insert(value);
VTR_ASSERT_MSG(ret.second, "Was inserted");
}
}
//Non copyable/assignable/moveable
NetlistWriterVisitor(NetlistWriterVisitor& other) = delete;
NetlistWriterVisitor(NetlistWriterVisitor&& other) = delete;
NetlistWriterVisitor& operator=(NetlistWriterVisitor& rhs) = delete;
NetlistWriterVisitor& operator=(NetlistWriterVisitor&& rhs) = delete;
private: //Internal types
private: //NetlistVisitor interface functions
void visit_top_impl(const char* top_level_name) override {
top_module_name_ = top_level_name;
}
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)) {
inputs_.emplace_back(make_io(atom, PortType::INPUT));
} else if (model->name == std::string(MODEL_OUTPUT)) {
outputs_.emplace_back(make_io(atom, PortType::OUTPUT));
} 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));
}
}
void finish_impl() override {
print_verilog();
print_blif();
print_sdf();
}
private: //Internal Helper functions
//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";
//Primary Inputs
for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) {
verilog_os_ << indent(depth + 1) << "input " << escape_verilog_identifier(*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) {
verilog_os_ << indent(depth + 1) << "output " << escape_verilog_identifier(*iter);
if (iter + 1 != outputs_.end()) {
verilog_os_ << ",";
}
verilog_os_ << "\n";
}
verilog_os_ << indent(depth) << ");\n";
//Wire declarations
verilog_os_ << "\n";
verilog_os_ << indent(depth + 1) << "//Wires\n";
for (auto& kv : logical_net_drivers_) {
verilog_os_ << indent(depth + 1) << "wire " << escape_verilog_identifier(kv.second.first) << ";\n";
}
for (auto& kv : logical_net_sinks_) {
for (auto& wire_tnode_pair : kv.second) {
verilog_os_ << indent(depth + 1) << "wire " << escape_verilog_identifier(wire_tnode_pair.first) << ";\n";
}
}
//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));
}
//Interconnect between cell instances
verilog_os_ << "\n";
verilog_os_ << indent(depth + 1) << "//Interconnect\n";
for (const auto& kv : logical_net_sinks_) {
auto atom_net_id = kv.first;
auto driver_iter = logical_net_drivers_.find(atom_net_id);
VTR_ASSERT(driver_iter != logical_net_drivers_.end());
const auto& driver_wire = driver_iter->second.first;
for (auto& sink_wire_tnode_pair : kv.second) {
std::string inst_name = interconnect_name(driver_wire, sink_wire_tnode_pair.first);
verilog_os_ << indent(depth + 1) << "fpga_interconnect " << escape_verilog_identifier(inst_name) << " (\n";
verilog_os_ << indent(depth + 2) << ".datain(" << escape_verilog_identifier(driver_wire) << "),\n";
verilog_os_ << indent(depth + 2) << ".dataout(" << escape_verilog_identifier(sink_wire_tnode_pair.first) << ")\n";
verilog_os_ << indent(depth + 1) << ");\n\n";
}
}
//All the cell instances
verilog_os_ << "\n";
verilog_os_ << indent(depth + 1) << "//Cell instances\n";
for (auto& inst : cell_instances_) {
inst->print_verilog(verilog_os_, depth + 1);
}
verilog_os_ << "\n";
verilog_os_ << indent(depth) << "endmodule\n";
}
//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";
blif_os_ << indent(depth) << ".model " << top_module_name_ << "\n";
//Primary inputs
blif_os_ << indent(depth) << ".inputs ";
for (auto iter = inputs_.begin(); iter != inputs_.end(); ++iter) {
blif_os_ << *iter << " ";
}
blif_os_ << "\n";
//Primary outputs
blif_os_ << indent(depth) << ".outputs ";
for (auto iter = outputs_.begin(); iter != outputs_.end(); ++iter) {
blif_os_ << *iter << " ";
}
blif_os_ << "\n";
//I/O assignments
blif_os_ << "\n";
blif_os_ << indent(depth) << "#IO assignments\n";
for (auto& assign : assignments_) {
assign.print_blif(blif_os_, indent(depth));
}
//All interconnect between elements
blif_os_ << "\n";
blif_os_ << indent(depth) << "#Interconnect\n";
for (const auto& kv : logical_net_sinks_) {
auto atom_net_id = kv.first;
auto driver_iter = logical_net_drivers_.find(atom_net_id);
VTR_ASSERT(driver_iter != logical_net_drivers_.end());
const auto& driver_wire = driver_iter->second.first;
for (auto& sink_wire_tnode_pair : kv.second) {
blif_os_ << indent(depth) << ".names " << driver_wire << " " << sink_wire_tnode_pair.first << "\n";
blif_os_ << indent(depth) << "1 1\n";
}
}
//The cells
blif_os_ << "\n";
blif_os_ << indent(depth) << "#Cell instances\n";
size_t unconn_count = 0;
for (auto& inst : cell_instances_) {
inst->print_blif(blif_os_, unconn_count);
}
blif_os_ << "\n";
blif_os_ << indent(depth) << ".end\n";
}
//Writes out the SDF
void print_sdf(int depth = 0) {
sdf_os_ << indent(depth) << "(DELAYFILE\n";
sdf_os_ << indent(depth + 1) << "(SDFVERSION \"2.1\")\n";
sdf_os_ << indent(depth + 1) << "(DESIGN \"" << top_module_name_ << "\")\n";
sdf_os_ << indent(depth + 1) << "(VENDOR \"verilog-to-routing\")\n";
sdf_os_ << indent(depth + 1) << "(PROGRAM \"vpr\")\n";
sdf_os_ << indent(depth + 1) << "(VERSION \"" << vtr::VERSION << "\")\n";
sdf_os_ << indent(depth + 1) << "(DIVIDER /)\n";
sdf_os_ << indent(depth + 1) << "(TIMESCALE 1 ps)\n";
sdf_os_ << "\n";
//Interconnect
for (const auto& kv : logical_net_sinks_) {
auto atom_net_id = kv.first;
auto driver_iter = logical_net_drivers_.find(atom_net_id);
VTR_ASSERT(driver_iter != logical_net_drivers_.end());
auto driver_wire = driver_iter->second.first;
auto driver_tnode = driver_iter->second.second;
for (auto& sink_wire_tnode_pair : kv.second) {
auto sink_wire = sink_wire_tnode_pair.first;
auto sink_tnode = sink_wire_tnode_pair.second;
sdf_os_ << indent(depth + 1) << "(CELL\n";
sdf_os_ << indent(depth + 2) << "(CELLTYPE \"fpga_interconnect\")\n";
sdf_os_ << indent(depth + 2) << "(INSTANCE " << escape_sdf_identifier(interconnect_name(driver_wire, sink_wire)) << ")\n";
sdf_os_ << indent(depth + 2) << "(DELAY\n";
sdf_os_ << indent(depth + 3) << "(ABSOLUTE\n";
double delay = get_delay_ps(driver_tnode, sink_tnode);
std::stringstream delay_triple;
delay_triple << "(" << delay << ":" << delay << ":" << delay << ")";
sdf_os_ << indent(depth + 4) << "(IOPATH datain dataout " << delay_triple.str() << " " << delay_triple.str() << ")\n";
sdf_os_ << indent(depth + 3) << ")\n";
sdf_os_ << indent(depth + 2) << ")\n";
sdf_os_ << indent(depth + 1) << ")\n";
sdf_os_ << indent(depth) << "\n";
}
}
//Cells
for (const auto& inst : cell_instances_) {
inst->print_sdf(sdf_os_, depth + 1);
}
sdf_os_ << indent(depth) << ")\n";
}
//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;
}
//Returns the name of a circuit-level Input/Output
//The I/O is recorded and instantiated by the top level output routines
std::string make_io(const t_pb* atom, //The implementation primitive representing the I/O
PortType dir) { //The IO direction
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
std::string io_name;
int cluster_pin_idx = -1;
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 = atom->name;
} 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 = atom->name + 4;
}
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, io_name, wire_dir, 0, 0);
//Connect the wires to to I/Os with assign statements
if (wire_dir == PortType::INPUT) {
assignments_.emplace_back(io_name, wire_name);
} else {
assignments_.emplace_back(wire_name, io_name);
}
}
return io_name;
}
//Returns an Instance object representing the LUT
std::shared_ptr<Instance> make_lut_instance(const t_pb* atom) {
//Determine what size LUT
int lut_size = find_num_inputs(atom);
//Determine the truth table
auto lut_mask = load_lut_mask(lut_size, atom);
//Determine the instance name
auto inst_name = join_identifier("lut", atom->name);
//Determine the port connections
std::map<std::string, std::vector<std::string>> port_conns;
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
VTR_ASSERT(pb_graph_node->num_input_ports == 1); //LUT has one input port
const auto& top_pb_route = find_top_pb_route(atom);
VTR_ASSERT(pb_graph_node->num_output_ports == 1); //LUT has one output port
VTR_ASSERT(pb_graph_node->num_output_pins[0] == 1); //LUT has one output pin
int sink_cluster_pin_idx = pb_graph_node->output_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
//Add inputs connections
std::vector<Arc> timing_arcs;
for (int pin_idx = 0; pin_idx < pb_graph_node->num_input_pins[0]; pin_idx++) {
int cluster_pin_idx = pb_graph_node->input_pins[0][pin_idx].pin_count_in_cluster; //Unique pin index in cluster
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected to a net
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id; //Connected net in atom netlist
VTR_ASSERT(atom_net_id);
//Look up the tnode associated with this pin (used for delay calculation)
tatum::NodeId src_tnode_id = find_tnode(atom, cluster_pin_idx);
tatum::NodeId sink_tnode_id = find_tnode(atom, sink_cluster_pin_idx);
net = make_inst_wire(atom_net_id, src_tnode_id, inst_name, PortType::INPUT, 0, pin_idx);
//Record the timing arc
float delay = get_delay_ps(src_tnode_id, sink_tnode_id);
Arc timing_arc("in", pin_idx, "out", 0, delay);
timing_arcs.push_back(timing_arc);
}
port_conns["in"].push_back(net);
}
//Add the single output connection
{
auto atom_net_id = top_pb_route[sink_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
std::string net;
if (!atom_net_id) {
//Disconnected
net = "";
} else {
//Connected to a net
//Look up the tnode associated with this pin (used for delay calculation)
tatum::NodeId tnode_id = find_tnode(atom, sink_cluster_pin_idx);
net = make_inst_wire(atom_net_id, tnode_id, inst_name, PortType::OUTPUT, 0, 0);
}
port_conns["out"].push_back(net);
}
auto inst = std::make_shared<LutInst>(lut_size, lut_mask, inst_name, port_conns, timing_arcs);
return inst;
}
//Returns an Instance object representing the Latch
std::shared_ptr<Instance> make_latch_instance(const t_pb* atom) {
std::string inst_name = join_identifier("latch", atom->name);
const auto& top_pb_route = find_top_pb_route(atom);
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
//We expect a single input, output and clock ports
VTR_ASSERT(pb_graph_node->num_input_ports == 1);
VTR_ASSERT(pb_graph_node->num_output_ports == 1);
VTR_ASSERT(pb_graph_node->num_clock_ports == 1);
VTR_ASSERT(pb_graph_node->num_input_pins[0] == 1);
VTR_ASSERT(pb_graph_node->num_output_pins[0] == 1);
VTR_ASSERT(pb_graph_node->num_clock_pins[0] == 1);
//The connections
std::map<std::string, std::string> port_conns;
//Input (D)
int input_cluster_pin_idx = pb_graph_node->input_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
VTR_ASSERT(top_pb_route.count(input_cluster_pin_idx));
auto input_atom_net_id = top_pb_route[input_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
std::string input_net = make_inst_wire(input_atom_net_id, find_tnode(atom, input_cluster_pin_idx), inst_name, PortType::INPUT, 0, 0);
port_conns["D"] = input_net;
double tsu = pb_graph_node->input_pins[0][0].tsu;
//Output (Q)
int output_cluster_pin_idx = pb_graph_node->output_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
VTR_ASSERT(top_pb_route.count(output_cluster_pin_idx));
auto output_atom_net_id = top_pb_route[output_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
std::string output_net = make_inst_wire(output_atom_net_id, find_tnode(atom, output_cluster_pin_idx), inst_name, PortType::OUTPUT, 0, 0);
port_conns["Q"] = output_net;
double tcq = pb_graph_node->output_pins[0][0].tco_max;
//Clock (control)
int control_cluster_pin_idx = pb_graph_node->clock_pins[0][0].pin_count_in_cluster; //Unique pin index in cluster
VTR_ASSERT(top_pb_route.count(control_cluster_pin_idx));
auto control_atom_net_id = top_pb_route[control_cluster_pin_idx].atom_net_id; //Connected net in atom netlist
std::string control_net = make_inst_wire(control_atom_net_id, find_tnode(atom, control_cluster_pin_idx), inst_name, PortType::CLOCK, 0, 0);
port_conns["clock"] = control_net;
//VPR currently doesn't store enough information to determine these attributes,
//for now assume reasonable defaults.
LatchInst::Type type = LatchInst::Type::RISING_EDGE;
vtr::LogicValue init_value = vtr::LogicValue::FALSE;
return std::make_shared<LatchInst>(inst_name, port_conns, type, init_value, tcq, tsu);
}
//Returns an Instance object representing the RAM
// Note that the primtive interface to dual and single port rams is nearly identical,
// so we using a single function to handle both
std::shared_ptr<Instance> make_ram_instance(const t_pb* atom) {
const auto& top_pb_route = find_top_pb_route(atom);
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
const t_pb_type* pb_type = pb_graph_node->pb_type;
VTR_ASSERT(pb_type->class_type == MEMORY_CLASS);
std::string type = pb_type->model->name;
std::string inst_name = join_identifier(type, atom->name);
std::map<std::string, std::string> params;
std::map<std::string, std::string> attrs;
std::map<std::string, std::vector<std::string>> input_port_conns;
std::map<std::string, std::vector<std::string>> output_port_conns;
std::vector<Arc> timing_arcs;
std::map<std::string, double> ports_tsu;
std::map<std::string, double> ports_tcq;
params["ADDR_WIDTH"] = "0";
params["DATA_WIDTH"] = "0";
//Process the input ports
for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
const t_port* port = pin->port;
std::string port_class = port->port_class;
int cluster_pin_idx = pin->pin_count_in_cluster;
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id);
net = make_inst_wire(atom_net_id, find_tnode(atom, cluster_pin_idx), inst_name, PortType::INPUT, iport, ipin);
}
//RAMs use a port class specification to identify the purpose of each port
std::string port_name;
if (port_class == "address") {
port_name = "addr";
params["ADDR_WIDTH"] = std::to_string(port->num_pins);
} else if (port_class == "address1") {
port_name = "addr1";
params["ADDR_WIDTH"] = std::to_string(port->num_pins);
} else if (port_class == "address2") {
port_name = "addr2";
params["ADDR_WIDTH"] = std::to_string(port->num_pins);
} else if (port_class == "data_in") {
port_name = "data";
params["DATA_WIDTH"] = std::to_string(port->num_pins);
} else if (port_class == "data_in1") {
port_name = "data1";
params["DATA_WIDTH"] = std::to_string(port->num_pins);
} else if (port_class == "data_in2") {
port_name = "data2";
params["DATA_WIDTH"] = std::to_string(port->num_pins);
} else if (port_class == "write_en") {
port_name = "we";
} else if (port_class == "write_en1") {
port_name = "we1";
} else if (port_class == "write_en2") {
port_name = "we2";
} else {
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Unrecognized input port class '%s' for primitive '%s' (%s)\n", port_class.c_str(), atom->name, pb_type->name);
}
input_port_conns[port_name].push_back(net);
ports_tsu[port_name] = pin->tsu;
}
}
//Process the output ports
for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
const t_port* port = pin->port;
std::string port_class = port->port_class;
int cluster_pin_idx = pin->pin_count_in_cluster;
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id);
net = make_inst_wire(atom_net_id, find_tnode(atom, cluster_pin_idx), inst_name, PortType::OUTPUT, iport, ipin);
}
std::string port_name;
if (port_class == "data_out") {
port_name = "out";
} else if (port_class == "data_out1") {
port_name = "out1";
} else if (port_class == "data_out2") {
port_name = "out2";
} else {
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Unrecognized input port class '%s' for primitive '%s' (%s)\n", port_class.c_str(), atom->name, pb_type->name);
}
output_port_conns[port_name].push_back(net);
ports_tcq[port_name] = pin->tco_max;
}
}
//Process the clock ports
for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) {
VTR_ASSERT(pb_graph_node->num_clock_pins[iport] == 1); //Expect a single clock port
for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->clock_pins[iport][ipin];
const t_port* port = pin->port;
std::string port_class = port->port_class;
int cluster_pin_idx = pin->pin_count_in_cluster;
VTR_ASSERT(top_pb_route.count(cluster_pin_idx));
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id); //Must have a clock
std::string net = make_inst_wire(atom_net_id, find_tnode(atom, cluster_pin_idx), inst_name, PortType::CLOCK, iport, ipin);
if (port_class == "clock") {
VTR_ASSERT(pb_graph_node->num_clock_pins[iport] == 1); //Expect a single clock pin
input_port_conns["clock"].push_back(net);
} else {
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Unrecognized input port class '%s' for primitive '%s' (%s)\n", port_class.c_str(), atom->name, pb_type->name);
}
}
}
return std::make_shared<BlackBoxInst>(type, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
}
//Returns an Instance object representing a Multiplier
std::shared_ptr<Instance> make_multiply_instance(const t_pb* atom) {
auto& timing_ctx = g_vpr_ctx.timing();
const auto& top_pb_route = find_top_pb_route(atom);
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
const t_pb_type* pb_type = pb_graph_node->pb_type;
std::string type_name = pb_type->model->name;
std::string inst_name = join_identifier(type_name, atom->name);
std::map<std::string, std::string> params;
std::map<std::string, std::string> attrs;
std::map<std::string, std::vector<std::string>> input_port_conns;
std::map<std::string, std::vector<std::string>> output_port_conns;
std::vector<Arc> timing_arcs;
std::map<std::string, double> ports_tsu;
std::map<std::string, double> ports_tcq;
params["WIDTH"] = "0";
//Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay
std::map<tatum::NodeId, std::vector<std::tuple<std::string, int, double>>> tnode_delay_matrix;
//Process the input ports
for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
const t_port* port = pin->port;
params["WIDTH"] = std::to_string(port->num_pins); //Assume same width on all ports
int cluster_pin_idx = pin->pin_count_in_cluster;
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id);
auto src_tnode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::INPUT, iport, ipin);
//Delays
//
//We record the souce sink tnodes and thier delays here
for (tatum::EdgeId edge : timing_ctx.graph->node_out_edges(src_tnode)) {
double delay = delay_calc_->max_edge_delay(*timing_ctx.graph, edge);
auto sink_tnode = timing_ctx.graph->edge_sink_node(edge);
tnode_delay_matrix[sink_tnode].emplace_back(port->name, ipin, delay);
}
}
input_port_conns[port->name].push_back(net);
}
}
//Process the output ports
for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
const t_port* port = pin->port;
int cluster_pin_idx = pin->pin_count_in_cluster;
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id);
auto inode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, inode, inst_name, PortType::OUTPUT, iport, ipin);
//Record the timing arcs
for (auto& data_tuple : tnode_delay_matrix[inode]) {
auto src_name = std::get<0>(data_tuple);
auto src_ipin = std::get<1>(data_tuple);
auto delay = std::get<2>(data_tuple);
timing_arcs.emplace_back(src_name, src_ipin, port->name, ipin, delay);
}
}
output_port_conns[port->name].push_back(net);
}
}
VTR_ASSERT(pb_graph_node->num_clock_ports == 0); //No clocks
return std::make_shared<BlackBoxInst>(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
}
//Returns an Instance object representing an Adder
std::shared_ptr<Instance> make_adder_instance(const t_pb* atom) {
auto& timing_ctx = g_vpr_ctx.timing();
const auto& top_pb_route = find_top_pb_route(atom);
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
const t_pb_type* pb_type = pb_graph_node->pb_type;
std::string type_name = pb_type->model->name;
std::string inst_name = join_identifier(type_name, atom->name);
std::map<std::string, std::string> params;
std::map<std::string, std::string> attrs;
std::map<std::string, std::vector<std::string>> input_port_conns;
std::map<std::string, std::vector<std::string>> output_port_conns;
std::vector<Arc> timing_arcs;
std::map<std::string, double> ports_tsu;
std::map<std::string, double> ports_tcq;
params["WIDTH"] = "0";
//Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay
std::map<tatum::NodeId, std::vector<std::tuple<std::string, int, double>>> tnode_delay_matrix;
//Process the input ports
for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
const t_port* port = pin->port;
if (port->name == std::string("a") || port->name == std::string("b")) {
params["WIDTH"] = std::to_string(port->num_pins); //Assume same width on all ports
}
int cluster_pin_idx = pin->pin_count_in_cluster;
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id);
//Connected
auto src_tnode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::INPUT, iport, ipin);
//Delays
//
//We record the souce sink tnodes and thier delays here
for (tatum::EdgeId edge : timing_ctx.graph->node_out_edges(src_tnode)) {
double delay = delay_calc_->max_edge_delay(*timing_ctx.graph, edge);
auto sink_tnode = timing_ctx.graph->edge_sink_node(edge);
tnode_delay_matrix[sink_tnode].emplace_back(port->name, ipin, delay);
}
}
input_port_conns[port->name].push_back(net);
}
}
//Process the output ports
for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
const t_port* port = pin->port;
int cluster_pin_idx = pin->pin_count_in_cluster;
std::string net;
if (!top_pb_route.count(cluster_pin_idx)) {
//Disconnected
net = "";
} else {
//Connected
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id);
auto inode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, inode, inst_name, PortType::OUTPUT, iport, ipin);
//Record the timing arcs
for (auto& data_tuple : tnode_delay_matrix[inode]) {
auto src_name = std::get<0>(data_tuple);
auto src_ipin = std::get<1>(data_tuple);
auto delay = std::get<2>(data_tuple);
timing_arcs.emplace_back(src_name, src_ipin, port->name, ipin, delay);
}
}
output_port_conns[port->name].push_back(net);
}
}
return std::make_shared<BlackBoxInst>(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
}
std::shared_ptr<Instance> make_blackbox_instance(const t_pb* atom) {
const auto& top_pb_route = find_top_pb_route(atom);
const t_pb_graph_node* pb_graph_node = atom->pb_graph_node;
const t_pb_type* pb_type = pb_graph_node->pb_type;
std::string type_name = pb_type->model->name;
std::string inst_name = join_identifier(type_name, atom->name);
std::map<std::string, std::string> params;
std::map<std::string, std::string> attrs;
std::map<std::string, std::vector<std::string>> input_port_conns;
std::map<std::string, std::vector<std::string>> output_port_conns;
std::vector<Arc> timing_arcs;
std::map<std::string, double> ports_tsu;
std::map<std::string, double> ports_tcq;
//Delay matrix[sink_tnode] -> tuple of source_port_name, pin index, delay
std::map<tatum::NodeId, std::vector<std::tuple<std::string, int, double>>> tnode_delay_matrix;
//Process the input ports
for (int iport = 0; iport < pb_graph_node->num_input_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_input_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->input_pins[iport][ipin];
const t_port* port = pin->port;
int cluster_pin_idx = pin->pin_count_in_cluster;
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
std::string net;
if (!atom_net_id) {
//Disconnected
} else {
//Connected
auto src_tnode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::INPUT, iport, ipin);
}
input_port_conns[port->name].push_back(net);
}
}
//Process the output ports
for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->output_pins[iport][ipin];
const t_port* port = pin->port;
int cluster_pin_idx = pin->pin_count_in_cluster;
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
std::string net;
if (!atom_net_id) {
//Disconnected
net = "";
} else {
//Connected
auto inode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, inode, inst_name, PortType::OUTPUT, iport, ipin);
}
output_port_conns[port->name].push_back(net);
}
}
//Process the clock ports
for (int iport = 0; iport < pb_graph_node->num_clock_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_clock_pins[iport]; ++ipin) {
const t_pb_graph_pin* pin = &pb_graph_node->clock_pins[iport][ipin];
const t_port* port = pin->port;
int cluster_pin_idx = pin->pin_count_in_cluster;
auto atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
VTR_ASSERT(atom_net_id); //Must have a clock
std::string net;
if (!atom_net_id) {
//Disconnected
net = "";
} else {
//Connected
auto src_tnode = find_tnode(atom, cluster_pin_idx);
net = make_inst_wire(atom_net_id, src_tnode, inst_name, PortType::CLOCK, iport, ipin);
}
input_port_conns[port->name].push_back(net);
}
}
auto& atom_ctx = g_vpr_ctx.atom();
AtomBlockId blk_id = atom_ctx.lookup.pb_atom(atom);
for (auto param : atom_ctx.nlist.block_params(blk_id)) {
params[param.first] = param.second;
}
for (auto attr : atom_ctx.nlist.block_attrs(blk_id)) {
attrs[attr.first] = attr.second;
}
return std::make_shared<BlackBoxInst>(type_name, inst_name, params, attrs, input_port_conns, output_port_conns, timing_arcs, ports_tsu, ports_tcq);
}
//Returns the top level pb_route associated with the given pb
const t_pb_routes& find_top_pb_route(const t_pb* curr) {
const t_pb* top_pb = find_top_cb(curr);
return top_pb->pb_route;
}
//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;
}
//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();
AtomBlockId blk_id = atom_ctx.lookup.pb_atom(atom);
ClusterBlockId clb_index = atom_ctx.lookup.atom_clb(blk_id);
auto key = std::make_pair(clb_index, cluster_pin_idx);
auto iter = pin_id_to_tnode_lookup_.find(key);
VTR_ASSERT(iter != pin_id_to_tnode_lookup_.end());
tatum::NodeId tnode_id = iter->second;
VTR_ASSERT(tnode_id);
return tnode_id;
}
//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
auto& atom_ctx = g_vpr_ctx.atom();
const t_model* model = atom_ctx.nlist.block_model(atom_ctx.lookup.pb_atom(atom));
VTR_ASSERT(model->name == std::string(MODEL_NAMES));
#ifdef DEBUG_LUT_MASK
std::cout << "Loading LUT mask for: " << atom->name << std::endl;
#endif
//Figure out how the inputs were permuted (compared to the input netlist)
std::vector<int> permute = determine_lut_permutation(num_inputs, atom);
//Retrieve the truth table
const auto& truth_table = atom_ctx.nlist.block_truth_table(atom_ctx.lookup.pb_atom(atom));
//Apply the permutation
auto permuted_truth_table = permute_truth_table(truth_table, num_inputs, permute);
//Convert to lut mask
LogicVec lut_mask = truth_table_to_lut_mask(permuted_truth_table, num_inputs);
#ifdef DEBUG_LUT_MASK
std::cout << "\tLUT_MASK: " << lut_mask << std::endl;
#endif
return lut_mask;
}
//Helper function for load_lut_mask() which determines how the LUT inputs were
//permuted compared to the input BLIF
//
// Since the LUT inputs may have been rotated from the input blif specification we need to
// figure out this permutation to reflect the physical implementation connectivity.
//
// We return a permutation map (which is a list of swaps from index to index)
// which is then applied to do the rotation of the lutmask.
//
// The net in the atom netlist which was originally connected to pin i, is connected
// to pin permute[i] in the implementation.
std::vector<int> determine_lut_permutation(size_t num_inputs, const t_pb* atom_pb) {
auto& atom_ctx = g_vpr_ctx.atom();
std::vector<int> permute(num_inputs, OPEN);
#ifdef DEBUG_LUT_MASK
std::cout << "\tInit Permute: {";
for (size_t i = 0; i < permute.size(); i++) {
std::cout << permute[i] << ", ";
}
std::cout << "}" << std::endl;
#endif
//Determine the permutation
//
//We walk through the logical inputs to this atom (i.e. in the original truth table/netlist)
//and find the corresponding input in the implementation atom (i.e. in the current netlist)
auto ports = atom_ctx.nlist.block_input_ports(atom_ctx.lookup.pb_atom(atom_pb));
if (ports.size() == 1) {
const t_pb_graph_node* gnode = atom_pb->pb_graph_node;
VTR_ASSERT(gnode->num_input_ports == 1);
VTR_ASSERT(gnode->num_input_pins[0] >= (int)num_inputs);
AtomPortId port_id = *ports.begin();
for (size_t ipin = 0; ipin < num_inputs; ++ipin) {
AtomNetId impl_input_net_id = find_atom_input_logical_net(atom_pb, ipin); //The net currently connected to input j
//Find the original pin index
const t_pb_graph_pin* gpin = &gnode->input_pins[0][ipin];
BitIndex orig_index = atom_pb->atom_pin_bit_index(gpin);
if (impl_input_net_id) {
//If there is a valid net connected in the implementation
AtomNetId logical_net_id = atom_ctx.nlist.port_net(port_id, orig_index);
VTR_ASSERT(impl_input_net_id == logical_net_id);
//Mark the permutation.
// The net originally located at orig_index in the atom netlist
// was moved to ipin in the implementation
permute[orig_index] = ipin;
}
}
} else {
//May have no inputs on a constant generator
VTR_ASSERT(ports.size() == 0);
}
//Fill in any missing values in the permutation (i.e. zeros)
std::set<int> perm_indicies(permute.begin(), permute.end());
size_t unused_index = 0;
for (size_t i = 0; i < permute.size(); i++) {
if (permute[i] == OPEN) {
while (perm_indicies.count(unused_index)) {
unused_index++;
}
permute[i] = unused_index;
perm_indicies.insert(unused_index);
}
}
#ifdef DEBUG_LUT_MASK
std::cout << "\tPermute: {";
for (size_t k = 0; k < permute.size(); k++) {
std::cout << permute[k] << ", ";
}
std::cout << "}" << std::endl;
std::cout << "\tBLIF = Input -> Rotated" << std::endl;
std::cout << "\t------------------------" << std::endl;
#endif
return permute;
}
//Helper function for load_lut_mask() which determines if the
//names is encodeing the ON (returns true) or OFF (returns false)
//set.
bool names_encodes_on_set(vtr::t_linked_vptr* names_row_ptr) {
//Determine the truth (output value) for this row
// By default we assume the on-set is encoded to correctly handle
// constant true/false
//
// False output:
// .names j
//
// True output:
// .names j
// 1
bool encoding_on_set = true;
//We may get a nullptr if the output is always false
if (names_row_ptr) {
//Determine whether the truth table stores the ON or OFF set
//
// In blif, the 'output values' of a .names must be either '1' or '0', and must be consistent
// within a single .names -- that is a single .names can encode either the ON or OFF set
// (of which only one will be encoded in a single .names)
//
const std::string names_first_row = (const char*)names_row_ptr->data_vptr;
auto names_first_row_output_iter = names_first_row.end() - 1;
if (*names_first_row_output_iter == '1') {
encoding_on_set = true;
} else if (*names_first_row_output_iter == '0') {
encoding_on_set = false;
} else {
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Invalid .names truth-table character '%c'. Must be one of '1', '0' or '-'. \n", *names_first_row_output_iter);
}
}
return encoding_on_set;
}
//Helper function for load_lut_mask()
//
//Converts the given names_row string to a LogicVec
LogicVec names_row_to_logic_vec(const std::string names_row, size_t num_inputs, bool encoding_on_set) {
//Get an iterator to the last character (i.e. the output value)
auto output_val_iter = names_row.end() - 1;
//Sanity-check, the 2nd last character should be a space
auto space_iter = names_row.end() - 2;
VTR_ASSERT(*space_iter == ' ');
//Extract the truth (output value) for this row
if (*output_val_iter == '1') {
VTR_ASSERT(encoding_on_set);
} else if (*output_val_iter == '0') {
VTR_ASSERT(!encoding_on_set);
} else {
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Invalid .names encoding both ON and OFF set\n");
}
//Extract the input values for this row
LogicVec input_values(num_inputs, vtr::LogicValue::FALSE);
size_t i = 0;
//Walk through each input in the input cube for this row
while (names_row[i] != ' ') {
vtr::LogicValue input_val = vtr::LogicValue::UNKOWN;
if (names_row[i] == '1') {
input_val = vtr::LogicValue::TRUE;
} else if (names_row[i] == '0') {
input_val = vtr::LogicValue::FALSE;
} else if (names_row[i] == '-') {
input_val = vtr::LogicValue::DONT_CARE;
} else {
VPR_FATAL_ERROR(VPR_ERROR_IMPL_NETLIST_WRITER,
"Invalid .names truth-table character '%c'. Must be one of '1', '0' or '-'. \n", names_row[i]);
}
input_values[i] = input_val;
i++;
}
return input_values;
}
//Returns the total number of input pins on the given pb
int find_num_inputs(const t_pb* pb) {
int count = 0;
for (int i = 0; i < pb->pb_graph_node->num_input_ports; i++) {
count += pb->pb_graph_node->num_input_pins[i];
}
return count;
}
//Returns the logical net ID
AtomNetId find_atom_input_logical_net(const t_pb* atom, int atom_input_idx) {
const t_pb_graph_node* pb_node = atom->pb_graph_node;
int cluster_pin_idx = pb_node->input_pins[0][atom_input_idx].pin_count_in_cluster;
const auto& top_pb_route = find_top_pb_route(atom);
AtomNetId atom_net_id;
if (top_pb_route.count(cluster_pin_idx)) {
atom_net_id = top_pb_route[cluster_pin_idx].atom_net_id;
}
return atom_net_id;
}
//Returns the name of the routing segment between two wires
std::string interconnect_name(std::string driver_wire, std::string sink_wire) {
std::string name = join_identifier("routing_segment", driver_wire);
name = join_identifier(name, "to");
name = join_identifier(name, sink_wire);
return name;
}
//Returns the delay in pico-seconds from source_tnode to sink_tnode
double get_delay_ps(tatum::NodeId source_tnode, tatum::NodeId sink_tnode) {
auto& timing_ctx = g_vpr_ctx.timing();
tatum::EdgeId edge = timing_ctx.graph->find_edge(source_tnode, sink_tnode);
VTR_ASSERT(edge);
double delay_sec = delay_calc_->max_edge_delay(*timing_ctx.graph, edge);
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)
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
//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_;
//Sinks of logical nets.
// Key: logical net id, Value: vector wire_name tnode_id pairs
std::map<AtomNetId, std::vector<std::pair<std::string, tatum::NodeId>>> logical_net_sinks_;
std::map<std::string, float> logical_net_sink_delays_;
//Output streams
std::ostream& verilog_os_;
std::ostream& blif_os_;
std::ostream& sdf_os_;
//Look-up from pins to tnodes
std::map<std::pair<ClusterBlockId, int>, tatum::NodeId> pin_id_to_tnode_lookup_;
std::shared_ptr<const AnalysisDelayCalculator> delay_calc_;
};
//
// Externally Accessible Functions
//
//Main routing for this file. See netlist_writer.h for details.
void netlist_writer(const std::string basename, std::shared_ptr<const AnalysisDelayCalculator> delay_calc) {
std::string verilog_filename = basename + "_post_synthesis.v";
std::string blif_filename = basename + "_post_synthesis.blif";
std::string sdf_filename = basename + "_post_synthesis.sdf";
VTR_LOG("Writing Implementation Netlist: %s\n", verilog_filename.c_str());
VTR_LOG("Writing Implementation Netlist: %s\n", blif_filename.c_str());
VTR_LOG("Writing Implementation SDF : %s\n", sdf_filename.c_str());
std::ofstream verilog_os(verilog_filename);
std::ofstream blif_os(blif_filename);
std::ofstream sdf_os(sdf_filename);
NetlistWriterVisitor visitor(verilog_os, blif_os, sdf_os, delay_calc);
NetlistWalker nl_walker(visitor);
nl_walker.walk();
}
//
// File-scope function implementations
//
//Returns a blank string for indenting the given depth
std::string indent(size_t depth) {
std::string indent_ = " ";
std::string new_indent;
for (size_t i = 0; i < depth; ++i) {
new_indent += indent_;
}
return new_indent;
}
//Returns the delay in pico-seconds from a floating point delay
double get_delay_ps(double delay_sec) {
return delay_sec * 1e12; //Scale to picoseconds
}
//Returns the name of a unique unconnected net
std::string create_unconn_net(size_t& unconn_count) {
//We increment unconn_count by reference so each
//call generates a unique name
return "__vpr__unconn" + std::to_string(unconn_count++);
}
//Pretty-Prints a blif port to the given output stream
//
// Handles special cases like multi-bit and disconnected ports
void print_blif_port(std::ostream& os, size_t& unconn_count, const std::string& port_name, const std::vector<std::string>& nets, int depth) {
if (nets.size() == 1) {
//If only a single bit port, don't include port indexing
os << indent(depth) << port_name << "=";
if (nets[0].empty()) {
os << create_unconn_net(unconn_count);
} else {
os << nets[0];
}
} else {
//Include bit indexing for multi-bit ports
for (int ipin = 0; ipin < (int)nets.size(); ++ipin) {
os << indent(depth) << port_name << "[" << ipin << "]"
<< "=";
if (nets[ipin].empty()) {
os << create_unconn_net(unconn_count);
} else {
os << nets[ipin];
}
if (ipin != (int)nets.size() - 1) {
os << " "
<< "\\"
<< "\n";
}
}
}
}
//Pretty-Prints a verilog port to the given output stream
//
// Handles special cases like multi-bit and disconnected ports
void print_verilog_port(std::ostream& os, const std::string& port_name, const std::vector<std::string>& nets, PortType type, int depth) {
//Port name
os << indent(depth) << "." << port_name << "(";
//Pins
if (nets.size() == 1) {
//Single-bit port
if (nets[0].empty()) {
//Disconnected
if (type == PortType::INPUT || type == PortType::CLOCK) {
os << "1'b0";
} else {
VTR_ASSERT(type == PortType::OUTPUT);
os << "";
}
} else {
//Connected
os << escape_verilog_identifier(nets[0]);
}
} else {
//A multi-bit port, we explicitly concat the single-bit nets to build the port,
//taking care to print MSB on left and LSB on right
os << "{"
<< "\n";
for (int ipin = (int)nets.size() - 1; ipin >= 0; --ipin) { //Reverse order to match endianess
os << indent(depth + 1);
if (nets[ipin].empty()) {
//Disconnected
if (type == PortType::INPUT || type == PortType::CLOCK) {
os << "1'b0";
} else {
VTR_ASSERT(type == PortType::OUTPUT);
os << "";
}
} else {
//Connected
os << escape_verilog_identifier(nets[ipin]);
}
if (ipin != 0) {
os << ",";
os << "\n";
}
}
os << "}";
}
os << ")";
}
//Escapes the given identifier to be safe for verilog
std::string escape_verilog_identifier(const std::string identifier) {
//Verilog allows escaped identifiers
//
//The escaped identifiers start with a literal back-slash '\'
//followed by the identifier and are terminated by white space
//
//We pre-pend the escape back-slash and append a space to avoid
//the identifier gobbling up adjacent characters like commas which
//are not actually part of the identifier
std::string prefix = "\\";
std::string suffix = " ";
std::string escaped_name = prefix + identifier + suffix;
return escaped_name;
}
//Returns true if c is categorized as a special character in SDF
bool is_special_sdf_char(char c) {
//From section 3.2.5 of IEEE1497 Part 3 (i.e. the SDF spec)
//Special characters run from:
// ! to # (ASCII decimal 33-35)
// % to / (ASCII decimal 37-47)
// : to @ (ASCII decimal 58-64)
// [ to ^ (ASCII decimal 91-94)
// ` to ` (ASCII decimal 96)
// { to ~ (ASCII decimal 123-126)
//
//Note that the spec defines _ (decimal code 95) and $ (decimal code 36)
//as non-special alphanumeric characters.
//
//However it inconsistently also lists $ in the list of special characters.
//Since the spec allows for non-special characters to be escaped (they are treated
//normally), we treat $ as a special character to be safe.
//
//Note that the spec appears to have rendering errors in the PDF availble
//on IEEE Xplore, listing the 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK'
//character (decimal code 171) in place of the APOSTROPHE character '
//with decimal code 39 in the special character list. We assume code 39.
if ((c >= 33 && c <= 35) || (c == 36) || // $
(c >= 37 && c <= 47) || (c >= 58 && c <= 64) || (c >= 91 && c <= 94) || (c == 96) || (c >= 123 && c <= 126)) {
return true;
}
return false;
}
//Escapes the given identifier to be safe for sdf
std::string escape_sdf_identifier(const std::string identifier) {
//SDF allows escaped characters
//
//We look at each character in the string and escape it if it is
//a special character
std::string escaped_name;
for (char c : identifier) {
if (is_special_sdf_char(c)) {
//Escape the special character
escaped_name += '\\';
}
escaped_name += c;
}
return escaped_name;
}
//Joins two identifier strings
std::string join_identifier(std::string lhs, std::string rhs) {
return lhs + '_' + rhs;
}