blob: 636f00fc21ef0e5d7738304fe9a357c066d034d7 [file] [log] [blame]
#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"
//If defined produces verbose output while sweeping the netlist
//#define SWEEP_VERBOSE
std::map<AtomNetId,std::vector<AtomPinId>> find_clock_used_as_data_pins(const AtomNetlist& netlist);
AtomBlockId fix_clock_to_data_pins(AtomNetlist& netlist,
const AtomNetId clock_net, const std::vector<AtomPinId>& data_pins,
const t_model* blk_model,
const t_model_ports* model_clock_port, const BitIndex clock_port_bit,
const t_model_ports* model_data_port, const BitIndex data_port_bit);
std::vector<AtomBlockId> identify_buffer_luts(const AtomNetlist& netlist);
bool is_buffer_lut(const AtomNetlist& netlist, const AtomBlockId blk);
bool is_removable_block(const AtomNetlist& netlist, const AtomBlockId blk);
bool is_removable_input(const AtomNetlist& netlist, const AtomBlockId blk);
bool is_removable_output(const AtomNetlist& netlist, const AtomBlockId blk);
void remove_buffer_lut(AtomNetlist& netlist, AtomBlockId blk);
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_THROW(VPR_ERROR_ATOM_NETLIST, "Unrecognzied latch port '%s'", netlist.port_name(port_id).c_str());
}
}
}
}
if(d_net.empty()) {
d_net = make_unconn(unconn_count, PinType::SINK);
}
if(q_net.empty()) {
q_net = make_unconn(unconn_count, PinType::DRIVER);
}
if(clk_net.empty()) {
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);
VTR_ASSERT(out_pins.size() == 1);
auto out_net_id = netlist.pin_net(*out_pins.begin());
nets.push_back(out_net_id);
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");
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");
}
}
void absorb_buffer_luts(AtomNetlist& netlist) {
//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
//Find buffer luts
auto buffer_luts = identify_buffer_luts(netlist);
vtr::printf_info("Absorbing %zu LUT buffers\n", buffer_luts.size());
//Remove the buffer luts
for(auto blk : buffer_luts) {
remove_buffer_lut(netlist, blk);
}
//TODO: absorb inverter LUTs?
}
std::vector<AtomBlockId> identify_buffer_luts(const AtomNetlist& netlist) {
std::vector<AtomBlockId> buffer_luts;
for(auto blk : netlist.blocks()) {
if(is_buffer_lut(netlist, blk)) {
#ifdef SWEEP_VERBOSE
vtr::printf_warning(__FILE__, __LINE__, "%s is a lut buffer and will be absorbed\n", netlist.block_name(blk).c_str());
#endif
buffer_luts.push_back(blk);
}
}
return buffer_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;
}
void remove_buffer_lut(AtomNetlist& netlist, AtomBlockId blk) {
//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);
//Collect the new driver and sink pins
AtomPinId new_driver = netlist.net_driver(input_net);
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_sinks = std::any_of(new_sinks.begin(), new_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_sinks) {
//No PIs or POs, we can choose arbitarily in this case
new_net_name = netlist.net_name(output_net);
} else if(driver_is_pi && !po_in_sinks) {
//Must use the input name to perserve primary-input name
new_net_name = netlist.net_name(input_net);
} else if(!driver_is_pi && po_in_sinks) {
//Must use the output name to perserve primary-output name
new_net_name = netlist.net_name(output_net);
} else {
VTR_ASSERT(driver_is_pi && po_in_sinks);
//This is a buffered connection from a primary input, to primary output
//TODO: consider implications of removing these...
//Do not remove such buffers
return;
}
size_t initial_input_net_pins = netlist.net_pins(input_net).size();
//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
netlist.add_net(new_net_name, new_driver, new_sinks);
}
void fix_clock_to_data_conversions(AtomNetlist& netlist, const t_model* library_models) {
//Some netlists contain clock signals that are also used as data inputs
//
//This function removes these cases by creating a new net whose sinks are the
//'data' sinks of the clock net. This net is then driven by a latch with no
//input data signal, but clocked by the original clock
auto clock_data_pins = find_clock_used_as_data_pins(netlist);
//Use the latch D port to generate the data version fo the clock
const t_model* model = find_model(library_models, MODEL_LATCH);
VTR_ASSERT(model);
const t_model_ports* clock_port = find_model_port(model, "clk");
VTR_ASSERT(clock_port);
BitIndex clock_port_bit = 0;
const t_model_ports* data_port = find_model_port(model, "Q");
VTR_ASSERT(data_port);
BitIndex data_port_bit = 0;
for(auto kv : clock_data_pins) {
AtomNetId clock_net = kv.first;
const std::vector<AtomPinId>& data_pins = kv.second;
vtr::printf_warning(__FILE__, __LINE__, "Clock '%s' drives both clock and data pins, splitting it into separate clock and data nets\n",
netlist.net_name(clock_net).c_str());
fix_clock_to_data_pins(netlist,
clock_net, data_pins,
model,
clock_port, clock_port_bit,
data_port, data_port_bit);
}
}
//Finds all clock sinks which are data ports in the netlist.
//Returns a map of clock_net to the clock's data_sinks
std::map<AtomNetId,std::vector<AtomPinId>> find_clock_used_as_data_pins(const AtomNetlist& netlist) {
std::map<AtomNetId,std::vector<AtomPinId>> clock_data_pins;
auto netlist_clocks = find_netlist_clocks(netlist);
for(AtomNetId clock_net : netlist_clocks) {
for(AtomPinId clock_sink : netlist.net_sinks(clock_net)) {
auto port_type = netlist.pin_port_type(clock_sink);
if(port_type != PortType::CLOCK) {
clock_data_pins[clock_net].push_back(clock_sink);
}
}
}
return clock_data_pins;
}
AtomBlockId fix_clock_to_data_pins(AtomNetlist& netlist,
const AtomNetId clock_net, const std::vector<AtomPinId>& data_pins,
const t_model* blk_model,
const t_model_ports* model_clock_port, const BitIndex clock_port_bit,
const t_model_ports* model_data_port, const BitIndex data_port_bit) {
//Create a new block clocked by clock_net which will drive the 'data' version of the clock
std::string blk_name = "__vpr__" + netlist.net_name(clock_net) + "__as_data__";
VTR_ASSERT_MSG(!netlist.find_block(blk_name), "Failed to create unique clock as data source block name");
//Make the block
AtomBlockId blk = netlist.create_block(blk_name, blk_model);
VTR_ASSERT(blk);
//Connect the clock
AtomPortId clock_port = netlist.create_port(blk, model_clock_port);
netlist.create_pin(clock_port, clock_port_bit, clock_net, PinType::SINK);
//Make the data port
AtomPortId data_port = netlist.create_port(blk, model_data_port);
//Make the net
VTR_ASSERT_MSG(!netlist.find_net(blk_name), "Failed to create unique clock as data net name");
AtomNetId clock_data_net = netlist.create_net(blk_name);
VTR_ASSERT(clock_data_net);
//Create the driver pin
netlist.create_pin(data_port, data_port_bit, clock_data_net, PinType::DRIVER);
//Update all the data pins to connect to clock_data_net instead of the original clock net
for(AtomPinId sink_pin : data_pins) {
netlist.set_pin_net(sink_pin, PinType::SINK, clock_data_net);
}
return blk;
}
bool is_removable_block(const AtomNetlist& netlist, const AtomBlockId blk_id) {
//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;
}
}
return true;
}
bool is_removable_input(const AtomNetlist& netlist, const AtomBlockId blk_id) {
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);
}
bool is_removable_output(const AtomNetlist& netlist, const AtomBlockId blk_id) {
AtomBlockType type = netlist.block_type(blk_id);
//Only return true if an INPAD
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 output net
return false;
}
}
return true;
}
size_t sweep_constant_primary_outputs(AtomNetlist& netlist) {
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
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) {
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;
//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;
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;
if(should_sweep_ios) {
pass_dangling_inputs_swept += sweep_inputs(netlist);
pass_dangling_outputs_swept += sweep_outputs(netlist);
}
if(should_sweep_blocks) {
pass_dangling_blocks_swept += sweep_blocks(netlist);
}
if(should_sweep_nets) {
pass_dangling_nets_swept += sweep_nets(netlist);
}
if(should_sweep_constant_primary_outputs) {
pass_constant_outputs_swept += sweep_constant_primary_outputs(netlist);
}
dangling_nets_swept += pass_dangling_nets_swept;
dangling_blocks_swept += pass_dangling_blocks_swept;
dangling_inputs_swept += pass_dangling_outputs_swept;
dangling_outputs_swept += pass_dangling_outputs_swept;
constant_outputs_swept += pass_constant_outputs_swept;
} 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);
vtr::printf_info("Swept input(s) : %zu\n", dangling_inputs_swept);
vtr::printf_info("Swept output(s): %zu (%zu dangling, %zu constant)\n", dangling_outputs_swept + constant_outputs_swept,
dangling_outputs_swept,
constant_outputs_swept);
vtr::printf_info("Swept net(s) : %zu\n", dangling_nets_swept);
vtr::printf_info("Swept block(s) : %zu\n", dangling_blocks_swept);
return dangling_nets_swept
+ dangling_blocks_swept
+ dangling_inputs_swept
+ dangling_outputs_swept
+ constant_outputs_swept;
}
size_t sweep_blocks(AtomNetlist& netlist) {
//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
if(is_removable_block(netlist, blk_id)) {
blocks_to_remove.insert(blk_id);
}
}
//Remove them
for(auto blk_id : blocks_to_remove) {
#ifdef SWEEP_VERBOSE
vtr::printf_warning(__FILE__, __LINE__, "Sweeping block '%s'\n", netlist.block_name(blk_id).c_str());
#endif
netlist.remove_block(blk_id);
}
return blocks_to_remove.size();
}
size_t sweep_inputs(AtomNetlist& netlist) {
//Identify any inputs for removal
std::unordered_set<AtomBlockId> inputs_to_remove;
for(auto blk_id : netlist.blocks()) {
if(!blk_id) continue;
if(is_removable_input(netlist, blk_id)) {
inputs_to_remove.insert(blk_id);
}
}
//Remove them
for(auto blk_id : inputs_to_remove) {
#ifdef SWEEP_VERBOSE
vtr::printf_warning(__FILE__, __LINE__, "Sweeping primary input '%s'\n", netlist.block_name(blk_id).c_str());
#endif
netlist.remove_block(blk_id);
}
return inputs_to_remove.size();
}
size_t sweep_outputs(AtomNetlist& netlist) {
//Identify any outputs for removal
std::unordered_set<AtomBlockId> outputs_to_remove;
for(auto blk_id : netlist.blocks()) {
if(!blk_id) continue;
if(is_removable_output(netlist, blk_id)) {
#ifdef SWEEP_VERBOSE
vtr::printf_warning(__FILE__, __LINE__, "Sweeping primary output '%s'\n", netlist.block_name(blk_id).c_str());
#endif
outputs_to_remove.insert(blk_id);
}
}
//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) {
//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
#ifdef SWEEP_VERBOSE
vtr::printf_warning(__FILE__, __LINE__, "Net '%s' has no driver and will be removed\n", netlist.net_name(net_id).c_str());
#endif
nets_to_remove.insert(net_id);
}
if(netlist.net_sinks(net_id).size() == 0) {
//No sinks
#ifdef SWEEP_VERBOSE
vtr::printf_warning(__FILE__, __LINE__, "Net '%s' has no sinks and will be removed\n", netlist.net_name(net_id).c_str());
#endif
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_THROW(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 clock nets in the specified netlist
std::set<AtomNetId> find_netlist_clocks(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);
auto iter = clock_gen_ports.find(model);
if(iter == 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;
}