blob: 966794574a3059e68dffc2e483eb195f83153793 [file] [log] [blame]
/*
* BLIF Netlist Loader
* ===================
*
* This loader handles loading a post-technology mapping fully flattened (i.e not
* hierarchical) netlist in Berkely Logic Interchange Format (BLIF) file, and
* builds a netlist data structure (AtomNetlist) from it.
*
* BLIF text parsing is handled by the blifparse library, while this file is responsible
* for creating the netlist data structure.
*
* The main object of interest is the BlifAllocCallback struct, which implements the
* blifparse callback interface. The callback methods are then called when basic blif
* primitives are encountered by the parser. The callback methods then create the associated
* netlist data structures.
*
*/
#include <cstdio>
#include <cstring>
#include <ctime>
#include <sstream>
#include <unordered_set>
#include <cctype> //std::isdigit
#include "blifparse.hpp"
#include "atom_netlist.h"
#include "vtr_assert.h"
#include "vtr_util.h"
#include "vtr_log.h"
#include "vtr_logic.h"
#include "vtr_time.h"
#include "vtr_digest.h"
#include "vpr_types.h"
#include "vpr_error.h"
#include "globals.h"
#include "read_blif.h"
#include "arch_types.h"
#include "echo_files.h"
#include "hash.h"
vtr::LogicValue to_vtr_logic_value(blifparse::LogicValue);
struct BlifAllocCallback : public blifparse::Callback {
public:
BlifAllocCallback(e_circuit_format blif_format, AtomNetlist& main_netlist, const std::string netlist_id, const t_model* user_models, const t_model* library_models)
: main_netlist_(main_netlist)
, netlist_id_(netlist_id)
, user_arch_models_(user_models)
, library_arch_models_(library_models)
, blif_format_(blif_format) {
VTR_ASSERT(blif_format_ == e_circuit_format::BLIF
|| blif_format_ == e_circuit_format::EBLIF);
}
static constexpr const char* OUTPAD_NAME_PREFIX = "out:";
public: //Callback interface
void start_parse() override {}
void finish_parse() override {
//When parsing is finished we *move* the main netlist
//into the user object. This ensures we never have two copies
//(consuming twice the memory).
size_t main_netlist_idx = determine_main_netlist_index();
main_netlist_ = std::move(blif_models_[main_netlist_idx]);
}
void begin_model(std::string model_name) override {
//Create a new model, and set it's name
blif_models_.emplace_back(model_name, netlist_id_);
blif_models_black_box_.emplace_back(false);
ended_ = false;
set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
}
void inputs(std::vector<std::string> input_names) override {
const t_model* blk_model = find_model(MODEL_INPUT);
VTR_ASSERT_MSG(!blk_model->inputs, "Inpad model has an input port");
VTR_ASSERT_MSG(blk_model->outputs, "Inpad model has no output port");
VTR_ASSERT_MSG(blk_model->outputs->size == 1, "Inpad model has non-single-bit output port");
VTR_ASSERT_MSG(!blk_model->outputs->next, "Inpad model has multiple output ports");
std::string pin_name = blk_model->outputs->name;
for (const auto& input : input_names) {
AtomBlockId blk_id = curr_model().create_block(input, blk_model);
AtomPortId port_id = curr_model().create_port(blk_id, blk_model->outputs);
AtomNetId net_id = curr_model().create_net(input);
curr_model().create_pin(port_id, 0, net_id, PinType::DRIVER);
}
set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
}
void outputs(std::vector<std::string> output_names) override {
const t_model* blk_model = find_model(MODEL_OUTPUT);
VTR_ASSERT_MSG(!blk_model->outputs, "Outpad model has an output port");
VTR_ASSERT_MSG(blk_model->inputs, "Outpad model has no input port");
VTR_ASSERT_MSG(blk_model->inputs->size == 1, "Outpad model has non-single-bit input port");
VTR_ASSERT_MSG(!blk_model->inputs->next, "Outpad model has multiple input ports");
std::string pin_name = blk_model->inputs->name;
for (const auto& output : output_names) {
//Since we name blocks based on their drivers we need to uniquify outpad names,
//which we do with a prefix
AtomBlockId blk_id = curr_model().create_block(OUTPAD_NAME_PREFIX + output, blk_model);
AtomPortId port_id = curr_model().create_port(blk_id, blk_model->inputs);
AtomNetId net_id = curr_model().create_net(output);
curr_model().create_pin(port_id, 0, net_id, PinType::SINK);
}
set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
}
void names(std::vector<std::string> nets, std::vector<std::vector<blifparse::LogicValue>> so_cover) override {
const t_model* blk_model = find_model(MODEL_NAMES);
VTR_ASSERT_MSG(nets.size() > 0, "BLIF .names has no connections");
VTR_ASSERT_MSG(blk_model->inputs, ".names model has no input port");
VTR_ASSERT_MSG(!blk_model->inputs->next, ".names model has multiple input ports");
if (static_cast<int>(nets.size()) - 1 > blk_model->inputs->size) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "BLIF .names input size (%zu) greater than .names model input size (%d)",
nets.size() - 1, blk_model->inputs->size);
}
VTR_ASSERT_MSG(blk_model->outputs, ".names has no output port");
VTR_ASSERT_MSG(!blk_model->outputs->next, ".names model has multiple output ports");
VTR_ASSERT_MSG(blk_model->outputs->size == 1, ".names model has non-single-bit output");
//Convert the single-output cover to a netlist truth table
AtomNetlist::TruthTable truth_table;
for (const auto& row : so_cover) {
truth_table.emplace_back();
for (auto val : row) {
truth_table[truth_table.size() - 1].push_back(to_vtr_logic_value(val));
}
}
AtomBlockId blk_id = curr_model().create_block(nets[nets.size() - 1], blk_model, truth_table);
set_curr_block(blk_id);
//Create inputs
AtomPortId input_port_id = curr_model().create_port(blk_id, blk_model->inputs);
for (size_t i = 0; i < nets.size() - 1; ++i) {
AtomNetId net_id = curr_model().create_net(nets[i]);
curr_model().create_pin(input_port_id, i, net_id, PinType::SINK);
}
//Figure out if the output is a constant generator
bool output_is_const = false;
if (truth_table.empty()
|| (truth_table.size() == 1 && truth_table[0].size() == 1 && truth_table[0][0] == vtr::LogicValue::FALSE)) {
//An empty truth table in BLIF corresponds to a constant-zero
// e.g.
//
// #gnd is a constant 0 generator
// .names gnd
//
//An single entry truth table with value '0' also corresponds to a constant-zero
// e.g.
//
// #gnd2 is a constant 0 generator
// .names gnd2
// 0
//
output_is_const = true;
VTR_LOG("Found constant-zero generator '%s'\n", nets[nets.size() - 1].c_str());
} else if (truth_table.size() == 1 && truth_table[0].size() == 1 && truth_table[0][0] == vtr::LogicValue::TRUE) {
//A single-entry truth table with value '1' in BLIF corresponds to a constant-one
// e.g.
//
// #vcc is a constant 1 generator
// .names vcc
// 1
//
output_is_const = true;
VTR_LOG("Found constant-one generator '%s'\n", nets[nets.size() - 1].c_str());
}
//Create output
AtomNetId net_id = curr_model().create_net(nets[nets.size() - 1]);
AtomPortId output_port_id = curr_model().create_port(blk_id, blk_model->outputs);
curr_model().create_pin(output_port_id, 0, net_id, PinType::DRIVER, output_is_const);
}
void latch(std::string input, std::string output, blifparse::LatchType type, std::string control, blifparse::LogicValue init) override {
if (type == blifparse::LatchType::UNSPECIFIED) {
VTR_LOGF_WARN(filename_.c_str(), lineno_, "Treating latch '%s' of unspecified type as rising edge triggered\n", output.c_str());
} else if (type != blifparse::LatchType::RISING_EDGE) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Only rising edge latches supported\n");
}
if (control.empty()) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Latch must have a clock\n");
}
const t_model* blk_model = find_model(MODEL_LATCH);
VTR_ASSERT_MSG(blk_model->inputs, "Has one input port");
VTR_ASSERT_MSG(blk_model->inputs->next, "Has two input port");
VTR_ASSERT_MSG(!blk_model->inputs->next->next, "Has no more than two input port");
VTR_ASSERT_MSG(blk_model->outputs, "Has one output port");
VTR_ASSERT_MSG(!blk_model->outputs->next, "Has no more than one input port");
const t_model_ports* d_model_port = blk_model->inputs;
const t_model_ports* clk_model_port = blk_model->inputs->next;
const t_model_ports* q_model_port = blk_model->outputs;
VTR_ASSERT(d_model_port->name == std::string("D"));
VTR_ASSERT(clk_model_port->name == std::string("clk"));
VTR_ASSERT(q_model_port->name == std::string("Q"));
VTR_ASSERT(d_model_port->size == 1);
VTR_ASSERT(clk_model_port->size == 1);
VTR_ASSERT(q_model_port->size == 1);
VTR_ASSERT(clk_model_port->is_clock);
//We set the initital value as a single entry in the 'truth_table' field
AtomNetlist::TruthTable truth_table(1);
truth_table[0].push_back(to_vtr_logic_value(init));
AtomBlockId blk_id = curr_model().create_block(output, blk_model, truth_table);
set_curr_block(blk_id);
//The input
AtomPortId d_port_id = curr_model().create_port(blk_id, d_model_port);
AtomNetId d_net_id = curr_model().create_net(input);
curr_model().create_pin(d_port_id, 0, d_net_id, PinType::SINK);
//The output
AtomPortId q_port_id = curr_model().create_port(blk_id, q_model_port);
AtomNetId q_net_id = curr_model().create_net(output);
curr_model().create_pin(q_port_id, 0, q_net_id, PinType::DRIVER);
//The clock
AtomPortId clk_port_id = curr_model().create_port(blk_id, clk_model_port);
AtomNetId clk_net_id = curr_model().create_net(control);
curr_model().create_pin(clk_port_id, 0, clk_net_id, PinType::SINK);
}
void subckt(std::string subckt_model, std::vector<std::string> ports, std::vector<std::string> nets) override {
VTR_ASSERT(ports.size() == nets.size());
const t_model* blk_model = find_model(subckt_model);
//We name the subckt based on the net it's first output pin drives
std::string subckt_name;
for (size_t i = 0; i < ports.size(); ++i) {
const t_model_ports* model_port = find_model_port(blk_model, ports[i]);
VTR_ASSERT(model_port);
if (model_port->dir == OUT_PORT) {
subckt_name = nets[i];
break;
}
}
if (subckt_name.empty()) {
//We need to name the block uniquely ourselves since there is no connected output
subckt_name = unique_subckt_name();
//Since this is unusual, warn the user
VTR_LOGF_WARN(filename_.c_str(), lineno_,
"Subckt of type '%s' at %s:%d has no output pins, and has been named '%s'\n",
blk_model->name, filename_.c_str(), lineno_, subckt_name.c_str());
}
//The name for every block should be unique, check that there is no name conflict
AtomBlockId blk_id = curr_model().find_block(subckt_name);
if (blk_id) {
const t_model* conflicting_model = curr_model().block_model(blk_id);
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
"Duplicate blocks named '%s' found in netlist."
" Existing block of type '%s' conflicts with subckt of type '%s'.",
subckt_name.c_str(), conflicting_model->name, subckt_model.c_str());
}
//Create the block
blk_id = curr_model().create_block(subckt_name, blk_model);
set_curr_block(blk_id);
for (size_t i = 0; i < ports.size(); ++i) {
//Check for consistency between model and ports
const t_model_ports* model_port = find_model_port(blk_model, ports[i]);
VTR_ASSERT(model_port);
//Determine the pin type
PinType pin_type = PinType::SINK;
if (model_port->dir == OUT_PORT) {
pin_type = PinType::DRIVER;
} else {
VTR_ASSERT_MSG(model_port->dir == IN_PORT, "Unexpected port type");
}
//Make the port
std::string port_base;
size_t port_bit;
std::tie(port_base, port_bit) = split_index(ports[i]);
AtomPortId port_id = curr_model().create_port(blk_id, find_model_port(blk_model, port_base));
//Make the net
AtomNetId net_id = curr_model().create_net(nets[i]);
//Make the pin
curr_model().create_pin(port_id, port_bit, net_id, pin_type);
}
}
void blackbox() override {
//We treat black-boxes as netlists during parsing so they should contain
//only inpads/outpads
for (const auto& blk_id : curr_model().blocks()) {
auto blk_type = curr_model().block_type(blk_id);
if (!(blk_type == AtomBlockType::INPAD || blk_type == AtomBlockType::OUTPAD)) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Unexpected primitives in blackbox model");
}
}
set_curr_model_blackbox(true);
set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
}
void end_model() override {
if (ended_) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Unexpected .end");
}
merge_conn_nets();
//Mark as ended
ended_ = true;
set_curr_block(AtomBlockId::INVALID()); //This statement doesn't define a block, so mark invalid
}
//BLIF Extensions
void conn(std::string src, std::string dst) override {
if (blif_format_ != e_circuit_format::EBLIF) {
parse_error(lineno_, ".conn", "Supported only in extended BLIF format");
}
//We allow the .conn to create the nets if they don't exist,
//however typically they will have already been defined.
AtomNetId driver_net = curr_model().create_net(src);
AtomNetId sink_net = curr_model().create_net(dst);
//We eventually need to merge the driver and sink nets,
//however we must defer that until all the net drivers
//and sinks have been created (otherwise they may not
//be properly merged depending on where the .conn is
//delcared).
//
//As a result we record the nets to merge and do the actual merging at
//the end of the .model
curr_nets_to_merge_.emplace_back(driver_net, sink_net);
set_curr_block(AtomBlockId::INVALID());
}
void cname(std::string cell_name) override {
if (blif_format_ != e_circuit_format::EBLIF) {
parse_error(lineno_, ".cname", "Supported only in extended BLIF format");
}
//Re-name the block
curr_model().set_block_name(curr_block(), cell_name);
}
void attr(std::string name, std::string value) override {
if (blif_format_ != e_circuit_format::EBLIF) {
parse_error(lineno_, ".attr", "Supported only in extended BLIF format");
}
curr_model().set_block_attr(curr_block(), name, value);
}
void param(std::string name, std::string value) override {
if (blif_format_ != e_circuit_format::EBLIF) {
parse_error(lineno_, ".param", "Supported only in extended BLIF format");
}
curr_model().set_block_param(curr_block(), name, value);
}
//Utilities
void filename(std::string fname) override { filename_ = fname; }
void lineno(int line_num) override { lineno_ = line_num; }
void parse_error(const int curr_lineno, const std::string& near_text, const std::string& msg) override {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), curr_lineno,
"Error in blif file near '%s': %s\n", near_text.c_str(), msg.c_str());
}
public:
//Retrieve the netlist
size_t determine_main_netlist_index() {
//Look through all the models loaded, to find the one which is non-blackbox (i.e. has real blocks
//and is not a blackbox). To check for errors we look at all models, even if we've already
//found a non-blackbox model.
int top_model_idx = -1; //Not valid
for (int i = 0; i < static_cast<int>(blif_models_.size()); ++i) {
if (!blif_models_black_box_[i]) {
//A non-blackbox model
if (top_model_idx == -1) {
//This is the top model
top_model_idx = i;
} else {
//We already have a top model
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
"Found multiple models with primitives. "
"Only one model can contain primitives, the others must be blackboxes.");
}
} else {
//Verify blackbox models match the architecture
verify_blackbox_model(blif_models_[i]);
}
}
if (top_model_idx == -1) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
"No non-blackbox models found. The main model must not be a blackbox.");
}
//Return the main model
VTR_ASSERT(top_model_idx >= 0);
return static_cast<size_t>(top_model_idx);
}
private:
const t_model* find_model(std::string name) {
const t_model* arch_model = nullptr;
for (const t_model* arch_models : {user_arch_models_, library_arch_models_}) {
arch_model = arch_models;
while (arch_model) {
if (name == arch_model->name) {
//Found it
break;
}
arch_model = arch_model->next;
}
if (arch_model) {
//Found it
break;
}
}
if (!arch_model) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Failed to find matching architecture model for '%s'\n",
name.c_str());
}
return arch_model;
}
const t_model_ports* find_model_port(const t_model* blk_model, std::string port_name) {
//We need to handle both single, and multi-bit port names
//
//By convention multi-bit port names have the bit index stored in square brackets
//at the end of the name. For example:
//
// my_signal_name[2]
//
//indicates the 2nd bit of the port 'my_signal_name'.
std::string trimmed_port_name;
int bit_index;
//Extract the index bit
std::tie(trimmed_port_name, bit_index) = split_index(port_name);
//We now have the valid bit index and port_name is the name excluding the index info
VTR_ASSERT(bit_index >= 0);
//We now look through all the ports on the model looking for the matching port
for (const t_model_ports* ports : {blk_model->inputs, blk_model->outputs}) {
const t_model_ports* curr_port = ports;
while (curr_port) {
if (trimmed_port_name == curr_port->name) {
//Found a matching port, we need to verify the index
if (bit_index < curr_port->size) {
//Valid port index
return curr_port;
} else {
//Out of range
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
"Port '%s' on architecture model '%s' exceeds port width (%d bits)\n",
port_name.c_str(), blk_model->name, curr_port->size);
}
}
curr_port = curr_port->next;
}
}
//No match
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_,
"Found no matching port '%s' on architecture model '%s'\n",
port_name.c_str(), blk_model->name);
return nullptr;
}
//Splits the index off a signal name and returns the base signal name (excluding
//the index) and the index as an integer. For example
//
// "my_signal_name[2]" -> "my_signal_name", 2
std::pair<std::string, int> split_index(const std::string& signal_name) {
int bit_index = 0;
std::string trimmed_signal_name = signal_name;
auto iter = --signal_name.end(); //Initialized to the last char
if (*iter == ']') {
//The name may end in an index
//
//To verify, we iterate back through the string until we find
//an open square brackets, or non digit character
--iter; //Advance past ']'
while (iter != signal_name.begin() && std::isdigit(*iter))
--iter;
//We are at the first non-digit character from the end (or the beginning of the string)
if (*iter == '[') {
//We have a valid index in the open range (iter, --signal_name.end())
std::string index_str(iter + 1, --signal_name.end());
//Convert to an integer
std::stringstream ss(index_str);
ss >> bit_index;
VTR_ASSERT_MSG(!ss.fail() && ss.eof(), "Failed to extract signal index");
//Trim the signal name to exclude the final index
trimmed_signal_name = std::string(signal_name.begin(), iter);
}
}
return std::make_pair(trimmed_signal_name, bit_index);
}
//Retieves a reference to the currently active .model
AtomNetlist& curr_model() {
if (blif_models_.empty() || ended_) {
vpr_throw(VPR_ERROR_BLIF_F, filename_.c_str(), lineno_, "Expected .model");
}
return blif_models_[blif_models_.size() - 1];
}
void set_curr_model_blackbox(bool val) {
VTR_ASSERT(blif_models_.size() == blif_models_black_box_.size());
blif_models_black_box_[blif_models_black_box_.size() - 1] = val;
}
bool verify_blackbox_model(AtomNetlist& blif_model) {
const t_model* arch_model = find_model(blif_model.netlist_name());
//Verify each port on the model
//
// We parse each .model as it's own netlist so the IOs
// get converted to blocks
for (auto blk_id : blif_model.blocks()) {
//Check that the port directions match
if (blif_model.block_type(blk_id) == AtomBlockType::INPAD) {
const auto& input_name = blif_model.block_name(blk_id);
//Find model port already verifies the port widths
const t_model_ports* arch_model_port = find_model_port(arch_model, input_name);
VTR_ASSERT(arch_model_port);
VTR_ASSERT(arch_model_port->dir == IN_PORT);
} else {
VTR_ASSERT(blif_model.block_type(blk_id) == AtomBlockType::OUTPAD);
auto raw_output_name = blif_model.block_name(blk_id);
std::string output_name = vtr::replace_first(raw_output_name, OUTPAD_NAME_PREFIX, "");
//Find model port already verifies the port widths
const t_model_ports* arch_model_port = find_model_port(arch_model, output_name);
VTR_ASSERT(arch_model_port);
VTR_ASSERT(arch_model_port->dir == OUT_PORT);
}
}
return true;
}
//Returns a different unique subck name each time it is called
std::string unique_subckt_name() {
return "unnamed_subckt" + std::to_string(unique_subckt_name_counter_++);
}
//Sets the current block which is being processed
// Used to determine which block a .cname, .param, .attr apply to
void set_curr_block(AtomBlockId blk) {
curr_block_ = blk;
}
//Gets the current block which is being processed
AtomBlockId curr_block() const {
return curr_block_;
}
//Merges all the recorded net pairs which need to be merged
//
//This should only be called at the end of a .model to ensure that
//all the associated driver/sink pins have been delcared and connected
//to their nets
void merge_conn_nets() {
for (auto net_pair : curr_nets_to_merge_) {
curr_model().merge_nets(net_pair.first, net_pair.second);
}
curr_nets_to_merge_.clear();
}
private:
bool ended_ = true; //Initially no active .model
std::string filename_ = "";
int lineno_ = -1;
std::vector<AtomNetlist> blif_models_;
std::vector<bool> blif_models_black_box_;
AtomNetlist& main_netlist_; //User object we fill
const std::string netlist_id_; //Unique identifier based on the contents of the blif file
const t_model* user_arch_models_ = nullptr;
const t_model* library_arch_models_ = nullptr;
size_t unique_subckt_name_counter_ = 0;
AtomBlockId curr_block_;
std::vector<std::pair<AtomNetId, AtomNetId>> curr_nets_to_merge_;
e_circuit_format blif_format_ = e_circuit_format::BLIF;
};
vtr::LogicValue to_vtr_logic_value(blifparse::LogicValue val) {
vtr::LogicValue new_val = vtr::LogicValue::UNKOWN;
switch (val) {
case blifparse::LogicValue::TRUE:
new_val = vtr::LogicValue::TRUE;
break;
case blifparse::LogicValue::FALSE:
new_val = vtr::LogicValue::FALSE;
break;
case blifparse::LogicValue::DONT_CARE:
new_val = vtr::LogicValue::DONT_CARE;
break;
case blifparse::LogicValue::UNKOWN:
new_val = vtr::LogicValue::UNKOWN;
break;
default:
VTR_ASSERT_OPT_MSG(false, "Unkown logic value");
}
return new_val;
}
AtomNetlist read_blif(e_circuit_format circuit_format,
const char* blif_file,
const t_model* user_models,
const t_model* library_models) {
AtomNetlist netlist;
std::string netlist_id = vtr::secure_digest_file(blif_file);
BlifAllocCallback alloc_callback(circuit_format, netlist, netlist_id, user_models, library_models);
blifparse::blif_parse_filename(blif_file, alloc_callback);
return netlist;
}