547 lines
20 KiB
C++
547 lines
20 KiB
C++
/********************************************************************
|
|
* This file includes most utilized functions to manipulate LUTs,
|
|
* especially their truth tables, in the OpenFPGA context
|
|
*******************************************************************/
|
|
#include <cmath>
|
|
|
|
/* Headers from vtrutil library */
|
|
#include "vtr_assert.h"
|
|
#include "vtr_log.h"
|
|
|
|
/* Headers from openfpgautil library */
|
|
#include "lut_utils.h"
|
|
#include "openfpga_decode.h"
|
|
|
|
/* begin namespace openfpga */
|
|
namespace openfpga {
|
|
|
|
/********************************************************************
|
|
* This function aims to adapt the truth table to a mapped physical LUT
|
|
* subject to a pin rotation map
|
|
* The modification is applied to line by line
|
|
* - For unused inputs : insert dont care
|
|
* - For used inputs : find the bit in the truth table rows and move it by the
|
|
*given mapping
|
|
*
|
|
* The rotated pin map is the reference to adapt the truth table.
|
|
* Each element of the map represents the input index in the original truth
|
|
*table The sequence of the rotate pin map is the final sequence of how each
|
|
*line of the original truth table should be shuffled Example: output_value(we
|
|
*do not modify)
|
|
* |
|
|
* v
|
|
* Truth table line: 00111
|
|
* rotated_pin_map: 2310
|
|
* Adapt truth table line: 11001
|
|
*
|
|
* An illustrative example:
|
|
*
|
|
* Original Truth Table Post VPR Truth Table
|
|
*
|
|
* +-------+ +-------+
|
|
* net0 --->| | net1--->| |
|
|
* net1 --->| LUT | net0--->| LUT |
|
|
* ... | | ... | |
|
|
* +-------+ +-------+
|
|
*
|
|
* Truth table line Truth table line
|
|
* .names net0 net1 out .names net1 net0 out
|
|
* 01 1 10 1
|
|
*
|
|
*******************************************************************/
|
|
AtomNetlist::TruthTable lut_truth_table_adaption(
|
|
const AtomNetlist::TruthTable& orig_tt,
|
|
const std::vector<int>& rotated_pin_map) {
|
|
AtomNetlist::TruthTable tt;
|
|
|
|
for (auto row : orig_tt) {
|
|
VTR_ASSERT(row.size() - 1 <= rotated_pin_map.size());
|
|
|
|
std::vector<vtr::LogicValue> tt_line;
|
|
/* We do not care about the last digit, which is the output value */
|
|
for (size_t i = 0; i < rotated_pin_map.size(); ++i) {
|
|
if (-1 == rotated_pin_map[i]) {
|
|
tt_line.push_back(vtr::LogicValue::DONT_CARE);
|
|
} else {
|
|
/* Ensure we never access the last digit, i.e., the output value! */
|
|
VTR_ASSERT((size_t)rotated_pin_map[i] < row.size() - 1);
|
|
tt_line.push_back(row[rotated_pin_map[i]]);
|
|
}
|
|
}
|
|
|
|
/* Do not miss the last digit in the final result */
|
|
tt_line.push_back(row.back());
|
|
tt.push_back(tt_line);
|
|
}
|
|
|
|
return tt;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Convert a truth table to strings, which are ready to be printed out
|
|
*******************************************************************/
|
|
std::vector<std::string> truth_table_to_string(
|
|
const AtomNetlist::TruthTable& tt) {
|
|
std::vector<std::string> tt_str;
|
|
for (auto row : tt) {
|
|
std::string row_str;
|
|
for (size_t i = 0; i < row.size(); ++i) {
|
|
/* Add a gap between inputs and outputs */
|
|
if (i == row.size() - 1) {
|
|
row_str += std::string(" ");
|
|
}
|
|
switch (row[i]) {
|
|
case vtr::LogicValue::TRUE:
|
|
row_str += std::string("1");
|
|
break;
|
|
case vtr::LogicValue::FALSE:
|
|
row_str += std::string("0");
|
|
break;
|
|
case vtr::LogicValue::DONT_CARE:
|
|
row_str += std::string("-");
|
|
break;
|
|
default:
|
|
VTR_ASSERT_MSG(false, "Valid single-output cover logic value");
|
|
}
|
|
}
|
|
tt_str.push_back(row_str);
|
|
}
|
|
|
|
return tt_str;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Adapt the truth table from the short-wire connection
|
|
* from the input nets of a LUT to an output of a LUT
|
|
*
|
|
* LUT
|
|
* +-------------+
|
|
* lut_input--->|----+ |
|
|
* | | |
|
|
* | +------->|---> lut_output
|
|
* | |
|
|
* +-------------+
|
|
*
|
|
* In this case, LUT is configured as a wiring module
|
|
* This function will generate a truth for the wiring LUT
|
|
*
|
|
* For example:
|
|
* The truth table of the case where the 3rd input of
|
|
* a 4-input LUT is wired to output
|
|
*
|
|
* --1- 1
|
|
*
|
|
********************************************************************/
|
|
AtomNetlist::TruthTable build_wired_lut_truth_table(
|
|
const size_t& lut_size, const size_t& wire_input_id) {
|
|
AtomNetlist::TruthTable tt;
|
|
|
|
/* There is always only one line in this truth table */
|
|
tt.resize(1);
|
|
|
|
/* Pre-allocate the truth table:
|
|
* Each truth table line is organized in BLIF format:
|
|
* |<---LUT size--->|
|
|
* < a string of 0 or 1> <0 or 1>
|
|
* The first <lut_size> of characters represent the input values of each LUT
|
|
* input Here, we add 2 characters, which denote the space and a digit (0|1)
|
|
* By default, we set all the inputs as don't care value '-'
|
|
*
|
|
* For more details, please refer to the BLIF format documentation
|
|
*/
|
|
tt[0].resize(lut_size, vtr::LogicValue::DONT_CARE);
|
|
/* Fill the truth table !!! */
|
|
VTR_ASSERT(wire_input_id < lut_size);
|
|
tt[0][wire_input_id] = vtr::LogicValue::TRUE;
|
|
tt[0].push_back(vtr::LogicValue::TRUE);
|
|
|
|
return tt;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Adapt truth table for a fracturable LUT
|
|
* Determine fixed input bits for this truth table:
|
|
* 1. input bits within frac_level (all '-' if not specified)
|
|
* 2. input bits outside frac_level, decoded to its output mask (0 -> first part
|
|
*-> all '1')
|
|
*
|
|
* For example:
|
|
* A 4-input function is mapped to input[0..3] of a 6-input fracturable LUT
|
|
* Plus, it uses the 2nd output of the fracturable LUT
|
|
* The truth table of the 4-input function is
|
|
* 1001 1
|
|
* while truth table of a 6-input LUT requires 6 characters
|
|
* Therefore, it must be adapted by adding mask bits, which are
|
|
* a number of fixed digits to configure the fracturable LUT to
|
|
* operate in a 4-input LUT mode
|
|
* The mask bits can be decoded from the index of output used in the
|
|
*fracturable LUT For the 2nd output, it will be '01', the binary representation
|
|
*of index '1' Now the truth table will be adapt to 100101 1 where the first 4
|
|
*digits come from the original truth table the 2 following digits are mask bits
|
|
*
|
|
********************************************************************/
|
|
AtomNetlist::TruthTable adapt_truth_table_for_frac_lut(
|
|
const size_t& lut_frac_level, const size_t& lut_output_mask,
|
|
const AtomNetlist::TruthTable& truth_table) {
|
|
/* No adaption required for when the lut_frac_level is not set */
|
|
if (size_t(OPEN) == lut_frac_level) {
|
|
return truth_table;
|
|
}
|
|
|
|
AtomNetlist::TruthTable adapt_truth_table;
|
|
|
|
/* Apply modification to the truth table */
|
|
for (const std::vector<vtr::LogicValue>& tt_line : truth_table) {
|
|
/* Last element is the output */
|
|
size_t lut_size = tt_line.size() - 1;
|
|
/* Get the number of bits to be masked (modified) */
|
|
int num_mask_bits = lut_size - lut_frac_level;
|
|
/* Check if we need to modify any bits */
|
|
VTR_ASSERT(0 <= num_mask_bits);
|
|
if (0 == num_mask_bits) {
|
|
/* No modification needed, push to adapted truth table */
|
|
adapt_truth_table.push_back(tt_line);
|
|
continue;
|
|
}
|
|
/* Modify bits starting from lut_frac_level */
|
|
/* Decode the lut_output_mask to LUT input codes */
|
|
int temp = pow(2., num_mask_bits) - 1 - lut_output_mask;
|
|
VTR_ASSERT(0 <= temp);
|
|
std::vector<size_t> mask_bits_vec = itobin_vec(temp, num_mask_bits);
|
|
/* Copy the bits to the truth table line */
|
|
std::vector<vtr::LogicValue> adapt_tt_line = tt_line;
|
|
for (size_t itt = lut_frac_level;
|
|
itt < lut_frac_level + mask_bits_vec.size(); ++itt) {
|
|
vtr::LogicValue logic_val = vtr::LogicValue::FALSE;
|
|
VTR_ASSERT((1 == mask_bits_vec[itt - lut_frac_level]) ||
|
|
(0 == mask_bits_vec[itt - lut_frac_level]));
|
|
if (1 == mask_bits_vec[itt - lut_frac_level]) {
|
|
logic_val = vtr::LogicValue::TRUE;
|
|
}
|
|
adapt_tt_line[itt] = logic_val;
|
|
}
|
|
|
|
/* Push to adapted truth table */
|
|
adapt_truth_table.push_back(adapt_tt_line);
|
|
}
|
|
|
|
return adapt_truth_table;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Determine if the truth table of a LUT is a on-set or a off-set
|
|
* - An on-set is defined as the truth table lines are ended with logic '1'
|
|
* - An off-set is defined as the truth table lines are ended with logic '0'
|
|
*******************************************************************/
|
|
bool lut_truth_table_use_on_set(const AtomNetlist::TruthTable& truth_table) {
|
|
bool on_set = false;
|
|
bool off_set = false;
|
|
|
|
for (const std::vector<vtr::LogicValue>& tt_line : truth_table) {
|
|
switch (tt_line.back()) {
|
|
case vtr::LogicValue::TRUE:
|
|
on_set = true;
|
|
break;
|
|
case vtr::LogicValue::FALSE:
|
|
off_set = true;
|
|
break;
|
|
default:
|
|
VTR_LOGF_ERROR(__FILE__, __LINE__,
|
|
"Invalid truth_table_line ending '%s'!\n",
|
|
vtr::LOGIC_VALUE_STRING[size_t(tt_line.back())]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* Prefer on_set if both are true */
|
|
if (true == on_set && true == off_set) {
|
|
on_set = true;
|
|
off_set = false;
|
|
}
|
|
VTR_ASSERT(on_set == !off_set);
|
|
|
|
return on_set;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Complete a line in truth table with a given lut size
|
|
* Due to the size of truth table may be less than the lut size.
|
|
* i.e. in LUT-6 architecture, there exists LUT1-6 in technology-mapped netlists
|
|
* So, in truth table line, there may be 10- 1
|
|
* In this case, we should complete it by --10- 1
|
|
*******************************************************************/
|
|
static std::vector<vtr::LogicValue> complete_truth_table_line(
|
|
const size_t& lut_size, const std::vector<vtr::LogicValue>& tt_line) {
|
|
std::vector<vtr::LogicValue> ret;
|
|
|
|
VTR_ASSERT(0 < tt_line.size());
|
|
|
|
/* Complete the truth table line*/
|
|
size_t cover_len = tt_line.size() - 1;
|
|
VTR_ASSERT(cover_len <= lut_size);
|
|
|
|
/* Copy the original truth table line */
|
|
ret = tt_line;
|
|
/* Kick out the last value for now as it is the output value */
|
|
ret.pop_back();
|
|
|
|
/* Add the number of '-' we should add in the back !!! */
|
|
for (size_t j = cover_len; j < lut_size; ++j) {
|
|
ret.push_back(vtr::LogicValue::DONT_CARE);
|
|
}
|
|
|
|
/* Copy the original truth table line */
|
|
ret.push_back(tt_line.back());
|
|
|
|
/* Check if the size of ret matches our expectation */
|
|
VTR_ASSERT(lut_size + 1 == ret.size());
|
|
|
|
return ret;
|
|
}
|
|
|
|
/********************************************************************
|
|
* For each lut_bit_lines, we should recover the truth table,
|
|
* and then set the sram bits to "1" if the truth table defines so.
|
|
* Start_point: the position we start converting don't care sign '-'
|
|
* to explicit '0' or '1'
|
|
*******************************************************************/
|
|
static void rec_build_lut_bitstream_per_line(
|
|
std::vector<bool>& lut_bitstream, const size_t& lut_size,
|
|
const std::vector<vtr::LogicValue>& tt_line, const size_t& start_point) {
|
|
std::vector<vtr::LogicValue> temp_line = tt_line;
|
|
|
|
/* Check the length of sram bits and truth table line */
|
|
VTR_ASSERT(lut_size + 1 == tt_line.size()); /* lut_size + '1|0' */
|
|
|
|
/* End of truth_table_line should be "space" and "1" */
|
|
VTR_ASSERT((vtr::LogicValue::TRUE == tt_line.back()) ||
|
|
(vtr::LogicValue::FALSE == tt_line.back()));
|
|
|
|
/* Make sure before start point there is no '-' */
|
|
VTR_ASSERT(start_point < tt_line.size());
|
|
for (size_t i = 0; i < start_point; ++i) {
|
|
VTR_ASSERT(vtr::LogicValue::DONT_CARE != tt_line[i]);
|
|
}
|
|
|
|
/* Configure sram bits recursively */
|
|
for (size_t i = start_point; i < lut_size; ++i) {
|
|
if (vtr::LogicValue::DONT_CARE == tt_line[i]) {
|
|
/* if we find a dont_care, we don't do configure now but recursively*/
|
|
/* '0' branch */
|
|
temp_line[i] = vtr::LogicValue::FALSE;
|
|
rec_build_lut_bitstream_per_line(lut_bitstream, lut_size, temp_line,
|
|
start_point + 1);
|
|
/* '1' branch */
|
|
temp_line[i] = vtr::LogicValue::TRUE;
|
|
rec_build_lut_bitstream_per_line(lut_bitstream, lut_size, temp_line,
|
|
start_point + 1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* TODO: Use MuxGraph to decode this!!! */
|
|
/* Decode bitstream only when there are only 0 or 1 in the truth table */
|
|
size_t sram_id = 0;
|
|
for (size_t i = 0; i < lut_size; ++i) {
|
|
/* Should be either '0' or '1' */
|
|
switch (tt_line[i]) {
|
|
case vtr::LogicValue::FALSE:
|
|
/* We assume the 1-lut pass sram1 when input = 0 */
|
|
sram_id += (size_t)pow(2., (double)(i));
|
|
break;
|
|
case vtr::LogicValue::TRUE:
|
|
/* We assume the 1-lut pass sram0 when input = 1 */
|
|
break;
|
|
case vtr::LogicValue::DONT_CARE:
|
|
default:
|
|
VTR_LOGF_ERROR(__FILE__, __LINE__,
|
|
"Invalid truth_table bit '%s', should be [0|1|]!\n",
|
|
vtr::LOGIC_VALUE_STRING[size_t(tt_line[i])]);
|
|
exit(1);
|
|
}
|
|
}
|
|
/* Set the sram bit to '1'*/
|
|
VTR_ASSERT(sram_id < lut_bitstream.size());
|
|
if (vtr::LogicValue::TRUE == tt_line.back()) {
|
|
lut_bitstream[sram_id] = true; /* on set*/
|
|
} else if (vtr::LogicValue::FALSE == tt_line.back()) {
|
|
lut_bitstream[sram_id] = false; /* off set */
|
|
} else {
|
|
VTR_LOGF_ERROR(__FILE__, __LINE__,
|
|
"Invalid truth_table_line ending '%s'!\n",
|
|
vtr::LOGIC_VALUE_STRING[size_t(tt_line.back())]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/********************************************************************
|
|
* Generate the bitstream for a single-output LUT with a given truth table
|
|
* As truth tables may come from different logic blocks, truth tables could be
|
|
*in on and off sets We first build a base SRAM bits, where different parts are
|
|
*set to tbe on/off sets Then, we can decode SRAM bits as regular process
|
|
*******************************************************************/
|
|
static std::vector<bool> build_single_output_lut_bitstream(
|
|
const AtomNetlist::TruthTable& truth_table, const MuxGraph& lut_mux_graph,
|
|
const size_t& default_sram_bit_value) {
|
|
size_t lut_size = lut_mux_graph.num_memory_bits();
|
|
size_t bitstream_size = lut_mux_graph.num_inputs();
|
|
std::vector<bool> lut_bitstream(bitstream_size, false);
|
|
AtomNetlist::TruthTable completed_truth_table;
|
|
bool on_set = false;
|
|
bool off_set = false;
|
|
|
|
/* if No truth_table, do default*/
|
|
if (0 == truth_table.size()) {
|
|
switch (default_sram_bit_value) {
|
|
case 0:
|
|
on_set = true;
|
|
off_set = false;
|
|
break;
|
|
case 1:
|
|
on_set = false;
|
|
off_set = true;
|
|
break;
|
|
default:
|
|
VTR_LOGF_ERROR(__FILE__, __LINE__,
|
|
"Invalid default_signal_init_value '%lu'!\n",
|
|
default_sram_bit_value);
|
|
exit(1);
|
|
}
|
|
} else {
|
|
on_set = lut_truth_table_use_on_set(truth_table);
|
|
off_set = !on_set;
|
|
}
|
|
|
|
/* Read in truth table lines, decode one by one */
|
|
for (const std::vector<vtr::LogicValue>& tt_line : truth_table) {
|
|
/* Complete the truth table line by line*/
|
|
completed_truth_table.push_back(
|
|
complete_truth_table_line(lut_size, tt_line));
|
|
}
|
|
|
|
/* Initial all the bits in the bitstream */
|
|
if (true == off_set) {
|
|
/* By default, the lut_bitstream is initialize for on_set
|
|
* For off set, it should be flipped
|
|
*/
|
|
lut_bitstream.clear();
|
|
lut_bitstream.resize(bitstream_size, true);
|
|
}
|
|
|
|
for (const std::vector<vtr::LogicValue>& tt_line : completed_truth_table) {
|
|
/* Update the truth table, sram_bits */
|
|
rec_build_lut_bitstream_per_line(lut_bitstream, lut_size, tt_line, 0);
|
|
}
|
|
|
|
return lut_bitstream;
|
|
}
|
|
|
|
/********************************************************************
|
|
* Generate bitstream for a fracturable LUT (also applicable to single-output
|
|
*LUT) Check type of truth table of each mapped logical block if it is on-set,
|
|
*we give a all 0 base bitstream if it is off-set, we give a all 1 base
|
|
*bitstream
|
|
*******************************************************************/
|
|
std::vector<bool> build_frac_lut_bitstream(
|
|
const CircuitLibrary& circuit_lib, const MuxGraph& lut_mux_graph,
|
|
const VprDeviceAnnotation& device_annotation,
|
|
const std::map<const t_pb_graph_pin*, AtomNetlist::TruthTable>& truth_tables,
|
|
const size_t& default_sram_bit_value) {
|
|
/* Initialization */
|
|
std::vector<bool> lut_bitstream(lut_mux_graph.num_inputs(),
|
|
default_sram_bit_value);
|
|
|
|
for (std::pair<const t_pb_graph_pin*, AtomNetlist::TruthTable> element :
|
|
truth_tables) {
|
|
/* Find the corresponding circuit model output port and assoicated
|
|
* lut_output_mask */
|
|
CircuitPortId lut_model_output_port =
|
|
device_annotation.pb_circuit_port(element.first->port);
|
|
size_t lut_frac_level =
|
|
circuit_lib.port_lut_frac_level(lut_model_output_port);
|
|
/* By default, lut_frac_level will be the lut_size, i.e., number of levels
|
|
* of the mux graph */
|
|
if (size_t(-1) == lut_frac_level) {
|
|
lut_frac_level = lut_mux_graph.num_levels();
|
|
}
|
|
|
|
/* Find the corresponding circuit model output port and assoicated
|
|
* lut_output_mask */
|
|
size_t lut_output_mask = circuit_lib.port_lut_output_mask(
|
|
lut_model_output_port)[element.first->pin_number];
|
|
|
|
/* Decode lut sram bits */
|
|
std::vector<bool> temp_bitstream = build_single_output_lut_bitstream(
|
|
element.second, lut_mux_graph, default_sram_bit_value);
|
|
|
|
/* Depending on the frac-level, we get the location(starting/end points) of
|
|
* sram bits */
|
|
size_t length_of_temp_bitstream_to_copy =
|
|
(size_t)pow(2., (double)(lut_frac_level));
|
|
size_t bitstream_offset =
|
|
length_of_temp_bitstream_to_copy * lut_output_mask;
|
|
/* Ensure the offset is in range */
|
|
VTR_ASSERT(bitstream_offset < lut_bitstream.size());
|
|
VTR_ASSERT(bitstream_offset + length_of_temp_bitstream_to_copy <=
|
|
lut_bitstream.size());
|
|
|
|
/* Print debug information
|
|
bool verbose = true;
|
|
VTR_LOGV(verbose, "Full truth table\n");
|
|
for (const std::string& tt_line : truth_table_to_string(element.second)) {
|
|
VTR_LOGV(verbose, "\t%s\n", tt_line.c_str());
|
|
}
|
|
VTR_LOGV(verbose, "\n");
|
|
|
|
VTR_LOGV(verbose, "Bitstream (size = %ld)\n", temp_bitstream.size());
|
|
for (const bool& bit : temp_bitstream) {
|
|
if (true == bit) {
|
|
VTR_LOGV(verbose, "1");
|
|
} else {
|
|
VTR_ASSERT(false == bit);
|
|
VTR_LOGV(verbose, "0");
|
|
}
|
|
}
|
|
VTR_LOGV(verbose, "\n");
|
|
|
|
VTR_LOGV(verbose, "Bitstream offset = %d\n", bitstream_offset);
|
|
VTR_LOGV(verbose, "Bitstream length to be used = %d\n",
|
|
length_of_temp_bitstream_to_copy);
|
|
*/
|
|
|
|
/* Copy to the segment of bitstream */
|
|
for (size_t bit = bitstream_offset;
|
|
bit < bitstream_offset + length_of_temp_bitstream_to_copy; ++bit) {
|
|
lut_bitstream[bit] = temp_bitstream[bit];
|
|
}
|
|
}
|
|
|
|
return lut_bitstream;
|
|
}
|
|
|
|
/***************************************************************************************
|
|
* Identify if LUT is used as wiring
|
|
* In this case, LUT functions as a buffer
|
|
* +------+
|
|
* in0 -->|--- |
|
|
* | \ |
|
|
* in1 -->| --|--->out
|
|
* ...
|
|
*
|
|
* Note that this function judge the LUT operating mode from the input nets and
|
|
*output nets that are mapped to inputs and outputs. If the output net appear in
|
|
*the list of input nets, this LUT is used as a wire
|
|
***************************************************************************************/
|
|
bool is_wired_lut(const std::vector<AtomNetId>& input_nets,
|
|
const AtomNetId& output_net) {
|
|
for (const AtomNetId& input_net : input_nets) {
|
|
if (input_net == output_net) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
} /* end namespace openfpga */
|