OpenFPGA/vpr/src/base/atom_netlist_utils.cpp

1487 lines
58 KiB
C++

#include "atom_netlist_utils.h"
#include <map>
#include <unordered_set>
#include <set>
#include <algorithm>
#include <iterator>
#include <cmath>
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vpr_error.h"
#include "vpr_utils.h"
//Marks primitive output pins constant if all inputs to the block are constant
//
//Since marking one block constant may cause a downstream block to also be constant,
//marking is repated until there is no further change
int infer_and_mark_constant_pins(AtomNetlist& netlist, e_const_gen_inference const_gen_inference_method, int verbosity);
//Marks all primtiive output pins which have no combinationally connected inputs as constant pins
int mark_undriven_primitive_outputs_as_constant(AtomNetlist& netlist, int verbosity);
//Marks all primtiive output pins of blk which have only constant inputs as constant pins
int infer_and_mark_block_pins_constant(AtomNetlist& netlist, AtomBlockId blk, e_const_gen_inference const_gen_inference_method, int verbosity);
int infer_and_mark_block_combinational_outputs_constant(AtomNetlist& netlist, AtomBlockId blk, e_const_gen_inference const_gen_inference_method, int verbosity);
int infer_and_mark_block_sequential_outputs_constant(AtomNetlist& netlist, AtomBlockId blk, e_const_gen_inference const_gen_inference_method, int verbosity);
//Returns the set of input ports which are combinationally connected to output_port
std::vector<AtomPortId> find_combinationally_connected_input_ports(const AtomNetlist& netlist, AtomPortId output_port);
//Returns the set of clock ports which are combinationally connected to output_port
std::vector<AtomPortId> find_combinationally_connected_clock_ports(const AtomNetlist& netlist, AtomPortId output_port);
bool is_buffer_lut(const AtomNetlist& netlist, const AtomBlockId blk);
bool is_removable_block(const AtomNetlist& netlist, const AtomBlockId blk, std::string* reason = nullptr);
bool is_removable_input(const AtomNetlist& netlist, const AtomBlockId blk, std::string* reason = nullptr);
bool is_removable_output(const AtomNetlist& netlist, const AtomBlockId blk, std::string* reason = nullptr);
//Attempts to remove the specified buffer LUT blk from the netlist. Returns true if successful.
bool remove_buffer_lut(AtomNetlist& netlist, AtomBlockId blk, int verbosity);
std::string make_unconn(size_t& unconn_count, PinType type);
void cube_to_minterms_recurr(std::vector<vtr::LogicValue> cube, std::vector<size_t>& minterms);
void print_netlist_as_blif(std::string filename, const AtomNetlist& netlist) {
FILE* f = std::fopen(filename.c_str(), "w");
print_netlist_as_blif(f, netlist);
std::fclose(f);
}
void print_netlist_as_blif(FILE* f, const AtomNetlist& netlist) {
constexpr const char* INDENT = " ";
size_t unconn_count = 0;
fprintf(f, "#Atom netlist generated by VPR\n");
fprintf(f, ".model %s\n", netlist.netlist_name().c_str());
{
std::vector<AtomBlockId> inputs;
for (auto blk_id : netlist.blocks()) {
if (netlist.block_type(blk_id) == AtomBlockType::INPAD) {
inputs.push_back(blk_id);
}
}
fprintf(f, ".inputs \\\n");
for (size_t i = 0; i < inputs.size(); ++i) {
fprintf(f, "%s%s", INDENT, netlist.block_name(inputs[i]).c_str());
if (i != inputs.size() - 1) {
fprintf(f, " \\\n");
}
}
fprintf(f, "\n");
}
{
std::vector<AtomBlockId> outputs;
for (auto blk_id : netlist.blocks()) {
if (netlist.block_type(blk_id) == AtomBlockType::OUTPAD) {
outputs.push_back(blk_id);
}
}
fprintf(f, ".outputs \\\n");
size_t i = 0;
std::set<std::pair<std::string, std::string>> artificial_buffer_connections_required;
for (AtomBlockId blk_id : outputs) {
VTR_ASSERT(netlist.block_pins(blk_id).size() == 1);
AtomPinId pin = *netlist.block_pins(blk_id).begin();
std::string blk_name = netlist.block_name(blk_id);
std::string out_name(blk_name.begin() + 4, blk_name.end()); //+4 to trim out: prefix
fprintf(f, "%s%s", INDENT, out_name.c_str());
//BLIF requires that primary outputs be driven by nets of the same name
//
//This is not something we enforce within the netlist data structures
//
//Since BLIF has no 'logical assignment' other than buffers we need to create
//buffers to represent the change of net name.
//
//See if the net has a different name than the current port, if so we
//need an artificial buffer LUT
AtomNetId net = netlist.pin_net(pin);
if (net) {
std::string net_name = netlist.net_name(net);
if (net_name != out_name) {
artificial_buffer_connections_required.insert({net_name, out_name});
}
}
if (i != outputs.size() - 1) {
fprintf(f, " \\\n");
}
++i;
}
fprintf(f, "\n");
fprintf(f, "\n");
//Artificial buffers
for (auto buf_pair : artificial_buffer_connections_required) {
fprintf(f, "#Artificially inserted primary-output assigment buffer\n");
fprintf(f, ".names %s %s\n", buf_pair.first.c_str(), buf_pair.second.c_str());
fprintf(f, "1 1\n");
fprintf(f, "\n");
}
}
//Latch
for (auto blk_id : netlist.blocks()) {
if (netlist.block_type(blk_id) == AtomBlockType::BLOCK) {
const t_model* blk_model = netlist.block_model(blk_id);
if (blk_model->name != std::string(MODEL_LATCH)) continue;
//Nets
std::string d_net;
std::string q_net;
std::string clk_net;
//Determine the nets
auto input_ports = netlist.block_input_ports(blk_id);
auto output_ports = netlist.block_output_ports(blk_id);
auto clock_ports = netlist.block_clock_ports(blk_id);
for (auto ports : {input_ports, output_ports, clock_ports}) {
for (AtomPortId port_id : ports) {
auto pins = netlist.port_pins(port_id);
VTR_ASSERT(pins.size() <= 1);
for (auto in_pin_id : pins) {
auto net_id = netlist.pin_net(in_pin_id);
if (netlist.port_name(port_id) == "D") {
d_net = netlist.net_name(net_id);
} else if (netlist.port_name(port_id) == "Q") {
q_net = netlist.net_name(net_id);
} else if (netlist.port_name(port_id) == "clk") {
clk_net = netlist.net_name(net_id);
} else {
VPR_FATAL_ERROR(VPR_ERROR_ATOM_NETLIST, "Unrecognzied latch port '%s'", netlist.port_name(port_id).c_str());
}
}
}
}
if (d_net.empty()) {
VTR_LOG_WARN("No net found for .latch '%s' data input (D pin)\n", netlist.block_name(blk_id).c_str());
d_net = make_unconn(unconn_count, PinType::SINK);
}
if (q_net.empty()) {
VTR_LOG_WARN("No net found for .latch '%s' data output (Q pin)\n", netlist.block_name(blk_id).c_str());
q_net = make_unconn(unconn_count, PinType::DRIVER);
}
if (clk_net.empty()) {
VTR_LOG_WARN("No net found for .latch '%s' clock (clk pin)\n", netlist.block_name(blk_id).c_str());
clk_net = make_unconn(unconn_count, PinType::SINK);
}
//Latch type: VPR always assumes rising edge
auto type = "re";
//Latch initial value
int init_val = 3; //Unkown or unspecified
//The initial value is stored as a single value in the truth table
const auto& so_cover = netlist.block_truth_table(blk_id);
if (so_cover.size() == 1) {
VTR_ASSERT(so_cover.size() == 1); //Only one row
VTR_ASSERT(so_cover[0].size() == 1); //Only one column
switch (so_cover[0][0]) {
case vtr::LogicValue::TRUE:
init_val = 1;
break;
case vtr::LogicValue::FALSE:
init_val = 0;
break;
case vtr::LogicValue::DONT_CARE:
init_val = 2;
break;
case vtr::LogicValue::UNKOWN:
init_val = 3;
break;
default:
VTR_ASSERT_MSG(false, "Unrecognzied latch initial state");
}
}
fprintf(f, ".latch %s %s %s %s %d\n", d_net.c_str(), q_net.c_str(), type, clk_net.c_str(), init_val);
fprintf(f, "\n");
}
}
//Names
for (auto blk_id : netlist.blocks()) {
if (netlist.block_type(blk_id) == AtomBlockType::BLOCK) {
const t_model* blk_model = netlist.block_model(blk_id);
if (blk_model->name != std::string(MODEL_NAMES)) continue;
std::vector<AtomNetId> nets;
//Collect Inputs
auto input_ports = netlist.block_input_ports(blk_id);
VTR_ASSERT(input_ports.size() <= 1);
for (auto in_pin_id : netlist.block_input_pins(blk_id)) {
auto net_id = netlist.pin_net(in_pin_id);
nets.push_back(net_id);
}
//Collect Outputs
auto out_pins = netlist.block_output_pins(blk_id);
if (out_pins.size() == 1) {
auto out_net_id = netlist.pin_net(*out_pins.begin());
nets.push_back(out_net_id);
} else {
VTR_ASSERT(out_pins.size() == 0);
}
fprintf(f, ".names ");
for (size_t i = 0; i < nets.size(); ++i) {
auto net_id = nets[i];
fprintf(f, "%s", netlist.net_name(net_id).c_str());
if (i != nets.size() - 1) {
fprintf(f, " ");
}
}
fprintf(f, "\n");
//Print the truth table
for (auto row : netlist.block_truth_table(blk_id)) {
for (size_t i = 0; i < row.size(); ++i) {
//Space between input and output columns
if (i == row.size() - 1) {
fprintf(f, " ");
}
switch (row[i]) {
case vtr::LogicValue::TRUE:
fprintf(f, "1");
break;
case vtr::LogicValue::FALSE:
fprintf(f, "0");
break;
case vtr::LogicValue::DONT_CARE:
fprintf(f, "-");
break;
default:
VTR_ASSERT_MSG(false, "Valid single-output cover logic value");
}
}
fprintf(f, "\n");
}
fprintf(f, "\n");
}
}
//Subckt
std::set<const t_model*> subckt_models;
for (auto blk_id : netlist.blocks()) {
const t_model* blk_model = netlist.block_model(blk_id);
if (blk_model->name == std::string(MODEL_LATCH)
|| blk_model->name == std::string(MODEL_NAMES)
|| blk_model->name == std::string(MODEL_INPUT)
|| blk_model->name == std::string(MODEL_OUTPUT)) {
continue;
}
//Must be a subckt
subckt_models.insert(blk_model);
std::vector<AtomPortId> ports;
for (auto port_id : netlist.block_ports(blk_id)) {
VTR_ASSERT(netlist.port_width(port_id) > 0);
ports.push_back(port_id);
}
fprintf(f, ".subckt %s \\\n", blk_model->name);
for (size_t i = 0; i < ports.size(); i++) {
auto width = netlist.port_width(ports[i]);
for (size_t j = 0; j < width; ++j) {
fprintf(f, "%s%s", INDENT, netlist.port_name(ports[i]).c_str());
if (width != 1) {
fprintf(f, "[%zu]", j);
}
fprintf(f, "=");
auto net_id = netlist.port_net(ports[i], j);
if (net_id) {
fprintf(f, "%s", netlist.net_name(net_id).c_str());
} else {
PortType port_type = netlist.port_type(ports[i]);
PinType pin_type = PinType::OPEN;
switch (port_type) {
case PortType::INPUT: //fallthrough
case PortType::CLOCK:
pin_type = PinType::SINK;
break;
case PortType::OUTPUT:
pin_type = PinType::DRIVER;
break;
default:
VTR_ASSERT_OPT_MSG(false, "Invalid port type");
}
fprintf(f, "%s", make_unconn(unconn_count, pin_type).c_str());
}
if (i != ports.size() - 1 || j != width - 1) {
fprintf(f, " \\\n");
}
}
}
fprintf(f, "\n");
for (auto param : netlist.block_params(blk_id)) {
fprintf(f, ".param %s %s\n", param.first.c_str(), param.second.c_str());
}
for (auto attr : netlist.block_attrs(blk_id)) {
fprintf(f, ".attr %s %s\n", attr.first.c_str(), attr.second.c_str());
}
fprintf(f, "\n");
}
fprintf(f, ".end\n"); //Main model
fprintf(f, "\n");
//The subckt models
for (const t_model* model : subckt_models) {
fprintf(f, ".model %s\n", model->name);
fprintf(f, ".inputs");
const t_model_ports* port = model->inputs;
while (port) {
VTR_ASSERT(port->size >= 0);
if (port->size == 1) {
fprintf(f, " \\\n");
fprintf(f, "%s%s", INDENT, port->name);
} else {
for (int i = 0; i < port->size; ++i) {
fprintf(f, " \\\n");
fprintf(f, "%s%s[%d]", INDENT, port->name, i);
}
}
port = port->next;
}
fprintf(f, "\n");
fprintf(f, ".outputs");
port = model->outputs;
while (port) {
VTR_ASSERT(port->size >= 0);
if (port->size == 1) {
fprintf(f, " \\\n");
fprintf(f, "%s%s", INDENT, port->name);
} else {
for (int i = 0; i < port->size; ++i) {
fprintf(f, " \\\n");
fprintf(f, "%s%s[%d]", INDENT, port->name, i);
}
}
port = port->next;
}
fprintf(f, "\n");
fprintf(f, ".blackbox\n");
fprintf(f, ".end\n");
fprintf(f, "\n");
}
}
std::string atom_pin_arch_name(const AtomNetlist& netlist, const AtomPinId pin) {
std::string arch_name;
AtomBlockId blk = netlist.pin_block(pin);
AtomPortId port = netlist.pin_port(pin);
arch_name += netlist.block_model(blk)->name;
arch_name += ".";
arch_name += netlist.port_model(port)->name;
arch_name += "[";
arch_name += std::to_string(netlist.pin_port_bit(pin));
arch_name += "]";
return arch_name;
}
int mark_constant_generators(AtomNetlist& netlist, e_const_gen_inference const_gen_inference_method, int verbosity) {
int num_undriven_pins_marked_const = mark_undriven_primitive_outputs_as_constant(netlist, verbosity);
VTR_LOGV(verbosity > 0, "Inferred %4d additional primitive pins as constant generators since they have no combinationally connected inputs\n", num_undriven_pins_marked_const);
int num_inferred_pins_marked_const = infer_and_mark_constant_pins(netlist, const_gen_inference_method, verbosity);
VTR_LOGV(verbosity > 0, "Inferred %4d additional primitive pins as constant generators due to constant inputs\n", num_inferred_pins_marked_const);
return num_undriven_pins_marked_const + num_inferred_pins_marked_const;
}
int mark_undriven_primitive_outputs_as_constant(AtomNetlist& netlist, int verbosity) {
//For each model/primtiive we know the set of internal timing edges.
//
//If there is not upstream pin/net driving *any* of an outputs timing edges
//we assume that pin is a constant.
size_t num_pins_marked_constant = 0;
for (AtomBlockId blk : netlist.blocks()) {
if (!blk) continue;
//Don't mark primary I/Os as constants
if (netlist.block_type(blk) != AtomBlockType::BLOCK) continue;
for (AtomPortId output_port : netlist.block_output_ports(blk)) {
const t_model_ports* model_port = netlist.port_model(output_port);
//Don't mark sequential or clock generator ports as constants
if (!model_port->clock.empty() || model_port->is_clock) continue;
//Find the upstream combinationally connected ports
std::vector<AtomPortId> upstream_ports = find_combinationally_connected_input_ports(netlist, output_port);
//Check if any of the 'upstream' input pins have connected nets
//
//Note that we only check to see whether they are *connected* not whether they are non-constant.
//Inference of pins as constant generators from upstream *constant nets* is handled elsewhere.
bool has_connected_inputs = false;
for (AtomPortId input_port : upstream_ports) {
for (AtomPinId input_pin : netlist.port_pins(input_port)) {
AtomNetId input_net = netlist.pin_net(input_pin);
if (input_net) {
has_connected_inputs = true;
break;
}
}
}
if (!has_connected_inputs) {
//The current output port has no inputs driving the primitive's internal
//timing edges. Therefore we treat all its pins as constant generators.
for (AtomPinId output_pin : netlist.port_pins(output_port)) {
if (netlist.pin_is_constant(output_pin)) continue;
VTR_LOGV(verbosity > 1, "Marking pin '%s' as constant since it has no combinationally connected inputs\n",
netlist.pin_name(output_pin).c_str());
netlist.set_pin_is_constant(output_pin, true);
++num_pins_marked_constant;
}
}
}
}
return num_pins_marked_constant;
}
int infer_and_mark_constant_pins(AtomNetlist& netlist, e_const_gen_inference const_gen_inference_method, int verbosity) {
size_t num_pins_inferred_constant = 0;
//It is possible that by marking one constant generator
//it may 'reveal' another constant generator downstream.
//As a result we iteratively mark constant generators until
//no additional ones are identified.
size_t num_pins_marked = 0;
do {
num_pins_marked = 0;
//Look through all the blocks marking those pins which are
//constant generataors
for (auto blk : netlist.blocks()) {
if (!blk) continue;
num_pins_marked += infer_and_mark_block_pins_constant(netlist, blk, const_gen_inference_method, verbosity);
}
num_pins_inferred_constant += num_pins_marked;
} while (num_pins_marked != 0);
return num_pins_inferred_constant;
}
int infer_and_mark_block_pins_constant(AtomNetlist& netlist, AtomBlockId block, e_const_gen_inference const_gen_inference_method, int verbosity) {
size_t num_pins_marked_constant = 0;
num_pins_marked_constant += infer_and_mark_block_combinational_outputs_constant(netlist, block, const_gen_inference_method, verbosity);
num_pins_marked_constant += infer_and_mark_block_sequential_outputs_constant(netlist, block, const_gen_inference_method, verbosity);
return num_pins_marked_constant;
}
int infer_and_mark_block_combinational_outputs_constant(AtomNetlist& netlist, AtomBlockId blk, e_const_gen_inference const_gen_inference_method, int verbosity) {
//Only if combinational constant generator inference enabled
if (const_gen_inference_method != e_const_gen_inference::COMB
&& const_gen_inference_method != e_const_gen_inference::COMB_SEQ) {
return 0;
}
VTR_ASSERT(const_gen_inference_method == e_const_gen_inference::COMB
|| const_gen_inference_method == e_const_gen_inference::COMB_SEQ);
//Don't mark primary I/Os as constants
if (netlist.block_type(blk) != AtomBlockType::BLOCK) return 0;
size_t num_pins_marked_constant = 0;
for (AtomPortId output_port : netlist.block_output_ports(blk)) {
const t_model_ports* model_port = netlist.port_model(output_port);
//Only handle combinational ports
if (!model_port->clock.empty() || model_port->is_clock) continue;
//Find the upstream combinationally connected ports
std::vector<AtomPortId> upstream_ports = find_combinationally_connected_input_ports(netlist, output_port);
//Check if any of the 'upstream' input pins have connected nets
//
//Here we check whether *all* of the upstream nets are constants
bool all_constant_inputs = true;
for (AtomPortId input_port : upstream_ports) {
for (AtomPinId input_pin : netlist.port_pins(input_port)) {
AtomNetId input_net = netlist.pin_net(input_pin);
if (input_net && !netlist.net_is_constant(input_net)) {
all_constant_inputs = false;
break;
} else {
VTR_ASSERT(!input_net || netlist.net_is_constant(input_net));
}
}
}
if (all_constant_inputs) {
//The current output port is combinational and has only constant upstream inputs.
//Therefore we treat all its pins as constant generators.
for (AtomPinId output_pin : netlist.port_pins(output_port)) {
if (netlist.pin_is_constant(output_pin)) continue;
VTR_LOGV(verbosity > 1, "Marking combinational pin '%s' as constant since all it's upstream inputs are constant\n",
netlist.pin_name(output_pin).c_str());
netlist.set_pin_is_constant(output_pin, true);
++num_pins_marked_constant;
}
}
}
return num_pins_marked_constant;
}
int infer_and_mark_block_sequential_outputs_constant(AtomNetlist& netlist, AtomBlockId blk, e_const_gen_inference const_gen_inference_method, int verbosity) {
//Only if sequential constant generator inference enabled
if (const_gen_inference_method != e_const_gen_inference::COMB_SEQ) {
return 0;
}
VTR_ASSERT(const_gen_inference_method == e_const_gen_inference::COMB_SEQ);
//Don't mark primary I/Os as constants
if (netlist.block_type(blk) != AtomBlockType::BLOCK) return 0;
//Collect the sequential output ports
std::vector<AtomPortId> sequential_outputs;
for (AtomPortId output_port : netlist.block_output_ports(blk)) {
const t_model_ports* model_port = netlist.port_model(output_port);
if (model_port->clock.empty() || model_port->is_clock) continue;
//Only handle sequential ports
sequential_outputs.push_back(output_port);
}
if (sequential_outputs.empty()) return 0; //No sequential outputs, nothing to do
//We mark sequential output ports as constants only when *all* inputs are constant
//
//Note that this is a safely pessimistic condition, which means there may be some constant
//sequential pins (i.e. those which depend on only a subset of input ports) are not marked
//as constants.
//
//To improve upon this we could look at the internal dependencies specified between comb/seq
//inputs and seq outputs. Provided they were all constants we could mark the sequential
//output as constant. However this is left as future work. In particularl many of
//the architecture files do not yet specify block-internal timing paths which would make
//the proposed approach optimistic.
bool all_inputs_constant = true;
for (AtomPinId input_pin : netlist.block_input_pins(blk)) {
AtomNetId input_net = netlist.pin_net(input_pin);
if (input_net && !netlist.net_is_constant(input_net)) {
all_inputs_constant = false;
break;
} else {
VTR_ASSERT(!input_net || netlist.net_is_constant(input_net));
}
}
if (!all_inputs_constant) return 0;
int num_pins_marked_constant = 0;
for (AtomPortId output_port : sequential_outputs) {
for (AtomPinId output_pin : netlist.port_pins(output_port)) {
if (netlist.pin_is_constant(output_pin)) continue;
VTR_LOGV(verbosity > 1, "Marking sequential pin '%s' as constant since all inputs to block '%s' (%s) are constant\n",
netlist.pin_name(output_pin).c_str(),
netlist.block_name(blk).c_str(),
netlist.block_model(blk)->name);
netlist.set_pin_is_constant(output_pin, true);
++num_pins_marked_constant;
}
}
return num_pins_marked_constant;
}
std::vector<AtomPortId> find_combinationally_connected_input_ports(const AtomNetlist& netlist, AtomPortId output_port) {
std::vector<AtomPortId> upstream_ports;
VTR_ASSERT(netlist.port_type(output_port) == PortType::OUTPUT);
std::string out_port_name = netlist.port_name(output_port);
AtomBlockId blk = netlist.port_block(output_port);
//Look through each block input port to find those which are combinationally connected to the output port
for (AtomPortId input_port : netlist.block_input_ports(blk)) {
const t_model_ports* input_model_port = netlist.port_model(input_port);
for (const std::string& sink_port_name : input_model_port->combinational_sink_ports) {
if (sink_port_name == out_port_name) {
upstream_ports.push_back(input_port);
}
}
}
return upstream_ports;
}
std::vector<AtomPortId> find_combinationally_connected_clock_ports(const AtomNetlist& netlist, AtomPortId output_port) {
std::vector<AtomPortId> upstream_ports;
VTR_ASSERT(netlist.port_type(output_port) == PortType::OUTPUT);
std::string out_port_name = netlist.port_name(output_port);
AtomBlockId blk = netlist.port_block(output_port);
//Look through each block input port to find those which are combinationally connected to the output port
for (AtomPortId clock_port : netlist.block_clock_ports(blk)) {
const t_model_ports* clock_model_port = netlist.port_model(clock_port);
for (const std::string& sink_port_name : clock_model_port->combinational_sink_ports) {
if (sink_port_name == out_port_name) {
upstream_ports.push_back(clock_port);
}
}
}
return upstream_ports;
}
void absorb_buffer_luts(AtomNetlist& netlist, int verbosity) {
//First we look through the netlist to find LUTs with identity logic functions
//we then remove those luts, replacing the net's they drove with the inputs to the
//buffer lut
size_t removed_buffer_count = 0;
//Remove the buffer luts
for (auto blk : netlist.blocks()) {
if (is_buffer_lut(netlist, blk)) {
if (remove_buffer_lut(netlist, blk, verbosity)) {
++removed_buffer_count;
}
}
}
VTR_LOGV(verbosity > 0, "Absorbed %zu LUT buffers\n", removed_buffer_count);
//TODO: absorb inverter LUTs?
}
bool is_buffer_lut(const AtomNetlist& netlist, const AtomBlockId blk) {
if (netlist.block_type(blk) == AtomBlockType::BLOCK) {
const t_model* blk_model = netlist.block_model(blk);
if (blk_model->name != std::string(MODEL_NAMES)) return false;
auto input_ports = netlist.block_input_ports(blk);
auto output_ports = netlist.block_output_ports(blk);
//Buffer LUTs have a single input port and a single output port
if (input_ports.size() == 1 && output_ports.size() == 1) {
//Count the number of connected input pins
size_t connected_input_pins = 0;
for (auto input_pin : netlist.block_input_pins(blk)) {
if (input_pin && netlist.pin_net(input_pin)) {
++connected_input_pins;
}
}
//Count the number of connected output pins
size_t connected_output_pins = 0;
for (auto output_pin : netlist.block_output_pins(blk)) {
if (output_pin && netlist.pin_net(output_pin)) {
++connected_output_pins;
}
}
//Both ports must be single bit
if (connected_input_pins == 1 && connected_output_pins == 1) {
//It is a single-input single-output LUT, we now
//inspect it's truth table
//
const auto& truth_table = netlist.block_truth_table(blk);
VTR_ASSERT_MSG(truth_table.size() == 1, "One truth-table row");
VTR_ASSERT_MSG(truth_table[0].size() == 2, "Two truth-table row entries");
//Check for valid buffer logic functions
// A LUT is a buffer provided it has the identity logic
// function and a single input. For example:
//
// .names in_buf out_buf
// 1 1
//
// and
//
// .names int_buf out_buf
// 0 0
//
// both implement logical identity.
if ((truth_table[0][0] == vtr::LogicValue::TRUE && truth_table[0][1] == vtr::LogicValue::TRUE)
|| (truth_table[0][0] == vtr::LogicValue::FALSE && truth_table[0][1] == vtr::LogicValue::FALSE)) {
//It is a buffer LUT
return true;
}
}
}
}
return false;
}
bool remove_buffer_lut(AtomNetlist& netlist, AtomBlockId blk, int verbosity) {
//General net connectivity, numbers equal pin ids
//
// 1 in 2 ----- m+1 out
// --------->| buf |---------> m+2
// | ----- |
// | |
// |--> 3 |----> m+3
// | |
// | ... | ...
// | |
// |--> m |----> m+k+1
//
//On the input net we have a single driver (pin 1) and sinks (pins 2 through m)
//On the output net we have a single driver (pin m+1) and sinks (pins m+2 through m+k+1)
//
//The resulting connectivity after removing the buffer is:
//
// 1 in
// --------------------------> m+2
// | |
// | |
// |--> 3 |----> m+3
// | |
// | ... | ...
// | |
// |--> m |----> m+k+1
//
//
//We remove the buffer and fix-up the connectivity using the following steps
// - Remove the buffer (this also removes pins 2 and m+1 from the 'in' and 'out' nets)
// - Copy the pins left on 'in' and 'out' nets
// - Remove the 'in' and 'out' nets (this sets the pin's associated net to invalid)
// - We create a new net using the pins we copied, setting pin 1 as the driver and
// all other pins as sinks
//Find the input and output nets
auto input_pins = netlist.block_input_pins(blk);
auto output_pins = netlist.block_output_pins(blk);
VTR_ASSERT(input_pins.size() == 1);
VTR_ASSERT(output_pins.size() == 1);
auto input_pin = *input_pins.begin(); //i.e. pin 2
auto output_pin = *output_pins.begin(); //i.e. pin m+1
auto input_net = netlist.pin_net(input_pin);
auto output_net = netlist.pin_net(output_pin);
VTR_LOGV_WARN(verbosity > 1, "Attempting to remove buffer '%s' (%s) from net '%s' to net '%s'\n", netlist.block_name(blk).c_str(), netlist.block_model(blk)->name, netlist.net_name(input_net).c_str(), netlist.net_name(output_net).c_str());
//Collect the new driver and sink pins
AtomPinId new_driver = netlist.net_driver(input_net);
if (!new_driver) {
VTR_LOGV_WARN(verbosity > 2, "Buffer '%s' has no input and will not be absorbed (left to be swept)\n", netlist.block_name(blk).c_str(), netlist.block_model(blk)->name, netlist.net_name(input_net).c_str(), netlist.net_name(output_net).c_str());
return false; //Dangling/undriven input, leave buffer to be swept
}
VTR_ASSERT(netlist.pin_type(new_driver) == PinType::DRIVER);
std::vector<AtomPinId> new_sinks;
auto input_sinks = netlist.net_sinks(input_net);
auto output_sinks = netlist.net_sinks(output_net);
//We don't copy the input pin (i.e. pin 2)
std::copy_if(input_sinks.begin(), input_sinks.end(), std::back_inserter(new_sinks),
[input_pin](AtomPinId id) {
return id != input_pin;
});
//Since we are copying sinks we don't include the output driver (i.e. pin m+1)
std::copy(output_sinks.begin(), output_sinks.end(), std::back_inserter(new_sinks));
VTR_ASSERT(new_sinks.size() == input_sinks.size() + output_sinks.size() - 1);
//We now need to determine the name of the 'new' net
//
// We need to be careful about this name since a net pin could be
// a Primary-Input/Primary-Output, and we don't want to change PI/PO names (for equivalance checking)
//
//Check if we have any PI/POs in the new net's pins
// Note that the driver can only (potentially) be an INPAD, and the sinks only (potentially) OUTPADs
AtomBlockType driver_block_type = netlist.block_type(netlist.pin_block(new_driver));
bool driver_is_pi = (driver_block_type == AtomBlockType::INPAD);
bool po_in_input_sinks = std::any_of(input_sinks.begin(), input_sinks.end(),
[&](AtomPinId pin_id) {
VTR_ASSERT(netlist.pin_type(pin_id) == PinType::SINK);
AtomBlockId blk_id = netlist.pin_block(pin_id);
return netlist.block_type(blk_id) == AtomBlockType::OUTPAD;
});
bool po_in_output_sinks = std::any_of(output_sinks.begin(), output_sinks.end(),
[&](AtomPinId pin_id) {
VTR_ASSERT(netlist.pin_type(pin_id) == PinType::SINK);
AtomBlockId blk_id = netlist.pin_block(pin_id);
return netlist.block_type(blk_id) == AtomBlockType::OUTPAD;
});
std::string new_net_name;
if ((driver_is_pi || po_in_input_sinks) && !po_in_output_sinks) {
//Must use the input name to perserve primary-input or primary-output name
new_net_name = netlist.net_name(input_net);
} else if (!(driver_is_pi || po_in_input_sinks) && po_in_output_sinks) {
//Must use the output name to perserve primary-output name
new_net_name = netlist.net_name(output_net);
} else {
//Arbitrarily merge the net names
new_net_name = netlist.net_name(input_net) + "__" + netlist.net_name(output_net);
}
size_t initial_input_net_pins = netlist.net_pins(input_net).size();
VTR_LOGV_WARN(verbosity > 2, "%s is a LUT buffer and will be absorbed\n", netlist.block_name(blk).c_str());
//Remove the buffer
//
// Note that this removes pins 2 and m+1
netlist.remove_block(blk);
VTR_ASSERT(netlist.net_pins(input_net).size() == initial_input_net_pins - 1); //Should have removed pin 2
VTR_ASSERT(netlist.net_driver(output_net) == AtomPinId::INVALID()); //Should have removed pin m+1
//Remove the nets
netlist.remove_net(input_net);
netlist.remove_net(output_net);
//Create the new merged net
AtomNetId new_net = netlist.add_net(new_net_name, new_driver, new_sinks);
VTR_ASSERT(netlist.net_pins(new_net).size() == initial_input_net_pins - 1 + output_sinks.size());
return true;
}
bool is_removable_block(const AtomNetlist& netlist, const AtomBlockId blk_id, std::string* reason) {
//Any block with no fanout is removable
for (AtomPinId pin_id : netlist.block_output_pins(blk_id)) {
if (!pin_id) continue;
AtomNetId net_id = netlist.pin_net(pin_id);
if (net_id) {
//There is a valid output net
return false;
}
}
if (reason) *reason = "has no fanout";
return true;
}
bool is_removable_input(const AtomNetlist& netlist, const AtomBlockId blk_id, std::string* reason) {
AtomBlockType type = netlist.block_type(blk_id);
//Only return true if an INPAD
if (type != AtomBlockType::INPAD) return false;
return is_removable_block(netlist, blk_id, reason);
}
bool is_removable_output(const AtomNetlist& netlist, const AtomBlockId blk_id, std::string* reason) {
AtomBlockType type = netlist.block_type(blk_id);
//Only return true if an OUTPAD
if (type != AtomBlockType::OUTPAD) return false;
//An output is only removable if it has no fan-in
for (AtomPinId pin_id : netlist.block_input_pins(blk_id)) {
if (!pin_id) continue;
AtomNetId net_id = netlist.pin_net(pin_id);
if (net_id) {
//There is a valid input net
return false;
}
}
if (reason) *reason = "has no fanin";
return true;
}
size_t sweep_constant_primary_outputs(AtomNetlist& netlist, int verbosity) {
size_t removed_count = 0;
for (AtomBlockId blk_id : netlist.blocks()) {
if (!blk_id) continue;
if (netlist.block_type(blk_id) == AtomBlockType::OUTPAD) {
VTR_ASSERT(netlist.block_output_pins(blk_id).size() == 0);
VTR_ASSERT(netlist.block_clock_pins(blk_id).size() == 0);
bool all_inputs_are_const = true;
for (AtomPinId pin_id : netlist.block_input_pins(blk_id)) {
AtomNetId net_id = netlist.pin_net(pin_id);
if (net_id && !netlist.net_is_constant(net_id)) {
all_inputs_are_const = false;
break;
}
}
if (all_inputs_are_const) {
//All inputs are constant, so we should remove this output
VTR_LOGV_WARN(verbosity > 2, "Sweeping constant primary output '%s'\n", netlist.block_name(blk_id).c_str());
netlist.remove_block(blk_id);
removed_count++;
}
}
}
return removed_count;
}
size_t sweep_iterative(AtomNetlist& netlist,
bool should_sweep_ios,
bool should_sweep_nets,
bool should_sweep_blocks,
bool should_sweep_constant_primary_outputs,
e_const_gen_inference const_gen_inference_method,
int verbosity) {
size_t dangling_nets_swept = 0;
size_t dangling_blocks_swept = 0;
size_t dangling_inputs_swept = 0;
size_t dangling_outputs_swept = 0;
size_t constant_outputs_swept = 0;
size_t constant_generators_marked = 0;
//We perform multiple passes of sweeping, since sweeping something may
//enable more things to be swept afterward.
//
//We keep sweeping until nothing else is removed
size_t pass_dangling_nets_swept;
size_t pass_dangling_blocks_swept;
size_t pass_dangling_inputs_swept;
size_t pass_dangling_outputs_swept;
size_t pass_constant_outputs_swept;
size_t pass_constant_generators_marked;
do {
pass_dangling_nets_swept = 0;
pass_dangling_blocks_swept = 0;
pass_dangling_inputs_swept = 0;
pass_dangling_outputs_swept = 0;
pass_constant_outputs_swept = 0;
pass_constant_generators_marked = 0;
if (should_sweep_ios) {
pass_dangling_inputs_swept += sweep_inputs(netlist, verbosity);
pass_dangling_outputs_swept += sweep_outputs(netlist, verbosity);
}
if (should_sweep_blocks) {
pass_dangling_blocks_swept += sweep_blocks(netlist, verbosity);
}
if (should_sweep_nets) {
pass_dangling_nets_swept += sweep_nets(netlist, verbosity);
}
if (should_sweep_constant_primary_outputs) {
pass_constant_outputs_swept += sweep_constant_primary_outputs(netlist, verbosity);
}
pass_constant_generators_marked += mark_constant_generators(netlist, const_gen_inference_method, verbosity);
dangling_nets_swept += pass_dangling_nets_swept;
dangling_blocks_swept += pass_dangling_blocks_swept;
dangling_inputs_swept += pass_dangling_inputs_swept;
dangling_outputs_swept += pass_dangling_outputs_swept;
constant_outputs_swept += pass_constant_outputs_swept;
constant_generators_marked += pass_constant_generators_marked;
} while (pass_dangling_nets_swept != 0
|| pass_dangling_blocks_swept != 0
|| pass_dangling_inputs_swept != 0
|| pass_dangling_outputs_swept != 0
|| pass_constant_outputs_swept != 0
|| pass_constant_generators_marked != 0);
VTR_LOGV(verbosity > 0, "Swept input(s) : %zu\n", dangling_inputs_swept);
VTR_LOGV(verbosity > 0, "Swept output(s) : %zu (%zu dangling, %zu constant)\n",
dangling_outputs_swept + constant_outputs_swept,
dangling_outputs_swept,
constant_outputs_swept);
VTR_LOGV(verbosity > 0, "Swept net(s) : %zu\n", dangling_nets_swept);
VTR_LOGV(verbosity > 0, "Swept block(s) : %zu\n", dangling_blocks_swept);
VTR_LOGV(verbosity > 0, "Constant Pins Marked: %zu\n", constant_generators_marked);
return dangling_nets_swept
+ dangling_blocks_swept
+ dangling_inputs_swept
+ dangling_outputs_swept
+ constant_outputs_swept;
}
size_t sweep_blocks(AtomNetlist& netlist, int verbosity) {
//Identify any blocks (not inputs or outputs) for removal
std::unordered_set<AtomBlockId> blocks_to_remove;
for (auto blk_id : netlist.blocks()) {
if (!blk_id) continue;
AtomBlockType type = netlist.block_type(blk_id);
//Don't remove inpads/outpads here, we have seperate sweep functions for these
if (type == AtomBlockType::INPAD || type == AtomBlockType::OUTPAD) continue;
//We remove any blocks with no fanout
std::string reason;
if (is_removable_block(netlist, blk_id, &reason)) {
blocks_to_remove.insert(blk_id);
VTR_LOGV_WARN(verbosity > 1, "Block '%s' will be swept (%s)\n", netlist.block_name(blk_id).c_str(), reason.c_str());
}
}
//Remove them
for (auto blk_id : blocks_to_remove) {
netlist.remove_block(blk_id);
}
return blocks_to_remove.size();
}
size_t sweep_inputs(AtomNetlist& netlist, int verbosity) {
//Identify any inputs for removal
std::unordered_set<AtomBlockId> inputs_to_remove;
for (auto blk_id : netlist.blocks()) {
if (!blk_id) continue;
std::string reason;
if (is_removable_input(netlist, blk_id, &reason)) {
inputs_to_remove.insert(blk_id);
VTR_LOGV_WARN(verbosity > 1, "Primary input '%s' will be swept (%s)\n", netlist.block_name(blk_id).c_str(), reason.c_str());
}
}
//Remove them
for (auto blk_id : inputs_to_remove) {
netlist.remove_block(blk_id);
}
return inputs_to_remove.size();
}
size_t sweep_outputs(AtomNetlist& netlist, int verbosity) {
//Identify any outputs for removal
std::unordered_set<AtomBlockId> outputs_to_remove;
for (auto blk_id : netlist.blocks()) {
if (!blk_id) continue;
std::string reason;
if (is_removable_output(netlist, blk_id, &reason)) {
outputs_to_remove.insert(blk_id);
VTR_LOGV_WARN(verbosity > 1, "Primary output '%s' will be swept (%s)\n", netlist.block_name(blk_id).c_str(), reason.c_str());
}
}
//Remove them
for (auto blk_id : outputs_to_remove) {
netlist.remove_block(blk_id);
}
return outputs_to_remove.size();
}
size_t sweep_nets(AtomNetlist& netlist, int verbosity) {
//Find any nets with no fanout or no driver, and remove them
std::unordered_set<AtomNetId> nets_to_remove;
for (auto net_id : netlist.nets()) {
if (!net_id) continue;
if (!netlist.net_driver(net_id)) {
//No driver
VTR_LOGV_WARN(verbosity > 1, "Net '%s' has no driver and will be removed\n", netlist.net_name(net_id).c_str());
nets_to_remove.insert(net_id);
}
if (netlist.net_sinks(net_id).size() == 0) {
//No sinks
VTR_LOGV_WARN(verbosity > 1, "Net '%s' has no sinks and will be removed\n", netlist.net_name(net_id).c_str());
nets_to_remove.insert(net_id);
}
}
for (auto net_id : nets_to_remove) {
netlist.remove_net(net_id);
}
return nets_to_remove.size();
}
std::string make_unconn(size_t& unconn_count, PinType /*pin_type*/) {
#if 0
if(pin_type == PinType::DRIVER) {
return std::string("unconn") + std::to_string(unconn_count++);
} else {
return std::string("unconn");
}
#else
return std::string("__vpr__unconn") + std::to_string(unconn_count++);
#endif
}
bool truth_table_encodes_on_set(const AtomNetlist::TruthTable& truth_table) {
bool encodes_on_set = false;
if (truth_table.empty()) {
//An empyt truth table corresponds to a constant zero
// making whether the 'on' set is encoded an arbitrary
// choice (we choose true)
encodes_on_set = true;
} else {
VTR_ASSERT_MSG(truth_table[0].size() > 0, "Can not have an empty truth-table row");
//Inspect the last (output) value
auto out_val = truth_table[0][truth_table[0].size() - 1];
switch (out_val) {
case vtr::LogicValue::TRUE:
encodes_on_set = true;
break;
case vtr::LogicValue::FALSE:
encodes_on_set = false;
break;
default:
VPR_FATAL_ERROR(VPR_ERROR_OTHER, "Unrecognized truth-table output value");
}
}
return encodes_on_set;
}
AtomNetlist::TruthTable permute_truth_table(const AtomNetlist::TruthTable& truth_table, const size_t num_inputs, const std::vector<int>& permutation) {
AtomNetlist::TruthTable permuted_truth_table;
for (const auto& row : truth_table) {
//Space for the permuted row: num inputs + one output
std::vector<vtr::LogicValue> permuted_row(num_inputs + 1, vtr::LogicValue::FALSE);
//Permute the inputs in the row
for (size_t i = 0; i < row.size() - 1; i++) {
int permuted_idx = permutation[i];
permuted_row[permuted_idx] = row[i];
}
//Assign the output value
permuted_row[permuted_row.size() - 1] = row[row.size() - 1];
permuted_truth_table.push_back(permuted_row);
}
return permuted_truth_table;
}
AtomNetlist::TruthTable expand_truth_table(const AtomNetlist::TruthTable& truth_table, const size_t num_inputs) {
AtomNetlist::TruthTable expanded_truth_table;
for (const auto& row : truth_table) {
//Initialize an empty row
std::vector<vtr::LogicValue> expanded_row(num_inputs + 1, vtr::LogicValue::FALSE);
//Copy the existing input values
for (size_t i = 0; i < row.size() - 1; ++i) {
expanded_row[i] = row[i];
}
//Set the output value
expanded_row[expanded_row.size() - 1] = row[row.size() - 1];
expanded_truth_table.push_back(expanded_row);
}
return expanded_truth_table;
}
std::vector<vtr::LogicValue> truth_table_to_lut_mask(const AtomNetlist::TruthTable& truth_table, const size_t num_inputs) {
bool on_set = truth_table_encodes_on_set(truth_table);
//Initialize the lut mask
size_t mask_bits = std::pow(2, num_inputs);
std::vector<vtr::LogicValue> mask;
if (on_set) {
//If we are encoding the on-set the background value is false
mask = std::vector<vtr::LogicValue>(mask_bits, vtr::LogicValue::FALSE);
} else {
//If we are encoding the off-set the background value is true
mask = std::vector<vtr::LogicValue>(mask_bits, vtr::LogicValue::TRUE);
}
for (const auto& row : truth_table) {
//Each row in the truth table (excluding the output) is a cube,
//and may need to be expanded to account for don't cares
std::vector<vtr::LogicValue> cube(row.begin(), --row.end());
VTR_ASSERT(cube.size() == num_inputs);
std::vector<size_t> minterms;
for (auto minterm : cube_to_minterms(cube)) {
//Mark the minterms in the mask
VTR_ASSERT(minterm < mask.size());
if (on_set) {
mask[minterm] = vtr::LogicValue::TRUE;
} else {
mask[minterm] = vtr::LogicValue::FALSE;
}
}
}
return mask;
}
std::vector<size_t> cube_to_minterms(std::vector<vtr::LogicValue> cube) {
std::vector<size_t> minterms;
cube_to_minterms_recurr(cube, minterms);
return minterms;
}
void cube_to_minterms_recurr(std::vector<vtr::LogicValue> cube, std::vector<size_t>& minterms) {
bool cube_has_dc = false;
for (size_t i = 0; i < cube.size(); ++i) {
if (cube[i] == vtr::LogicValue::DONT_CARE) {
//If we have a don't care we need to recursively expand
//the don't care for the true and false cases
cube_has_dc = true;
//True case
std::vector<vtr::LogicValue> cube_true = cube;
cube_true[i] = vtr::LogicValue::TRUE;
cube_to_minterms_recurr(cube_true, minterms); //Recurse
//False case
std::vector<vtr::LogicValue> cube_false = cube;
cube_false[i] = vtr::LogicValue::FALSE;
cube_to_minterms_recurr(cube_false, minterms); //Recurss
} else {
VTR_ASSERT(cube[i] == vtr::LogicValue::TRUE
|| cube[i] == vtr::LogicValue::FALSE);
}
}
if (!cube_has_dc) {
//This cube is actually a minterm
//Convert the cube to the minterm number
size_t minterm = 0;
for (size_t i = 0; i < cube.size(); ++i) {
//The minterm is the integer representation of the
//binary number stored in the cube. We do the conversion
//by summing up all powers of two where the cube is true.
if (cube[i] == vtr::LogicValue::TRUE) {
minterm += (1 << i); //Note powers of two by shifting
}
}
//Save the minterm number
minterms.push_back(minterm);
}
}
//Find all the nets connected to clock pins in the netlist
std::set<AtomNetId> find_netlist_physical_clock_nets(const AtomNetlist& netlist) {
std::set<AtomNetId> clock_nets; //The clock nets
std::map<const t_model*, std::vector<const t_model_ports*>> clock_gen_ports; //Records info about clock generating ports
//Look through all the blocks (except I/Os) to find sink clock pins, or
//clock generators
//
//Since we don't have good information about what pins are clock generators we build a lookup as we go
for (auto blk_id : netlist.blocks()) {
if (!blk_id) continue;
AtomBlockType type = netlist.block_type(blk_id);
if (type != AtomBlockType::BLOCK) continue;
//Save any clock generating ports on this model type
const t_model* model = netlist.block_model(blk_id);
VTR_ASSERT(model);
if (clock_gen_ports.find(model) == clock_gen_ports.end()) {
//First time we've seen this model, intialize it
clock_gen_ports[model] = {};
//Look at all the ports to find clock generators
for (const t_model_ports* model_port = model->outputs; model_port; model_port = model_port->next) {
VTR_ASSERT(model_port->dir == OUT_PORT);
if (model_port->is_clock) {
//Clock generator
clock_gen_ports[model].push_back(model_port);
}
}
}
//Look for connected input clocks
for (auto pin_id : netlist.block_clock_pins(blk_id)) {
if (!pin_id) continue;
AtomNetId clk_net_id = netlist.pin_net(pin_id);
VTR_ASSERT(clk_net_id);
clock_nets.insert(clk_net_id);
}
//Look for any generated clocks
if (!clock_gen_ports[model].empty()) {
//This is a clock generator
//Check all the clock generating ports
for (const t_model_ports* model_port : clock_gen_ports[model]) {
AtomPortId clk_gen_port = netlist.find_atom_port(blk_id, model_port);
for (AtomPinId pin_id : netlist.port_pins(clk_gen_port)) {
if (!pin_id) continue;
AtomNetId clk_net_id = netlist.pin_net(pin_id);
if (!clk_net_id) continue;
clock_nets.insert(clk_net_id);
}
}
}
}
return clock_nets;
}
//Finds all logical clock drivers in the netlist (by back-tracing through logic)
std::set<AtomPinId> find_netlist_logical_clock_drivers(const AtomNetlist& netlist) {
auto clock_nets = find_netlist_physical_clock_nets(netlist);
//We now have a set of nets which drive clock pins
//
//However, some of them may be the same logical clock (e.g. if there are
//buffers between them). Here we trace-back through any clock buffers
//to find the true source
size_t assumed_buffer_count = 0;
std::set<AtomNetId> prev_clock_nets;
while (prev_clock_nets != clock_nets) { //Still tracing back
prev_clock_nets = clock_nets;
clock_nets.clear();
for (auto clk_net : prev_clock_nets) {
AtomPinId driver_pin = netlist.net_driver(clk_net);
AtomPortId driver_port = netlist.pin_port(driver_pin);
AtomBlockId driver_blk = netlist.port_block(driver_port);
std::vector<AtomPortId> upstream_ports;
if (netlist.block_model(driver_blk)->name == std::string(".names")) {
//For .names we allow tracing back through data connections
//which allows us to traceback through white-box .names buffers
upstream_ports = find_combinationally_connected_input_ports(netlist, driver_port);
} else {
//For black boxes, we only trace back through inputs marked as clocks
upstream_ports = find_combinationally_connected_clock_ports(netlist, driver_port);
}
if (upstream_ports.empty()) {
//This net is a root net of a clock, keep it
clock_nets.insert(clk_net);
} else {
//Trace the clock back through any combinational logic
//
// We are assuming that the combinational connections are independent and non-inverting.
// If this is not the case, it is up to the end-user to specify the clocks explicitly
// at the intermediate pins in the netlist.
for (AtomPortId upstream_port : upstream_ports) {
for (AtomPinId upstream_pin : netlist.port_pins(upstream_port)) {
AtomNetId upstream_net = netlist.pin_net(upstream_pin);
VTR_ASSERT(upstream_net);
VTR_LOG_WARN("Assuming clocks may propagate through %s (%s) from pin %s to %s (assuming a non-inverting buffer).\n",
netlist.block_name(driver_blk).c_str(), netlist.block_model(driver_blk)->name,
netlist.pin_name(upstream_pin).c_str(), netlist.pin_name(driver_pin).c_str());
clock_nets.insert(upstream_net);
++assumed_buffer_count;
}
}
}
}
}
if (assumed_buffer_count > 0) {
VTR_LOG_WARN(
"Assumed %zu netlist logic connections may be clock buffers. "
"To override this behaviour explicitly create clocks at the appropriate netlist pins.\n",
assumed_buffer_count);
}
//Extract the net drivers
std::set<AtomPinId> clock_drivers;
for (auto net : clock_nets) {
AtomPinId driver = netlist.net_driver(net);
if (netlist.pin_is_constant(driver)) {
//Constant generators (e.g. gnd) are not clocks
continue;
}
clock_drivers.insert(driver);
}
return clock_drivers;
}
//Print information about clocks
void print_netlist_clock_info(const AtomNetlist& netlist) {
std::set<AtomPinId> netlist_clock_drivers = find_netlist_logical_clock_drivers(netlist);
VTR_LOG("Netlist contains %zu clocks\n", netlist_clock_drivers.size());
//Print out pin/block fanout info for each block
for (auto clock_driver : netlist_clock_drivers) {
AtomNetId net_id = netlist.pin_net(clock_driver);
auto sinks = netlist.net_sinks(net_id);
size_t fanout = sinks.size();
std::set<AtomBlockId> clk_blks;
for (auto pin_id : sinks) {
auto blk_id = netlist.pin_block(pin_id);
clk_blks.insert(blk_id);
}
VTR_LOG(" Netlist Clock '%s' Fanout: %zu pins (%.1f%), %zu blocks (%.1f%)\n", netlist.net_name(net_id).c_str(), fanout, 100. * float(fanout) / netlist.pins().size(), clk_blks.size(), 100 * float(clk_blks.size()) / netlist.blocks().size());
}
}
bool is_buffer(const AtomNetlist& netlist, const AtomBlockId blk) {
//For now only support LUT buffers
//TODO: In the future could add support for non-LUT buffers
return is_buffer_lut(netlist, blk);
}