#include "atom_netlist_utils.h" #include #include #include #include #include #include #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 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 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 cube, std::vector& 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 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 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> 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 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 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 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 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 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 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 find_combinationally_connected_input_ports(const AtomNetlist& netlist, AtomPortId output_port) { std::vector 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 find_combinationally_connected_clock_ports(const AtomNetlist& netlist, AtomPortId output_port) { std::vector 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 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 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 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 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 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& permutation) { AtomNetlist::TruthTable permuted_truth_table; for (const auto& row : truth_table) { //Space for the permuted row: num inputs + one output std::vector 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 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 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 mask; if (on_set) { //If we are encoding the on-set the background value is false mask = std::vector(mask_bits, vtr::LogicValue::FALSE); } else { //If we are encoding the off-set the background value is true mask = std::vector(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 cube(row.begin(), --row.end()); VTR_ASSERT(cube.size() == num_inputs); std::vector 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 cube_to_minterms(std::vector cube) { std::vector minterms; cube_to_minterms_recurr(cube, minterms); return minterms; } void cube_to_minterms_recurr(std::vector cube, std::vector& 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 cube_true = cube; cube_true[i] = vtr::LogicValue::TRUE; cube_to_minterms_recurr(cube_true, minterms); //Recurse //False case std::vector 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 find_netlist_physical_clock_nets(const AtomNetlist& netlist) { std::set clock_nets; //The clock nets std::map> 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 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 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 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 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 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 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); }