| #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); |
| |
| 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, 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); |
| void 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 |
| |
| //Find buffer luts |
| auto buffer_luts = identify_buffer_luts(netlist); |
| |
| VTR_LOGV(verbosity > 0, "Absorbing %zu LUT buffers\n", buffer_luts.size()); |
| |
| //Remove the buffer luts |
| for (auto blk : buffer_luts) { |
| remove_buffer_lut(netlist, blk, verbosity); |
| } |
| |
| //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)) { |
| 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, 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); |
| |
| //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(); |
| |
| VTR_LOGV_WARN(verbosity > 1, "%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 |
| netlist.add_net(new_net_name, new_driver, new_sinks); |
| } |
| |
| 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); |
| } |