Merge branch 'refactoring' into dev

This commit is contained in:
tangxifan 2020-02-26 11:42:50 -07:00
commit 1fa36c22d3
41 changed files with 3317 additions and 38 deletions

View File

@ -1,15 +1,20 @@
#ifndef VTR_LOGIC_H
#define VTR_LOGIC_H
#include <array>
namespace vtr {
enum class LogicValue {
FALSE = 0,
TRUE = 1,
DONT_CARE = 2,
UNKOWN = 3
UNKOWN = 3,
NUM_LOGIC_VALUE_TYPES
};
constexpr std::array<const char*, size_t(LogicValue::NUM_LOGIC_VALUE_TYPES)> LOGIC_VALUE_STRING = {{"false", "true", "don't care", "unknown"}};
} // namespace vtr
#endif

View File

@ -0,0 +1,34 @@
/********************************************************************
* This file includes functions that are used to annotate pb_graph_node
* and pb_graph_pins from VPR to OpenFPGA
*******************************************************************/
/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_geometry.h"
#include "annotate_placement.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* Assign mapped blocks to grid locations
* This is used by bitstream generator mainly as a fast look-up to
* get mapped blocks with a given coordinate
*******************************************************************/
void annotate_mapped_blocks(const DeviceContext& device_ctx,
const ClusteringContext& cluster_ctx,
const PlacementContext& place_ctx,
VprPlacementAnnotation& place_annotation) {
VTR_LOG("Building annotation for mapped blocks on grid locations...");
place_annotation.init_mapped_blocks(device_ctx.grid);
for (const ClusterBlockId& blk_id : cluster_ctx.clb_nlist.blocks()) {
vtr::Point<size_t> grid_coord(place_ctx.block_locs[blk_id].loc.x, place_ctx.block_locs[blk_id].loc.y);
place_annotation.add_mapped_block(grid_coord, place_ctx.block_locs[blk_id].loc.z, blk_id);
}
VTR_LOG("Done\n");
}
} /* end namespace openfpga */

View File

@ -0,0 +1,24 @@
#ifndef ANNOTATE_PLACEMENT_H
#define ANNOTATE_PLACEMENT_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include "vpr_context.h"
#include "vpr_placement_annotation.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
void annotate_mapped_blocks(const DeviceContext& device_ctx,
const ClusteringContext& cluster_ctx,
const PlacementContext& place_ctx,
VprPlacementAnnotation& place_annotation);
} /* end namespace openfpga */
#endif

View File

@ -86,5 +86,10 @@ void VprClusteringAnnotation::add_physical_pb(const ClusterBlockId& block_id,
physical_pbs_[block_id] = physical_pb;
}
PhysicalPb& VprClusteringAnnotation::mutable_physical_pb(const ClusterBlockId& block_id) {
VTR_ASSERT(physical_pbs_.end() != physical_pbs_.find(block_id));
return physical_pbs_.at(block_id);
}
} /* End namespace openfpga*/

View File

@ -41,6 +41,7 @@ class VprClusteringAnnotation {
const ClusterNetId& net_id);
void adapt_truth_table(t_pb* pb, const AtomNetlist::TruthTable& tt);
void add_physical_pb(const ClusterBlockId& block_id, const PhysicalPb& physical_pb);
PhysicalPb& mutable_physical_pb(const ClusterBlockId& block_id);
private: /* Internal data */
/* Pair a regular pb_type to its physical pb_type */
std::map<ClusterBlockId, std::map<int, ClusterNetId>> net_names_;

View File

@ -0,0 +1,49 @@
/************************************************************************
* Member functions for class VprPlacementAnnotation
***********************************************************************/
#include "vtr_log.h"
#include "vtr_assert.h"
#include "vpr_placement_annotation.h"
/* namespace openfpga begins */
namespace openfpga {
/************************************************************************
* Constructors
***********************************************************************/
/************************************************************************
* Public accessors
***********************************************************************/
std::vector<ClusterBlockId> VprPlacementAnnotation::grid_blocks(const vtr::Point<size_t>& grid_coord) const {
return blocks_[grid_coord.x()][grid_coord.y()];
}
/************************************************************************
* Public mutators
***********************************************************************/
void VprPlacementAnnotation::init_mapped_blocks(const DeviceGrid& grids) {
/* Size the block array with grid sizes */
blocks_.resize({grids.width(), grids.height()});
/* Resize the number of blocks allowed per grid by the capacity of the type */
for (size_t x = 0; x < grids.width(); ++x) {
for (size_t y = 0; y < grids.height(); ++y) {
/* Deposit invalid ids and we will fill later */
blocks_[x][y].resize(grids[x][y].type->capacity, ClusterBlockId::INVALID());
}
}
}
void VprPlacementAnnotation::add_mapped_block(const vtr::Point<size_t>& grid_coord,
const size_t& z,
const ClusterBlockId& mapped_block) {
VTR_ASSERT(z < grid_blocks(grid_coord).size());
if (ClusterBlockId::INVALID() != blocks_[grid_coord.x()][grid_coord.y()][z]) {
VTR_LOG("Override mapped blocks at grid[%lu][%lu][%lu]!\n",
grid_coord.x(), grid_coord.y(), z);
}
blocks_[grid_coord.x()][grid_coord.y()][z] = mapped_block;
}
} /* End namespace openfpga*/

View File

@ -0,0 +1,48 @@
#ifndef VPR_PLACEMENT_ANNOTATION_H
#define VPR_PLACEMENT_ANNOTATION_H
/********************************************************************
* Include header files required by the data structure definition
*******************************************************************/
#include <map>
/* Header from vtrutil library */
#include "vtr_geometry.h"
/* Header from vpr library */
#include "device_grid.h"
#include "clustered_netlist.h"
/* Begin namespace openfpga */
namespace openfpga {
/********************************************************************
* This is the critical data structure to annotate placement results
* in VPR context
*******************************************************************/
class VprPlacementAnnotation {
public: /* Public accessors */
std::vector<ClusterBlockId> grid_blocks(const vtr::Point<size_t>& grid_coord) const;
public: /* Public mutators */
void init_mapped_blocks(const DeviceGrid& grids);
void add_mapped_block(const vtr::Point<size_t>& grid_coord,
const size_t& z, const ClusterBlockId& mapped_block);
private: /* Internal data */
/* A direct mapping show each mapped/unmapped blocks in grids
* The blocks_ array represents each grid on the FPGA fabric
* For example, block_[x][y] showed the mapped/unmapped blocks
* at grid[x][y]. The third coordinate 'z' is the index of the same
* type of blocks in the grids. This is mainly applied to I/O
* blocks where you may have >1 I/O in a grid
*
* Note that this is different from the grid blocks in PlacementContext
* VPR considers only mapped blocks while this annotation
* considers both unmapped and mapped blocks
* Unmapped blocks will be labelled as an invalid id in the vector
*/
vtr::Matrix<std::vector<ClusterBlockId>> blocks_;
};
} /* End namespace openfpga*/
#endif

View File

@ -0,0 +1,51 @@
/********************************************************************
* This file includes functions to build bitstream database
*******************************************************************/
/* Headers from vtrutil library */
#include "vtr_time.h"
#include "vtr_log.h"
#include "build_device_bitstream.h"
#include "bitstream_writer.h"
#include "build_fabric_bitstream.h"
#include "openfpga_bitstream.h"
/* Include global variables of VPR */
#include "globals.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* A wrapper function to call the build_device_bitstream() in FPGA bitstream
*******************************************************************/
void fpga_bitstream(OpenfpgaContext& openfpga_ctx,
const Command& cmd, const CommandContext& cmd_context) {
CommandOptionId opt_verbose = cmd.option("verbose");
CommandOptionId opt_file = cmd.option("file");
openfpga_ctx.mutable_bitstream_manager() = build_device_bitstream(g_vpr_ctx,
openfpga_ctx,
cmd_context.option_enable(cmd, opt_verbose));
if (true == cmd_context.option_enable(cmd, opt_file)) {
write_arch_independent_bitstream_to_xml_file(openfpga_ctx.bitstream_manager(),
cmd_context.option_value(cmd, opt_file));
}
}
/********************************************************************
* A wrapper function to call the build_fabric_bitstream() in FPGA bitstream
*******************************************************************/
void build_fabric_bitstream(OpenfpgaContext& openfpga_ctx,
const Command& cmd, const CommandContext& cmd_context) {
CommandOptionId opt_verbose = cmd.option("verbose");
openfpga_ctx.mutable_fabric_bitstream() = build_fabric_dependent_bitstream(openfpga_ctx.bitstream_manager(),
openfpga_ctx.module_graph(),
cmd_context.option_enable(cmd, opt_verbose));
}
} /* end namespace openfpga */

View File

@ -0,0 +1,26 @@
#ifndef OPENFPGA_BITSTREAM_H
#define OPENFPGA_BITSTREAM_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include "command.h"
#include "command_context.h"
#include "openfpga_context.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
void fpga_bitstream(OpenfpgaContext& openfpga_ctx,
const Command& cmd, const CommandContext& cmd_context);
void build_fabric_bitstream(OpenfpgaContext& openfpga_ctx,
const Command& cmd, const CommandContext& cmd_context);
} /* end namespace openfpga */
#endif

View File

@ -5,6 +5,7 @@
* - repack : create physical pbs and redo packing
*******************************************************************/
#include "openfpga_repack.h"
#include "openfpga_bitstream.h"
#include "openfpga_bitstream_command.h"
/* begin namespace openfpga */
@ -33,6 +34,45 @@ void add_openfpga_bitstream_commands(openfpga::Shell<OpenfpgaContext>& shell) {
std::vector<ShellCommandId> cmd_dependency_repack;
cmd_dependency_repack.push_back(shell_cmd_build_fabric_id);
shell.set_command_dependency(shell_cmd_repack_id, cmd_dependency_repack);
/********************************
* Command 'fpga_bitstream'
*/
Command shell_cmd_fpga_bitstream("fpga_bitstream");
/* Add an option '--file' in short '-f'*/
CommandOptionId fpga_bitstream_opt_file = shell_cmd_fpga_bitstream.add_option("file", true, "file path to output the bitstream database");
shell_cmd_fpga_bitstream.set_option_short_name(fpga_bitstream_opt_file, "f");
shell_cmd_fpga_bitstream.set_option_require_value(fpga_bitstream_opt_file, openfpga::OPT_STRING);
/* Add an option '--verbose' */
shell_cmd_fpga_bitstream.add_option("verbose", false, "Enable verbose output");
/* Add command 'fpga_bitstream' to the Shell */
ShellCommandId shell_cmd_fpga_bitstream_id = shell.add_command(shell_cmd_fpga_bitstream, "Build bitstream database");
shell.set_command_class(shell_cmd_fpga_bitstream_id, openfpga_bitstream_cmd_class);
shell.set_command_execute_function(shell_cmd_fpga_bitstream_id, fpga_bitstream);
/* The 'fpga_bitstream' command should NOT be executed before 'repack' */
std::vector<ShellCommandId> cmd_dependency_fpga_bitstream;
cmd_dependency_fpga_bitstream.push_back(shell_cmd_repack_id);
shell.set_command_dependency(shell_cmd_fpga_bitstream_id, cmd_dependency_fpga_bitstream);
/********************************
* Command 'build_fabric_bitstream'
*/
Command shell_cmd_fabric_bitstream("build_fabric_bitstream");
/* Add an option '--verbose' */
shell_cmd_fabric_bitstream.add_option("verbose", false, "Enable verbose output");
/* Add command 'fabric_bitstream' to the Shell */
ShellCommandId shell_cmd_fabric_bitstream_id = shell.add_command(shell_cmd_fabric_bitstream, "Reorganize the fabric-independent bitstream for the FPGA fabric created by FPGA-Verilog");
shell.set_command_class(shell_cmd_fabric_bitstream_id, openfpga_bitstream_cmd_class);
shell.set_command_execute_function(shell_cmd_fabric_bitstream_id, build_fabric_bitstream);
/* The 'fabric_bitstream' command should NOT be executed before 'fpga_bitstream' */
std::vector<ShellCommandId> cmd_dependency_fabric_bitstream;
cmd_dependency_fabric_bitstream.push_back(shell_cmd_fpga_bitstream_id);
shell.set_command_dependency(shell_cmd_fabric_bitstream_id, cmd_dependency_fabric_bitstream);
}
} /* end namespace openfpga */

View File

@ -1,16 +1,19 @@
#ifndef OPENFPGA_CONTEXT_H
#define OPENFPGA_CONTEXT_H
#include <vector>
#include "vpr_context.h"
#include "openfpga_arch.h"
#include "vpr_netlist_annotation.h"
#include "vpr_device_annotation.h"
#include "vpr_clustering_annotation.h"
#include "vpr_placement_annotation.h"
#include "vpr_routing_annotation.h"
#include "mux_library.h"
#include "tile_direct.h"
#include "module_manager.h"
#include "openfpga_flow_manager.h"
#include "bitstream_manager.h"
#include "device_rr_gsb.h"
/********************************************************************
@ -46,23 +49,29 @@ class OpenfpgaContext : public Context {
const openfpga::VprDeviceAnnotation& vpr_device_annotation() const { return vpr_device_annotation_; }
const openfpga::VprNetlistAnnotation& vpr_netlist_annotation() const { return vpr_netlist_annotation_; }
const openfpga::VprClusteringAnnotation& vpr_clustering_annotation() const { return vpr_clustering_annotation_; }
const openfpga::VprPlacementAnnotation& vpr_placement_annotation() const { return vpr_placement_annotation_; }
const openfpga::VprRoutingAnnotation& vpr_routing_annotation() const { return vpr_routing_annotation_; }
const openfpga::DeviceRRGSB& device_rr_gsb() const { return device_rr_gsb_; }
const openfpga::MuxLibrary& mux_lib() const { return mux_lib_; }
const openfpga::TileDirect& tile_direct() const { return tile_direct_; }
const openfpga::ModuleManager& module_graph() const { return module_graph_; }
const openfpga::FlowManager& flow_manager() const { return flow_manager_; }
const openfpga::BitstreamManager& bitstream_manager() const { return bitstream_manager_; }
const std::vector<openfpga::ConfigBitId>& fabric_bitstream() const { return fabric_bitstream_; }
public: /* Public mutators */
openfpga::Arch& mutable_arch() { return arch_; }
openfpga::VprDeviceAnnotation& mutable_vpr_device_annotation() { return vpr_device_annotation_; }
openfpga::VprNetlistAnnotation& mutable_vpr_netlist_annotation() { return vpr_netlist_annotation_; }
openfpga::VprClusteringAnnotation& mutable_vpr_clustering_annotation() { return vpr_clustering_annotation_; }
openfpga::VprPlacementAnnotation& mutable_vpr_placement_annotation() { return vpr_placement_annotation_; }
openfpga::VprRoutingAnnotation& mutable_vpr_routing_annotation() { return vpr_routing_annotation_; }
openfpga::DeviceRRGSB& mutable_device_rr_gsb() { return device_rr_gsb_; }
openfpga::MuxLibrary& mutable_mux_lib() { return mux_lib_; }
openfpga::TileDirect& mutable_tile_direct() { return tile_direct_; }
openfpga::ModuleManager& mutable_module_graph() { return module_graph_; }
openfpga::FlowManager& mutable_flow_manager() { return flow_manager_; }
openfpga::BitstreamManager& mutable_bitstream_manager() { return bitstream_manager_; }
std::vector<openfpga::ConfigBitId>& mutable_fabric_bitstream() { return fabric_bitstream_; }
private: /* Internal data */
/* Data structure to store information from read_openfpga_arch library */
openfpga::Arch arch_;
@ -76,6 +85,9 @@ class OpenfpgaContext : public Context {
/* Pin net fix to cluster results */
openfpga::VprClusteringAnnotation vpr_clustering_annotation_;
/* Placement results */
openfpga::VprPlacementAnnotation vpr_placement_annotation_;
/* Routing results annotation */
openfpga::VprRoutingAnnotation vpr_routing_annotation_;
@ -90,6 +102,10 @@ class OpenfpgaContext : public Context {
/* Fabric module graph */
openfpga::ModuleManager module_graph_;
/* Bitstream database */
openfpga::BitstreamManager bitstream_manager_;
std::vector<openfpga::ConfigBitId> fabric_bitstream_;
/* Flow status */
openfpga::FlowManager flow_manager_;

View File

@ -15,6 +15,7 @@
#include "annotate_rr_graph.h"
#include "mux_library_builder.h"
#include "build_tile_direct.h"
#include "annotate_placement.h"
#include "openfpga_link_arch.h"
/* Include global variables of VPR */
@ -109,8 +110,14 @@ void link_arch(OpenfpgaContext& openfpga_ctx,
const_cast<const OpenfpgaContext&>(openfpga_ctx));
/* Build tile direct annotation */
openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(),
openfpga_ctx.arch().arch_direct);
openfpga_ctx.mutable_tile_direct() = build_device_tile_direct(g_vpr_ctx.device(),
openfpga_ctx.arch().arch_direct);
/* Annotate placement results */
annotate_mapped_blocks(g_vpr_ctx.device(),
g_vpr_ctx.clustering(),
g_vpr_ctx.placement(),
openfpga_ctx.mutable_vpr_placement_annotation());
}
} /* end namespace openfpga */

View File

@ -5,7 +5,7 @@
#include "vtr_time.h"
#include "vtr_log.h"
#include "verilog_api.h"
#include "build_physical_truth_table.h"
#include "repack.h"
#include "openfpga_repack.h"
@ -29,6 +29,12 @@ void repack(OpenfpgaContext& openfpga_ctx,
openfpga_ctx.mutable_vpr_device_annotation(),
openfpga_ctx.mutable_vpr_clustering_annotation(),
cmd_context.option_enable(cmd, opt_verbose));
build_physical_lut_truth_tables(openfpga_ctx.mutable_vpr_clustering_annotation(),
g_vpr_ctx.atom(),
g_vpr_ctx.clustering(),
openfpga_ctx.vpr_device_annotation(),
openfpga_ctx.arch().circuit_lib);
}
} /* end namespace openfpga */

View File

@ -914,35 +914,15 @@ void build_physical_tile_module(ModuleManager& module_manager,
ModuleId grid_module = module_manager.add_module(grid_module_name);
VTR_ASSERT(true == module_manager.valid_module_id(grid_module));
/* Now each physical tile may have a number of diffrent logical blocks
* We assume the following organization:
*
* Physical tile
* +-----------------------
* |
* | pb_typeA[0] - from equivalent site[A]
* | +--------------------
* | |
* | +--------------------
* |
* | pb_typeB[0] - from equivalent site[B]
* | +--------------------
* | |
* | +--------------------
* ... ...
* | pb_typeA[capacity - 1] - from equivalent site[A]
* | +--------------------
* | |
* | +--------------------
* |
* | pb_typeB[capacity - 1] - from equivalent site[B]
* | +--------------------
* | |
* | +--------------------
* |
* +-----------------------
/* Now each physical tile may have a number of logical blocks
* OpenFPGA only considers the physical implementation of the tiles.
* So, we do not allow multiple equivalent sites to be defined
* under a physical tile type.
* If you need different equivalent sites, you can always define
* it as a mode under a <pb_type>
*/
for (int iz = 0; iz < phy_block_type->capacity; ++iz) {
VTR_ASSERT(1 == phy_block_type->equivalent_sites.size());
for (t_logical_block_type_ptr lb_type : phy_block_type->equivalent_sites) {
/* Bypass empty pb_graph */
if (nullptr == lb_type->pb_graph_head) {

View File

@ -0,0 +1,187 @@
/******************************************************************************
* This file includes member functions for data structure BitstreamManager
******************************************************************************/
#include <algorithm>
#include "vtr_assert.h"
#include "bitstream_manager.h"
/* begin namespace openfpga */
namespace openfpga {
/**************************************************
* Public Accessors : Aggregates
*************************************************/
/* Find all the configuration bits */
BitstreamManager::config_bit_range BitstreamManager::bits() const {
return vtr::make_range(bit_ids_.begin(), bit_ids_.end());
}
/* Find all the configuration blocks */
BitstreamManager::config_block_range BitstreamManager::blocks() const {
return vtr::make_range(block_ids_.begin(), block_ids_.end());
}
/******************************************************************************
* Public Accessors
******************************************************************************/
bool BitstreamManager::bit_value(const ConfigBitId& bit_id) const {
/* Ensure a valid id */
VTR_ASSERT(true == valid_bit_id(bit_id));
return bit_values_[bit_id];
}
std::string BitstreamManager::block_name(const ConfigBlockId& block_id) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(block_id));
return block_names_[block_id];
}
ConfigBlockId BitstreamManager::block_parent(const ConfigBlockId& block_id) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(block_id));
return parent_block_ids_[block_id];
}
std::vector<ConfigBlockId> BitstreamManager::block_children(const ConfigBlockId& block_id) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(block_id));
return child_block_ids_[block_id];
}
std::vector<ConfigBitId> BitstreamManager::block_bits(const ConfigBlockId& block_id) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(block_id));
return block_bit_ids_[block_id];
}
ConfigBlockId BitstreamManager::bit_parent_block(const ConfigBitId& bit_id) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_bit_id(bit_id));
return bit_parent_block_ids_[bit_id];
}
size_t BitstreamManager::bit_index_in_parent_block(const ConfigBitId& bit_id) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_bit_id(bit_id));
ConfigBlockId bit_parent_block = bit_parent_block_ids_[bit_id];
VTR_ASSERT(true == valid_block_id(bit_parent_block));
for (size_t index = 0; index < block_bits(bit_parent_block).size(); ++index) {
if (bit_id == block_bits(bit_parent_block)[index]) {
return index;
}
}
/* Not found, return in valid value */
return size_t(-1);
}
/* Find the child block in a bitstream manager with a given name */
ConfigBlockId BitstreamManager::find_child_block(const ConfigBlockId& block_id,
const std::string& child_block_name) const {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(block_id));
std::vector<ConfigBlockId> candidates;
for (const ConfigBlockId& child : block_children(block_id)) {
if (0 == child_block_name.compare(block_name(child))) {
candidates.push_back(child);
}
}
/* We should have 0 or 1 candidate! */
VTR_ASSERT(0 == candidates.size() || 1 == candidates.size());
if (0 == candidates.size()) {
/* Not found, return an invalid value */
return ConfigBlockId::INVALID();
}
return candidates[0];
}
/******************************************************************************
* Public Mutators
******************************************************************************/
ConfigBitId BitstreamManager::add_bit(const bool& bit_value) {
ConfigBitId bit = ConfigBitId(bit_ids_.size());
/* Add a new bit, and allocate associated data structures */
bit_ids_.push_back(bit);
bit_values_.push_back(bit_value);
shared_config_bit_values_.emplace_back();
bit_parent_block_ids_.push_back(ConfigBlockId::INVALID());
return bit;
}
ConfigBlockId BitstreamManager::add_block(const std::string& block_name) {
ConfigBlockId block = ConfigBlockId(block_ids_.size());
/* Add a new bit, and allocate associated data structures */
block_ids_.push_back(block);
block_names_.push_back(block_name);
block_bit_ids_.emplace_back();
parent_block_ids_.push_back(ConfigBlockId::INVALID());
child_block_ids_.emplace_back();
return block;
}
void BitstreamManager::add_child_block(const ConfigBlockId& parent_block, const ConfigBlockId& child_block) {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(parent_block));
VTR_ASSERT(true == valid_block_id(child_block));
/* We should have only a parent block for each block! */
VTR_ASSERT(ConfigBlockId::INVALID() == parent_block_ids_[child_block]);
/* Ensure the child block is not in the list of children of the parent block */
std::vector<ConfigBlockId>::iterator it = std::find(child_block_ids_[parent_block].begin(), child_block_ids_[parent_block].end(), child_block);
VTR_ASSERT(it == child_block_ids_[parent_block].end());
/* Add the child_block to the parent_block */
child_block_ids_[parent_block].push_back(child_block);
/* Register the block in the parent of the block */
parent_block_ids_[child_block] = parent_block;
}
void BitstreamManager::add_bit_to_block(const ConfigBlockId& block, const ConfigBitId& bit) {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_block_id(block));
VTR_ASSERT(true == valid_bit_id(bit));
/* We should have only a parent block for each bit! */
VTR_ASSERT(ConfigBlockId::INVALID() == bit_parent_block_ids_[bit]);
/* Add the bit to the block */
block_bit_ids_[block].push_back(bit);
/* Register the block in the parent of the bit */
bit_parent_block_ids_[bit] = block;
}
void BitstreamManager::add_shared_config_bit_values(const ConfigBitId& bit, const std::vector<bool>& shared_config_bits) {
/* Ensure the input ids are valid */
VTR_ASSERT(true == valid_bit_id(bit));
shared_config_bit_values_[bit] = shared_config_bits;
}
/******************************************************************************
* Public Validators
******************************************************************************/
bool BitstreamManager::valid_bit_id(const ConfigBitId& bit_id) const {
return (size_t(bit_id) < bit_ids_.size()) && (bit_id == bit_ids_[bit_id]);
}
bool BitstreamManager::valid_block_id(const ConfigBlockId& block_id) const {
return (size_t(block_id) < block_ids_.size()) && (block_id == block_ids_[block_id]);
}
} /* end namespace openfpga */

View File

@ -0,0 +1,133 @@
/******************************************************************************
* This file introduces a data structure to store bitstream-related information
*
* General concept
* ---------------
* The idea is to create a unified data structure that stores all the configuration bits
* with proper annotation to which modules in FPGA fabric it belongs to.
* 1. It can be easily organized in fabric-dependent representation
* (generate a sequence of bitstream which exactly fit the configuration protocol of FPGA fabric)
* 2. Or it can be easily organized in fabric-independent representation (think about XML file)
*
* Cross-reference
* ---------------
* May be used only when you want to bind the bitstream to a specific FPGA fabric!
* If you do so, please make sure the block name is exactly same as the instance name
* of a child module in ModuleManager!!!
* The configurable modules/instances in module manager are arranged
* in the sequence to fit different configuration protocol.
* By using the link between ModuleManager and BitstreamManager,
* we can build a sequence of configuration bits to fit different configuration protocols.
*
* +------------------+ +-----------------+
* | | block_name == instance_name | |
* | BitstreamManager |-------------------------------->| ModuleManager |
* | | | |
* +------------------+ +-----------------+
*
* Restrictions:
* 1. Each block inside BitstreamManager should have only 1 parent block
* and multiple child block
* 2. Each bit inside BitstreamManager should have only 1 parent block
*
******************************************************************************/
#ifndef BITSTREAM_MANAGER_H
#define BITSTREAM_MANAGER_H
#include <vector>
#include <map>
#include "vtr_vector.h"
#include "bitstream_manager_fwd.h"
/* begin namespace openfpga */
namespace openfpga {
class BitstreamManager {
public: /* Types and ranges */
typedef vtr::vector<ConfigBitId, ConfigBitId>::const_iterator config_bit_iterator;
typedef vtr::vector<ConfigBlockId, ConfigBlockId>::const_iterator config_block_iterator;
typedef vtr::Range<config_bit_iterator> config_bit_range;
typedef vtr::Range<config_block_iterator> config_block_range;
public: /* Public aggregators */
/* Find all the configuration bits */
config_bit_range bits() const;
config_block_range blocks() const;
public: /* Public Accessors */
/* Find the value of bitstream */
bool bit_value(const ConfigBitId& bit_id) const;
/* Find a name of a block */
std::string block_name(const ConfigBlockId& block_id) const;
/* Find the parent of a block */
ConfigBlockId block_parent(const ConfigBlockId& block_id) const;
/* Find the children of a block */
std::vector<ConfigBlockId> block_children(const ConfigBlockId& block_id) const;
/* Find all the bits that belong to a block */
std::vector<ConfigBitId> block_bits(const ConfigBlockId& block_id) const;
/* Find the parent block of a bit */
ConfigBlockId bit_parent_block(const ConfigBitId& bit_id) const;
/* Find the index of a configuration bit in its parent block */
size_t bit_index_in_parent_block(const ConfigBitId& bit_id) const;
/* Find the child block in a bitstream manager with a given name */
ConfigBlockId find_child_block(const ConfigBlockId& block_id, const std::string& child_block_name) const;
public: /* Public Mutators */
/* Add a new configuration bit to the bitstream manager */
ConfigBitId add_bit(const bool& bit_value);
/* Add a new block of configuration bits to the bitstream manager */
ConfigBlockId add_block(const std::string& block_name);
/* Set a block as a child block of another */
void add_child_block(const ConfigBlockId& parent_block, const ConfigBlockId& child_block);
/* Add a configuration bit to a block */
void add_bit_to_block(const ConfigBlockId& block, const ConfigBitId& bit);
/* Add share configuration bits to a configuration bit */
void add_shared_config_bit_values(const ConfigBitId& bit, const std::vector<bool>& shared_config_bits);
public: /* Public Validators */
bool valid_bit_id(const ConfigBitId& bit_id) const;
bool valid_block_id(const ConfigBlockId& block_id) const;
private: /* Internal data */
/* Unique id of a block of bits in the Bitstream */
vtr::vector<ConfigBlockId, ConfigBlockId> block_ids_;
vtr::vector<ConfigBlockId, std::vector<ConfigBitId>> block_bit_ids_;
/* Back-annotation for the bits */
/* Parent block of a bit in the Bitstream
* For each bit, the block name can be designed to be same as the instance name in a module
* to reflect its position in the module tree (ModuleManager)
* Note that the blocks here all unique, unlike ModuleManager where modules can be instanciated
* Therefore, this block graph can be considered as a flattened graph of ModuleGraph
*/
vtr::vector<ConfigBlockId, std::string> block_names_;
vtr::vector<ConfigBlockId, ConfigBlockId> parent_block_ids_;
vtr::vector<ConfigBlockId, std::vector<ConfigBlockId>> child_block_ids_;
/* Unique id of a bit in the Bitstream */
vtr::vector<ConfigBitId, ConfigBitId> bit_ids_;
vtr::vector<ConfigBitId, ConfigBlockId> bit_parent_block_ids_;
/* value of a bit in the Bitstream */
vtr::vector<ConfigBitId, bool> bit_values_;
/* value of a shared configuration bits in the Bitstream */
vtr::vector<ConfigBitId, std::vector<bool>> shared_config_bit_values_;
};
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,25 @@
/**************************************************
* This file includes only declarations for
* the data structures for bitstream database
* Please refer to bitstream_manager.h for more details
*************************************************/
#ifndef BITSTREAM_MANAGER_FWD_H
#define BITSTREAM_MANAGER_FWD_H
#include "vtr_strong_id.h"
/* begin namespace openfpga */
namespace openfpga {
/* Strong Ids for BitstreamContext */
struct config_block_id_tag;
struct config_bit_id_tag;
typedef vtr::StrongId<config_block_id_tag> ConfigBlockId;
typedef vtr::StrongId<config_bit_id_tag> ConfigBitId;
class BitstreamManager;
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,58 @@
/********************************************************************
* This file includes most utilized functions for data structure
* BitstreamManager
*
* Note: These functions are not generic enough so that they
* should NOT be a member function!
*******************************************************************/
#include <algorithm>
/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "bitstream_manager_utils.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* Recursively find the hierarchy of a block of bitstream manager
* Return a vector of the block ids, where the top-level block
* locates in the head, while the leaf block locates in the tail
* top, next, ... , block
*******************************************************************/
std::vector<ConfigBlockId> find_bitstream_manager_block_hierarchy(const BitstreamManager& bitstream_manager,
const ConfigBlockId& block) {
std::vector<ConfigBlockId> block_hierarchy;
ConfigBlockId temp_block = block;
/* Generate a tree of parent block */
while (true == bitstream_manager.valid_block_id(temp_block)) {
block_hierarchy.push_back(temp_block);
/* Go to upper level */
temp_block = bitstream_manager.block_parent(temp_block);
}
/* Reverse the vector, so that top block stay in the first */
std::reverse(block_hierarchy.begin(), block_hierarchy.end());
return block_hierarchy;
}
/********************************************************************
* Find all the top-level blocks in a bitstream manager,
* which have no parents
*******************************************************************/
std::vector<ConfigBlockId> find_bitstream_manager_top_blocks(const BitstreamManager& bitstream_manager) {
std::vector<ConfigBlockId> top_blocks;
for (const ConfigBlockId& blk : bitstream_manager.blocks()) {
if (ConfigBlockId::INVALID() != bitstream_manager.block_parent(blk)) {
continue;
}
top_blocks.push_back(blk);
}
return top_blocks;
}
} /* end namespace openfpga */

View File

@ -0,0 +1,24 @@
#ifndef BITSTREAM_MANAGER_UTILS_H
#define BITSTREAM_MANAGER_UTILS_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <vector>
#include "bitstream_manager.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
std::vector<ConfigBlockId> find_bitstream_manager_block_hierarchy(const BitstreamManager& bitstream_manager,
const ConfigBlockId& block);
std::vector<ConfigBlockId> find_bitstream_manager_top_blocks(const BitstreamManager& bitstream_manager);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,145 @@
/********************************************************************
* This file includes functions that output bitstream database
* to files in different formats
*******************************************************************/
#include <chrono>
#include <ctime>
#include <fstream>
/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"
/* Headers from openfpgautil library */
#include "openfpga_digest.h"
#include "openfpga_naming.h"
#include "bitstream_manager_utils.h"
#include "bitstream_writer.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* This function write header information to a bitstream file
*******************************************************************/
static
void write_bitstream_xml_file_head(std::fstream& fp) {
valid_file_stream(fp);
auto end = std::chrono::system_clock::now();
std::time_t end_time = std::chrono::system_clock::to_time_t(end);
fp << "<!--" << std::endl;
fp << "\t- Architecture independent bitstream" << std::endl;
fp << "\t- Author: Xifan TANG" << std::endl;
fp << "\t- Organization: University of Utah" << std::endl;
fp << "\t- Date: " << std::ctime(&end_time) ;
fp << "-->" << std::endl;
fp << std::endl;
}
/********************************************************************
* Recursively write the bitstream of a block to a xml file
* This function will use a Depth-First Search in outputting bitstream
* for each block
* 1. For block with bits as children, we will output the XML lines
* 2. For block without bits/child blocks, we can return
* 3. For block with child blocks, we visit each child recursively
*******************************************************************/
static
void rec_write_block_bitstream_to_xml_file(std::fstream& fp,
const BitstreamManager& bitstream_manager,
const ConfigBlockId& block) {
valid_file_stream(fp);
/* Dive to child blocks if this block has any */
for (const ConfigBlockId& child_block : bitstream_manager.block_children(block)) {
rec_write_block_bitstream_to_xml_file(fp, bitstream_manager, child_block);
}
if (0 == bitstream_manager.block_bits(block).size()) {
return;
}
/* Write the bits of this block */
fp << "<bitstream_block index=\"" << size_t(block) << "\">" << std::endl;
std::vector<ConfigBlockId> block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, block);
/* Output hierarchy of this parent*/
fp << "\t<hierarchy>" << std::endl;
size_t hierarchy_counter = 0;
for (const ConfigBlockId& temp_block : block_hierarchy) {
fp << "\t\t<instance level=\"" << hierarchy_counter << "\"";
fp << " name=\"" << bitstream_manager.block_name(temp_block) << "\"";
fp << "/>" << std::endl;
hierarchy_counter++;
}
fp << "\t</hierarchy>" << std::endl;
/* Output child bits under this block */
size_t bit_counter = 0;
fp << "\t<bitstream>" << std::endl;
for (const ConfigBitId& child_bit : bitstream_manager.block_bits(block)) {
fp << "\t\t<bit";
fp << " memory_port=\"" << generate_configuration_chain_data_out_name() << "[" << bit_counter << "]" << "\"";
fp << " value=\"" << bitstream_manager.bit_value(child_bit) << "\"";
fp << "/>" << std::endl;
bit_counter++;
}
fp << "\t</bitstream>" << std::endl;
fp << "</bitstream_block>" <<std::endl;
}
/********************************************************************
* Write the bitstream to a file without binding to the configuration
* procotols of a given FPGA fabric in XML format
*
* Notes:
* This is a very generic representation for bitstream that are implemented
* by VPR engine. It shows the bitstream for each blocks in the FPGA
* architecture that users are modeling.
* This function can be used to:
* 1. Debug the bitstream decoding to see if there is any bug
* 2. Create an intermediate file to reorganize a bitstream for
* specific FPGAs
* 3. TODO: support FASM format
*******************************************************************/
void write_arch_independent_bitstream_to_xml_file(const BitstreamManager& bitstream_manager,
const std::string& fname) {
/* Ensure that we have a valid file name */
if (true == fname.empty()) {
VTR_LOG_ERROR("Received empty file name to output bitstream!\n\tPlease specify a valid file name.\n");
}
std::string timer_message = std::string("Write ") + std::to_string(bitstream_manager.bits().size()) + std::string(" architecture independent bitstream into XML file '") + fname + std::string("'");
vtr::ScopedStartFinishTimer timer(timer_message);
/* Create the file stream */
std::fstream fp;
fp.open(fname, std::fstream::out | std::fstream::trunc);
check_file_stream(fname.c_str(), fp);
/* Put down a brief introduction */
write_bitstream_xml_file_head(fp);
std::string top_block_name = generate_fpga_top_module_name();
/* Find the top block, which has not parents */
std::vector<ConfigBlockId> top_block = find_bitstream_manager_top_blocks(bitstream_manager);
/* Make sure we have only 1 top block and its name matches the top module */
VTR_ASSERT(1 == top_block.size());
VTR_ASSERT(0 == top_block_name.compare(bitstream_manager.block_name(top_block[0])));
/* Write bitstream, block by block, in a recursive way */
rec_write_block_bitstream_to_xml_file(fp, bitstream_manager, top_block[0]);
/* Close file handler */
fp.close();
}
} /* end namespace openfpga */

View File

@ -0,0 +1,22 @@
#ifndef BITSTREAM_WRITER_H
#define BITSTREAM_WRITER_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <string>
#include "bitstream_manager.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
void write_arch_independent_bitstream_to_xml_file(const BitstreamManager& bitstream_manager,
const std::string& fname);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,86 @@
/********************************************************************
* This file includes functions to build bitstream from a mapped
* FPGA fabric.
* We decode the bitstream from configuration of routing multiplexers
* and Look-Up Tables (LUTs) which locate in CLBs and global routing architecture
*******************************************************************/
#include <vector>
/* Headers from vtrutil library */
#include "vtr_log.h"
#include "vtr_assert.h"
#include "vtr_time.h"
#include "openfpga_naming.h"
#include "build_grid_bitstream.h"
#include "build_routing_bitstream.h"
#include "build_device_bitstream.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* A top-level function to build a bistream from the FPGA device
* 1. It will organize the bitstream w.r.t. the hierarchy of module graphs
* describing the FPGA fabric
* 2. It will decode configuration bits from routing multiplexers used in
* global routing architecture
* 3. It will decode configuration bits from routing multiplexers and LUTs
* used in CLBs
*
* Note: this function create a bitstream which is binding to the module graphs
* of the FPGA fabric that FPGA-X2P generates!
* But it can be used to output a generic bitstream for VPR mapping FPGA
*******************************************************************/
BitstreamManager build_device_bitstream(const VprContext& vpr_ctx,
const OpenfpgaContext& openfpga_ctx,
const bool& verbose) {
std::string timer_message = std::string("\nBuild fabric-independent bitstream for implementation '") + vpr_ctx.atom().nlist.netlist_name() + std::string("'\n");
vtr::ScopedStartFinishTimer timer(timer_message);
/* Bitstream manager to be built */
BitstreamManager bitstream_manager;
/* Assign the SRAM model applied to the FPGA fabric */
CircuitModelId sram_model = openfpga_ctx.arch().config_protocol.memory_model();
VTR_ASSERT(true == openfpga_ctx.arch().circuit_lib.valid_model_id(sram_model));
/* Create the top-level block for bitstream
* This is related to the top-level module of fpga
*/
std::string top_block_name = generate_fpga_top_module_name();
ConfigBlockId top_block = bitstream_manager.add_block(top_block_name);
/* Create bitstream from grids */
VTR_LOGV(verbose, "Building grid bitstream...\n");
build_grid_bitstream(bitstream_manager, top_block,
openfpga_ctx.module_graph(),
openfpga_ctx.arch().circuit_lib,
openfpga_ctx.mux_lib(),
vpr_ctx.device().grid,
openfpga_ctx.vpr_device_annotation(),
openfpga_ctx.vpr_clustering_annotation(),
openfpga_ctx.vpr_placement_annotation(),
verbose);
VTR_LOGV(verbose, "Done\n");
/* Create bitstream from routing architectures */
VTR_LOGV(verbose, "Building routing bitstream...\n");
build_routing_bitstream(bitstream_manager, top_block,
openfpga_ctx.module_graph(),
openfpga_ctx.arch().circuit_lib,
openfpga_ctx.mux_lib(),
openfpga_ctx.vpr_device_annotation(),
openfpga_ctx.vpr_routing_annotation(),
vpr_ctx.device().rr_graph,
openfpga_ctx.device_rr_gsb());
VTR_LOGV(verbose, "Done\n");
VTR_LOGV(verbose, "Decoded %lu configuration bits\n", bitstream_manager.bits().size());
return bitstream_manager;
}
} /* end namespace openfpga */

View File

@ -0,0 +1,24 @@
#ifndef BUILD_DEVICE_BITSTREAM_H
#define BUILD_DEVICE_BITSTREAM_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <vector>
#include "vpr_context.h"
#include "openfpga_context.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
BitstreamManager build_device_bitstream(const VprContext& vpr_ctx,
const OpenfpgaContext& openfpga_ctx,
const bool& verbose);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,136 @@
/********************************************************************
* This file includes functions to build fabric dependent bitstream
*******************************************************************/
#include <string>
#include <algorithm>
/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"
#include "openfpga_naming.h"
#include "bitstream_manager_utils.h"
#include "build_fabric_bitstream.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* This function will walk through all the configurable children under a module
* in a recursive way, following a Depth-First Search (DFS) strategy
* For each configuration child, we use its instance name as a key to spot the
* configuration bits in bitstream manager.
* Note that it is guarentee that the instance name in module manager is
* consistent with the block names in bitstream manager
* We use this link to reorganize the bitstream in the sequence of memories as we stored
* in the configurable_children) and configurable_child_instances() of each module of module manager
*******************************************************************/
static
void rec_build_module_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_block,
const ModuleManager& module_manager,
const ModuleId& parent_module,
std::vector<ConfigBitId>& fabric_bitstream) {
/* Depth-first search: if we have any children in the parent_block,
* we dive to the next level first!
*/
if (0 < bitstream_manager.block_children(parent_block).size()) {
for (size_t child_id = 0; child_id < module_manager.configurable_children(parent_module).size(); ++child_id) {
ModuleId child_module = module_manager.configurable_children(parent_module)[child_id];
size_t child_instance = module_manager.configurable_child_instances(parent_module)[child_id];
/* Get the instance name and ensure it is not empty */
std::string instance_name = module_manager.instance_name(parent_module, child_module, child_instance);
/* Find the child block that matches the instance name! */
ConfigBlockId child_block = bitstream_manager.find_child_block(parent_block, instance_name);
/* We must have one valid block id! */
if (true != bitstream_manager.valid_block_id(child_block))
VTR_ASSERT(true == bitstream_manager.valid_block_id(child_block));
/* Go recursively */
rec_build_module_fabric_dependent_bitstream(bitstream_manager, child_block,
module_manager, child_module,
fabric_bitstream);
}
/* Ensure that there should be no configuration bits in the parent block */
VTR_ASSERT(0 == bitstream_manager.block_bits(parent_block).size());
}
/* Note that, reach here, it means that this is a leaf node.
* We add the configuration bits to the fabric_bitstream,
* And then, we can return
*/
for (const ConfigBitId& config_bit : bitstream_manager.block_bits(parent_block)) {
fabric_bitstream.push_back(config_bit);
}
}
/********************************************************************
* A top-level function re-organizes the bitstream for a specific
* FPGA fabric, where configuration bits are organized in the sequence
* that can be directly loaded to the FPGA configuration protocol.
* Support:
* 1. Configuration chain
* 2. Memory decoders
* This function does NOT modify the bitstream database
* Instead, it builds a vector of ids for configuration bits in bitstream manager
*
* This function can be called ONLY after the function build_device_bitstream()
* Note that this function does NOT decode bitstreams from circuit implementation
* It was done in the function build_device_bitstream()
*******************************************************************/
std::vector<ConfigBitId> build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager,
const ModuleManager& module_manager,
const bool& verbose) {
std::vector<ConfigBitId> fabric_bitstream;
vtr::ScopedStartFinishTimer timer("\nBuild fabric dependent bitstream\n");
/* Get the top module name in module manager, which is our starting point */
std::string top_module_name = generate_fpga_top_module_name();
ModuleId top_module = module_manager.find_module(top_module_name);
VTR_ASSERT(true == module_manager.valid_module_id(top_module));
/* Find the top block in bitstream manager, which has not parents */
std::vector<ConfigBlockId> top_block = find_bitstream_manager_top_blocks(bitstream_manager);
/* Make sure we have only 1 top block and its name matches the top module */
VTR_ASSERT(1 == top_block.size());
VTR_ASSERT(0 == top_module_name.compare(bitstream_manager.block_name(top_block[0])));
rec_build_module_fabric_dependent_bitstream(bitstream_manager, top_block[0],
module_manager, top_module,
fabric_bitstream);
/* Time-consuming sanity check: Uncomment these codes only for debugging!!!
* Check which configuration bits are not touched
*/
/*
for (const ConfigBitId& config_bit : bitstream_manager.bits()) {
std::vector<ConfigBitId>::iterator it = std::find(fabric_bitstream.begin(), fabric_bitstream.end(), config_bit);
if (it == fabric_bitstream.end()) {
std::vector<ConfigBlockId> block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, bitstream_manager.bit_parent_block(config_bit));
std::string block_hierarchy_name;
for (const ConfigBlockId& temp_block : block_hierarchy) {
block_hierarchy_name += std::string("/") + bitstream_manager.block_name(temp_block);
}
vpr_printf(TIO_MESSAGE_INFO,
"bit (parent_block = %s) is not touched!\n",
block_hierarchy_name.c_str());
}
}
*/
/* Ensure our fabric bitstream is in the same size as device bistream */
VTR_ASSERT(bitstream_manager.bits().size() == fabric_bitstream.size());
VTR_LOGV(verbose,
"Built %lu configuration bits for fabric\n",
fabric_bitstream.size());
return fabric_bitstream;
}
} /* end namespace openfpga */

View File

@ -0,0 +1,24 @@
#ifndef BUILD_FABRIC_BITSTREAM_H
#define BUILD_FABRIC_BITSTREAM_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <vector>
#include "bitstream_manager.h"
#include "module_manager.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
std::vector<ConfigBitId> build_fabric_dependent_bitstream(const BitstreamManager& bitstream_manager,
const ModuleManager& module_manager,
const bool& verbose);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,699 @@
/********************************************************************
* This file includes functions that are used for building bitstreams
* for grids (CLBs, heterogenerous blocks, I/Os, etc.)
*******************************************************************/
#include <cmath>
#include <string>
/* Headers from vtrutil library */
#include "vtr_log.h"
#include "vtr_assert.h"
#include "vtr_time.h"
/* Headers from vpr library */
#include "vpr_utils.h"
#include "pb_graph_utils.h"
#include "mux_utils.h"
#include "circuit_library_utils.h"
#include "openfpga_interconnect_types.h"
#include "openfpga_reserved_words.h"
#include "openfpga_naming.h"
#include "mux_bitstream_constants.h"
#include "pb_type_utils.h"
#include "lut_utils.h"
#include "build_mux_bitstream.h"
#include "build_grid_bitstream.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* Decode mode bits "01..." to a bitstream vector
*******************************************************************/
static
std::vector<bool> generate_mode_select_bitstream(const std::vector<size_t>& mode_bits) {
std::vector<bool> mode_select_bitstream;
for (const size_t& mode_bit : mode_bits) {
/* Error out for unexpected bits */
VTR_ASSERT((0 == mode_bit) || (1 == mode_bit));
mode_select_bitstream.push_back(1 == mode_bit);
}
return mode_select_bitstream;
}
/********************************************************************
* Generate bitstream for a primitive node and add it to bitstream manager
*******************************************************************/
static
void build_primitive_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const VprDeviceAnnotation& device_annotation,
const PhysicalPb& physical_pb,
const PhysicalPbId& primitive_pb_id,
t_pb_type* primitive_pb_type) {
/* Ensure a valid physical pritimive pb */
if (nullptr == primitive_pb_type) {
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Invalid primitive_pb_type!\n");
exit(1);
}
CircuitModelId primitive_model = device_annotation.pb_type_circuit_model(primitive_pb_type);
VTR_ASSERT(CircuitModelId::INVALID() != primitive_model);
VTR_ASSERT( (CIRCUIT_MODEL_IOPAD == circuit_lib.model_type(primitive_model))
|| (CIRCUIT_MODEL_HARDLOGIC == circuit_lib.model_type(primitive_model))
|| (CIRCUIT_MODEL_FF == circuit_lib.model_type(primitive_model)) );
/* Find SRAM ports for mode-selection */
std::vector<CircuitPortId> primitive_mode_select_ports = find_circuit_mode_select_sram_ports(circuit_lib, primitive_model);
/* We may have a port for mode select or not. */
VTR_ASSERT( (0 == primitive_mode_select_ports.size())
|| (1 == primitive_mode_select_ports.size()) );
/* Generate bitstream for mode-select ports */
if (0 == primitive_mode_select_ports.size()) {
return; /* Nothing to do, return directly */
}
std::vector<bool> mode_select_bitstream;
if (true == physical_pb.valid_pb_id(primitive_pb_id)) {
mode_select_bitstream = generate_mode_select_bitstream(physical_pb.mode_bits(primitive_pb_id));
} else { /* get default mode_bits */
mode_select_bitstream = generate_mode_select_bitstream(device_annotation.pb_type_mode_bits(primitive_pb_type));
}
/* Ensure the length of bitstream matches the side of memory circuits */
std::vector<CircuitModelId> sram_models = find_circuit_sram_models(circuit_lib, primitive_model);
VTR_ASSERT(1 == sram_models.size());
std::string mem_block_name = generate_memory_module_name(circuit_lib, primitive_model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX));
ModuleId mem_module = module_manager.find_module(mem_block_name);
VTR_ASSERT (true == module_manager.valid_module_id(mem_module));
ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configuration_chain_data_out_name());
VTR_ASSERT(mode_select_bitstream.size() == module_manager.module_port(mem_module, mem_out_port_id).get_width());
/* Create a block for the bitstream which corresponds to the memory module associated to the LUT */
ConfigBlockId mem_block = bitstream_manager.add_block(mem_block_name);
bitstream_manager.add_child_block(parent_configurable_block, mem_block);
/* Add the bitstream to the bitstream manager */
for (const bool& bit : mode_select_bitstream) {
ConfigBitId config_bit = bitstream_manager.add_bit(bit);
/* Link the memory bits to the mux mem block */
bitstream_manager.add_bit_to_block(mem_block, config_bit);
}
}
/********************************************************************
* This function generates bitstream for a programmable routing
* multiplexer which drives an output pin of physical_pb_graph_node and its the input_edges
*
* src_pb_graph_node.[in|out]_pins -----------------> des_pb_graph_node.[in|out]pins
* /|\
* |
* input_pins, edges, output_pins
*******************************************************************/
static
void build_physical_block_pin_interc_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const PhysicalPb& physical_pb,
t_pb_graph_pin* des_pb_graph_pin,
t_mode* physical_mode) {
/* Identify the number of fan-in (Consider interconnection edges of only selected mode) */
t_interconnect* cur_interc = pb_graph_pin_interc(des_pb_graph_pin, physical_mode);
size_t fan_in = pb_graph_pin_inputs(des_pb_graph_pin, cur_interc).size();
if ((nullptr == cur_interc) || (0 == fan_in)) {
/* No interconnection matched */
return;
}
/* Identify pin interconnection type */
enum e_interconnect interc_type = device_annotation.interconnect_physical_type(cur_interc);
switch (interc_type) {
case DIRECT_INTERC:
/* Nothing to do, return */
break;
case COMPLETE_INTERC:
case MUX_INTERC: {
/* Find the circuit model id of the mux, we need its design technology which matters the bitstream generation */
CircuitModelId mux_model = device_annotation.interconnect_circuit_model(cur_interc);
VTR_ASSERT(CIRCUIT_MODEL_MUX == circuit_lib.model_type(mux_model));
/* Find the input size of the implementation of a routing multiplexer */
size_t datapath_mux_size = fan_in;
VTR_ASSERT(true == valid_mux_implementation_num_inputs(datapath_mux_size));
/* Find the path id:
* - if des pb is not valid, this is an unmapped pb, we can set a default path_id
*/
const PhysicalPbId& des_pb_id = physical_pb.find_pb(des_pb_graph_pin->parent_node);
size_t mux_input_pin_id = 0;
if (true != physical_pb.valid_pb_id(des_pb_id)) {
mux_input_pin_id = DEFAULT_PATH_ID;
} else {
for (t_pb_graph_pin* src_pb_graph_pin : pb_graph_pin_inputs(des_pb_graph_pin, cur_interc)) {
const PhysicalPbId& src_pb_id = physical_pb.find_pb(src_pb_graph_pin->parent_node);
/* If the src pb id is not valid, we bypass it */
if ( (true != physical_pb.valid_pb_id(src_pb_id))
&& (physical_pb.pb_graph_pin_atom_net(src_pb_id, src_pb_graph_pin) == physical_pb.pb_graph_pin_atom_net(des_pb_id, des_pb_graph_pin))) {
break;
}
mux_input_pin_id++;
}
VTR_ASSERT (mux_input_pin_id <= fan_in);
/* Unmapped pin, use default path id */
if (fan_in == mux_input_pin_id) {
mux_input_pin_id = DEFAULT_PATH_ID;
}
}
/* Generate bitstream depend on both technology and structure of this MUX */
std::vector<bool> mux_bitstream = build_mux_bitstream(circuit_lib, mux_model, mux_lib, datapath_mux_size, mux_input_pin_id);
/* Create the block denoting the memory instances that drives this node in physical_block */
std::string mem_block_name = generate_pb_memory_instance_name(GRID_MEM_INSTANCE_PREFIX, des_pb_graph_pin, std::string(""));
ConfigBlockId mux_mem_block = bitstream_manager.add_block(mem_block_name);
bitstream_manager.add_child_block(parent_configurable_block, mux_mem_block);
/* Find the module in module manager and ensure the bitstream size matches! */
std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX));
ModuleId mux_mem_module = module_manager.find_module(mem_module_name);
VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module));
ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name());
VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width());
/* Add the bistream to the bitstream manager */
for (const bool& bit : mux_bitstream) {
ConfigBitId config_bit = bitstream_manager.add_bit(bit);
/* Link the memory bits to the mux mem block */
bitstream_manager.add_bit_to_block(mux_mem_block, config_bit);
}
break;
}
default:
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Invalid interconnection type for %s (Arch[LINE%d])!\n",
cur_interc->name, cur_interc->line_num);
exit(1);
}
}
/********************************************************************
* This function generates bitstream for the programmable routing
* multiplexers in a pb_graph node
*******************************************************************/
static
void build_physical_block_interc_port_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
t_pb_graph_node* physical_pb_graph_node,
const PhysicalPb& physical_pb,
const e_circuit_pb_port_type& pb_port_type,
t_mode* physical_mode) {
switch (pb_port_type) {
case CIRCUIT_PB_PORT_INPUT:
for (int iport = 0; iport < physical_pb_graph_node->num_input_ports; ++iport) {
for (int ipin = 0; ipin < physical_pb_graph_node->num_input_pins[iport]; ++ipin) {
build_physical_block_pin_interc_bitstream(bitstream_manager, parent_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
physical_pb,
&(physical_pb_graph_node->input_pins[iport][ipin]),
physical_mode);
}
}
break;
case CIRCUIT_PB_PORT_OUTPUT:
for (int iport = 0; iport < physical_pb_graph_node->num_output_ports; ++iport) {
for (int ipin = 0; ipin < physical_pb_graph_node->num_output_pins[iport]; ++ipin) {
build_physical_block_pin_interc_bitstream(bitstream_manager, parent_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
physical_pb,
&(physical_pb_graph_node->output_pins[iport][ipin]),
physical_mode);
}
}
break;
case CIRCUIT_PB_PORT_CLOCK:
for (int iport = 0; iport < physical_pb_graph_node->num_clock_ports; ++iport) {
for (int ipin = 0; ipin < physical_pb_graph_node->num_clock_pins[iport]; ++ipin) {
build_physical_block_pin_interc_bitstream(bitstream_manager, parent_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
physical_pb,
&(physical_pb_graph_node->clock_pins[iport][ipin]),
physical_mode);
}
}
break;
default:
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Invalid pb port type!\n");
exit(1);
}
}
/********************************************************************
* This function generates bitstream for the programmable routing
* multiplexers in a pb_graph node
*******************************************************************/
static
void build_physical_block_interc_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
t_pb_graph_node* physical_pb_graph_node,
const PhysicalPb& physical_pb,
t_mode* physical_mode) {
/* Check if the pb_graph node is valid or not */
if (nullptr == physical_pb_graph_node) {
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Invalid physical_pb_graph_node.\n");
exit(1);
}
/* We check output_pins of physical_pb_graph_node and its the input_edges
* Iterate over the interconnections between outputs of physical_pb_graph_node
* and outputs of child_pb_graph_node
* child_pb_graph_node.output_pins -----------------> physical_pb_graph_node.outpins
* /|\
* |
* input_pins, edges, output_pins
* Note: it is not applied to primitive pb_type!
*/
build_physical_block_interc_port_bitstream(bitstream_manager, parent_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
physical_pb_graph_node, physical_pb,
CIRCUIT_PB_PORT_OUTPUT, physical_mode);
/* We check input_pins of child_pb_graph_node and its the input_edges
* Iterate over the interconnections between inputs of physical_pb_graph_node
* and inputs of child_pb_graph_node
* physical_pb_graph_node.input_pins -----------------> child_pb_graph_node.input_pins
* /|\
* |
* input_pins, edges, output_pins
*/
for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ipb++) {
for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; jpb++) {
t_pb_graph_node* child_pb_graph_node = &(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]);
/* For each child_pb_graph_node input pins*/
build_physical_block_interc_port_bitstream(bitstream_manager, parent_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
child_pb_graph_node, physical_pb,
CIRCUIT_PB_PORT_INPUT, physical_mode);
/* For clock pins, we should do the same work */
build_physical_block_interc_port_bitstream(bitstream_manager, parent_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
child_pb_graph_node, physical_pb,
CIRCUIT_PB_PORT_CLOCK, physical_mode);
}
}
}
/********************************************************************
* Generate bitstream for a LUT and add it to bitstream manager
* This function supports both single-output and fracturable LUTs
*******************************************************************/
static
void build_lut_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_configurable_block,
const VprDeviceAnnotation& device_annotation,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const PhysicalPb& physical_pb,
const PhysicalPbId& lut_pb_id,
t_pb_type* lut_pb_type) {
/* Ensure a valid physical pritimive pb */
if (nullptr == lut_pb_type) {
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Invalid lut_pb_type!\n");
exit(1);
}
CircuitModelId lut_model = device_annotation.pb_type_circuit_model(lut_pb_type);
VTR_ASSERT(CircuitModelId::INVALID() != lut_model);
VTR_ASSERT(CIRCUIT_MODEL_LUT == circuit_lib.model_type(lut_model));
/* Find the input ports for LUT size, this is used to decode the LUT memory bits! */
std::vector<CircuitPortId> model_input_ports = circuit_lib.model_ports_by_type(lut_model, CIRCUIT_MODEL_PORT_INPUT, true);
VTR_ASSERT(1 == model_input_ports.size());
size_t lut_size = circuit_lib.port_size(model_input_ports[0]);
/* Find SRAM ports for truth tables and mode-selection */
std::vector<CircuitPortId> lut_regular_sram_ports = find_circuit_regular_sram_ports(circuit_lib, lut_model);
std::vector<CircuitPortId> lut_mode_select_ports = find_circuit_mode_select_sram_ports(circuit_lib, lut_model);
/* We should always 1 regular sram port, where truth table is loaded to */
VTR_ASSERT(1 == lut_regular_sram_ports.size());
/* We may have a port for mode select or not. This depends on if the LUT is fracturable or not */
VTR_ASSERT( (0 == lut_mode_select_ports.size())
|| (1 == lut_mode_select_ports.size()) );
std::vector<bool> lut_bitstream;
/* Generate bitstream for the LUT */
if (false == physical_pb.valid_pb_id(lut_pb_id)) {
/* An empty pb means that this is an unused LUT,
* we give an empty truth table, which are full of default values (defined by users)
*/
for (size_t i = 0; i < circuit_lib.port_size(lut_regular_sram_ports[0]); ++i) {
VTR_ASSERT( (0 == circuit_lib.port_default_value(lut_regular_sram_ports[0]))
|| (1 == circuit_lib.port_default_value(lut_regular_sram_ports[0])) );
lut_bitstream.push_back(1 == circuit_lib.port_default_value(lut_regular_sram_ports[0]));
}
} else {
VTR_ASSERT(true == physical_pb.valid_pb_id(lut_pb_id));
/* Find MUX graph correlated to the LUT */
MuxId lut_mux_id = mux_lib.mux_graph(lut_model, (size_t)pow(2., lut_size));
const MuxGraph& mux_graph = mux_lib.mux_graph(lut_mux_id);
/* Ensure the LUT MUX has the expected input and SRAM port sizes */
VTR_ASSERT(mux_graph.num_memory_bits() == lut_size);
VTR_ASSERT(mux_graph.num_inputs() == (size_t)pow(2., lut_size));
/* Generate LUT bitstream */
lut_bitstream = build_frac_lut_bitstream(circuit_lib, mux_graph,
device_annotation,
physical_pb.truth_tables(lut_pb_id),
circuit_lib.port_default_value(lut_regular_sram_ports[0]));
}
/* Generate bitstream for mode-select ports */
if (0 != lut_mode_select_ports.size()) {
std::vector<bool> mode_select_bitstream;
if (true == physical_pb.valid_pb_id(lut_pb_id)) {
mode_select_bitstream = generate_mode_select_bitstream(physical_pb.mode_bits(lut_pb_id));
} else { /* get default mode_bits */
mode_select_bitstream = generate_mode_select_bitstream(device_annotation.pb_type_mode_bits(lut_pb_type));
}
/* Conjunct the mode-select bitstream to the lut bitstream */
for (const bool& bit : mode_select_bitstream) {
lut_bitstream.push_back(bit);
}
}
/* Ensure the length of bitstream matches the side of memory circuits */
std::vector<CircuitModelId> sram_models = find_circuit_sram_models(circuit_lib, lut_model);
VTR_ASSERT(1 == sram_models.size());
std::string mem_block_name = generate_memory_module_name(circuit_lib, lut_model, sram_models[0], std::string(MEMORY_MODULE_POSTFIX));
ModuleId mem_module = module_manager.find_module(mem_block_name);
VTR_ASSERT (true == module_manager.valid_module_id(mem_module));
ModulePortId mem_out_port_id = module_manager.find_module_port(mem_module, generate_configuration_chain_data_out_name());
VTR_ASSERT(lut_bitstream.size() == module_manager.module_port(mem_module, mem_out_port_id).get_width());
/* Create a block for the bitstream which corresponds to the memory module associated to the LUT */
ConfigBlockId mem_block = bitstream_manager.add_block(mem_block_name);
bitstream_manager.add_child_block(parent_configurable_block, mem_block);
/* Add the bitstream to the bitstream manager */
for (const bool& bit : lut_bitstream) {
ConfigBitId config_bit = bitstream_manager.add_bit(bit);
/* Link the memory bits to the mux mem block */
bitstream_manager.add_bit_to_block(mem_block, config_bit);
}
}
/********************************************************************
* This function generates bitstream for a physical block, which is
* a child block of a grid
* This function will follow a recursive way in generating bitstreams
* It will follow the same sequence in visiting all the sub blocks
* in a physical as we did during module generation
*
* Note: if you want to bind your bitstream with a FPGA fabric generated by FPGA-X2P
* Please follow the same sequence in visiting pb_graph nodes!!!
* For more details, you may refer to function rec_build_physical_block_modules()
*******************************************************************/
static
void rec_build_physical_block_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& parent_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const e_side& border_side,
const PhysicalPb& physical_pb,
const PhysicalPbId& pb_id,
t_pb_graph_node* physical_pb_graph_node,
const size_t& pb_graph_node_index) {
/* Get the physical pb_type that is linked to the pb_graph node */
t_pb_type* physical_pb_type = physical_pb_graph_node->pb_type;
/* Find the mode that define_idle_mode*/
t_mode* physical_mode = device_annotation.physical_mode(physical_pb_type);
/* Create a block for the physical block under the grid block in bitstream manager */
std::string pb_block_name = generate_physical_block_instance_name(physical_pb_type, pb_graph_node_index);
ConfigBlockId pb_configurable_block = bitstream_manager.add_block(pb_block_name);
bitstream_manager.add_child_block(parent_configurable_block, pb_configurable_block);
/* Recursively finish all the child pb_types*/
if (false == is_primitive_pb_type(physical_pb_type)) {
for (int ipb = 0; ipb < physical_mode->num_pb_type_children; ++ipb) {
for (int jpb = 0; jpb < physical_mode->pb_type_children[ipb].num_pb; ++jpb) {
PhysicalPbId child_pb = PhysicalPbId::INVALID();
/* Find the child pb that is mapped, and the mapping info is not stored in the physical mode ! */
if (true == physical_pb.valid_pb_id(pb_id)) {
child_pb = physical_pb.child(pb_id, &(physical_mode->pb_type_children[ipb]), jpb);
VTR_ASSERT(true == physical_pb.valid_pb_id(child_pb));
}
/* Go recursively */
rec_build_physical_block_bitstream(bitstream_manager, pb_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
border_side,
physical_pb, child_pb,
&(physical_pb_graph_node->child_pb_graph_nodes[physical_mode->index][ipb][jpb]),
jpb);
}
}
}
/* Check if this has defined a circuit_model*/
if (true == is_primitive_pb_type(physical_pb_type)) {
CircuitModelId primitive_circuit_model = device_annotation.pb_type_circuit_model(physical_pb_type);
VTR_ASSERT(CircuitModelId::INVALID() != primitive_circuit_model);
switch (circuit_lib.model_type(primitive_circuit_model)) {
case CIRCUIT_MODEL_LUT:
/* Special case for LUT !!!
* Mapped logical block information is stored in child_pbs of this pb!!!
*/
build_lut_bitstream(bitstream_manager, pb_configurable_block,
device_annotation,
module_manager, circuit_lib, mux_lib,
physical_pb, pb_id, physical_pb_type);
break;
case CIRCUIT_MODEL_FF:
case CIRCUIT_MODEL_HARDLOGIC:
case CIRCUIT_MODEL_IOPAD:
/* For other types of blocks, we can apply a generic therapy */
build_primitive_bitstream(bitstream_manager, pb_configurable_block,
module_manager, circuit_lib, device_annotation,
physical_pb, pb_id, physical_pb_type);
break;
default:
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Unknown circuit model type of pb_type '%s'!\n",
physical_pb_type->name);
exit(1);
}
/* Finish for primitive node, return */
return;
}
/* Generate the bitstream for the interconnection in this physical block */
build_physical_block_interc_bitstream(bitstream_manager, pb_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation,
physical_pb_graph_node, physical_pb,
physical_mode);
}
/********************************************************************
* This function generates bitstream for a grid, which could be a
* CLB, a heterogenerous block, an I/O, etc.
* Note that each grid may contain a number of physical blocks,
* this function will iterate over them
*******************************************************************/
static
void build_physical_block_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& top_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprClusteringAnnotation& cluster_annotation,
const VprPlacementAnnotation& place_annotation,
const DeviceGrid& grids,
const vtr::Point<size_t>& grid_coord,
const e_side& border_side) {
/* Create a block for the grid in bitstream manager */
t_physical_tile_type_ptr grid_type = grids[grid_coord.x()][grid_coord.y()].type;
std::string grid_module_name_prefix(GRID_MODULE_NAME_PREFIX);
std::string grid_block_name = generate_grid_block_instance_name(grid_module_name_prefix, std::string(grid_type->name),
is_io_type(grid_type), border_side, grid_coord);
ConfigBlockId grid_configurable_block = bitstream_manager.add_block(grid_block_name);
bitstream_manager.add_child_block(top_block, grid_configurable_block);
/* Iterate over the capacity of the grid
* Now each physical tile may have a number of logical blocks
* OpenFPGA only considers the physical implementation of the tiles.
* So, we do not allow multiple equivalent sites to be defined
* under a physical tile type.
* If you need different equivalent sites, you can always define
* it as a mode under a <pb_type>
*/
for (size_t z = 0; z < place_annotation.grid_blocks(grid_coord).size(); ++z) {
VTR_ASSERT(1 == grid_type->equivalent_sites.size());
for (t_logical_block_type_ptr lb_type : grid_type->equivalent_sites) {
/* Bypass empty pb_graph */
if (nullptr == lb_type->pb_graph_head) {
continue;
}
if (ClusterBlockId::INVALID() == place_annotation.grid_blocks(grid_coord)[z]) {
/* Recursively traverse the pb_graph and generate bitstream */
rec_build_physical_block_bitstream(bitstream_manager, grid_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation, border_side,
PhysicalPb(), PhysicalPbId::INVALID(),
lb_type->pb_graph_head, z);
} else {
const PhysicalPb& phy_pb = cluster_annotation.physical_pb(place_annotation.grid_blocks(grid_coord)[z]);
/* Get the top-level node of the pb_graph */
t_pb_graph_node* pb_graph_head = lb_type->pb_graph_head;
VTR_ASSERT(nullptr != pb_graph_head);
const PhysicalPbId& top_pb_id = phy_pb.find_pb(pb_graph_head);
/* Recursively traverse the pb_graph and generate bitstream */
rec_build_physical_block_bitstream(bitstream_manager, grid_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation, border_side,
phy_pb, top_pb_id, pb_graph_head, z);
}
}
}
}
/********************************************************************
* Top-level function of this file:
* Generate bitstreams for all the grids, including
* 1. core grids that sit in the center of the fabric
* 2. side grids (I/O grids) that sit in the borders for the fabric
*******************************************************************/
void build_grid_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& top_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const DeviceGrid& grids,
const VprDeviceAnnotation& device_annotation,
const VprClusteringAnnotation& cluster_annotation,
const VprPlacementAnnotation& place_annotation,
const bool& verbose) {
VTR_LOGV(verbose, "Generating bitstream for core grids...");
/* Generate bitstream for the core logic block one by one */
for (size_t ix = 1; ix < grids.width() - 1; ++ix) {
for (size_t iy = 1; iy < grids.height() - 1; ++iy) {
/* Bypass EMPTY grid */
if (true == is_empty_type(grids[ix][iy].type)) {
continue;
}
/* Skip width > 1 or height > 1 tiles (mostly heterogeneous blocks) */
if ( (0 < grids[ix][iy].width_offset)
|| (0 < grids[ix][iy].height_offset) ) {
continue;
}
/* We should not meet any I/O grid */
VTR_ASSERT(true != is_io_type(grids[ix][iy].type));
/* Add a grid module to top_module*/
vtr::Point<size_t> grid_coord(ix, iy);
build_physical_block_bitstream(bitstream_manager, top_block, module_manager,
circuit_lib, mux_lib,
device_annotation, cluster_annotation,
place_annotation,
grids, grid_coord, NUM_SIDES);
}
}
VTR_LOGV(verbose, "Done\n");
VTR_LOGV(verbose, "Generating bitstream for I/O grids...");
/* Create the coordinate range for each side of FPGA fabric */
std::vector<e_side> io_sides{TOP, RIGHT, BOTTOM, LEFT};
std::map<e_side, std::vector<vtr::Point<size_t>>> io_coordinates;
/* TOP side*/
for (size_t ix = 1; ix < grids.width() - 1; ++ix) {
io_coordinates[TOP].push_back(vtr::Point<size_t>(ix, grids.height() - 1));
}
/* RIGHT side */
for (size_t iy = 1; iy < grids.height() - 1; ++iy) {
io_coordinates[RIGHT].push_back(vtr::Point<size_t>(grids.width() - 1, iy));
}
/* BOTTOM side*/
for (size_t ix = 1; ix < grids.width() - 1; ++ix) {
io_coordinates[BOTTOM].push_back(vtr::Point<size_t>(ix, 0));
}
/* LEFT side */
for (size_t iy = 1; iy < grids.height() - 1; ++iy) {
io_coordinates[LEFT].push_back(vtr::Point<size_t>(0, iy));
}
/* Add instances of I/O grids to top_module */
for (const e_side& io_side : io_sides) {
for (const vtr::Point<size_t>& io_coordinate : io_coordinates[io_side]) {
/* Bypass EMPTY grid */
if (true == is_empty_type(grids[io_coordinate.x()][io_coordinate.y()].type)) {
continue;
}
/* Skip height > 1 tiles (mostly heterogeneous blocks) */
if ( (0 < grids[io_coordinate.x()][io_coordinate.y()].width_offset)
|| (0 < grids[io_coordinate.x()][io_coordinate.y()].height_offset) ) {
continue;
}
/* We should not meet any I/O grid */
VTR_ASSERT(true == is_io_type(grids[io_coordinate.x()][io_coordinate.y()].type));
build_physical_block_bitstream(bitstream_manager, top_block, module_manager,
circuit_lib, mux_lib,
device_annotation, cluster_annotation,
place_annotation,
grids, io_coordinate, io_side);
}
}
VTR_LOGV(verbose, "Done\n");
}
} /* end namespace openfpga */

View File

@ -0,0 +1,37 @@
#ifndef BUILD_GRID_BITSTREAM_H
#define BUILD_GRID_BITSTREAM_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <vector>
#include "device_grid.h"
#include "bitstream_manager.h"
#include "module_manager.h"
#include "circuit_library.h"
#include "mux_library.h"
#include "vpr_device_annotation.h"
#include "vpr_clustering_annotation.h"
#include "vpr_placement_annotation.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
void build_grid_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& top_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const DeviceGrid& grids,
const VprDeviceAnnotation& device_annotation,
const VprClusteringAnnotation& cluster_annotation,
const VprPlacementAnnotation& place_annotation,
const bool& verbose);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,173 @@
/********************************************************************
* This file includes functions to build bitstream from routing multiplexers
* which are based on different technology
*******************************************************************/
/* Headers from vtrutil library */
#include "vtr_log.h"
#include "vtr_assert.h"
#include "vtr_vector.h"
/* Headers from openfpgautil library */
#include "openfpga_decode.h"
#include "mux_utils.h"
#include "decoder_library_utils.h"
#include "mux_bitstream_constants.h"
#include "build_mux_bitstream.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* Find the default path id of a MUX
* This is applied when the path id specified is DEFAULT_PATH_ID,
* which is not correlated to the MUX implementation
* This function is binding the default path id to the implemented structure
* 1. If the MUX has a constant input, the default path id will be
* directed to the last input of the MUX, which is the constant input
* 2. If the MUX does not have a constant input, the default path id
* will the first input of the MUX.
*
* Restriction:
* we assume the default path is the first input of the MUX
* Change if this is not what you want
*******************************************************************/
size_t find_mux_default_path_id(const CircuitLibrary& circuit_lib,
const CircuitModelId& mux_model,
const size_t& mux_size) {
size_t default_path_id;
if (true == circuit_lib.mux_add_const_input(mux_model)) {
default_path_id = mux_size - 1; /* When there is a constant input, use the last path */
} else {
default_path_id = DEFAULT_MUX_PATH_ID; /* When there is no constant input, use the default one */
}
return default_path_id;
}
/********************************************************************
* This function generates bitstream for a CMOS routing multiplexer
* Thanks to MuxGraph object has already describe the internal multiplexing
* structure, bitstream generation is simply done by routing the signal
* to from a given input to the output
* All the memory bits can be generated by an API of MuxGraph
*
* To be generic, this function only returns a vector bit values
* without touching an bitstream-relate data structure
*******************************************************************/
static
std::vector<bool> build_cmos_mux_bitstream(const CircuitLibrary& circuit_lib,
const CircuitModelId& mux_model,
const MuxLibrary& mux_lib,
const size_t& mux_size,
const int& path_id) {
/* Note that the size of implemented mux could be different than the mux size we see here,
* due to the constant inputs
* We will find the input size of implemented MUX and fetch the graph-based representation in MUX library
*/
size_t implemented_mux_size = find_mux_implementation_num_inputs(circuit_lib, mux_model, mux_size);
/* Note that the mux graph is indexed using datapath MUX size!!!! */
MuxId mux_graph_id = mux_lib.mux_graph(mux_model, mux_size);
const MuxGraph mux_graph = mux_lib.mux_graph(mux_graph_id);
size_t datapath_id = path_id;
/* Find the path_id related to the implementation */
if (DEFAULT_PATH_ID == path_id) {
datapath_id = find_mux_default_path_id(circuit_lib, mux_model, implemented_mux_size);
} else {
VTR_ASSERT( datapath_id < mux_size);
}
/* Path id should makes sense */
VTR_ASSERT(datapath_id < mux_graph.inputs().size());
/* We should have only one output for this MUX! */
VTR_ASSERT(1 == mux_graph.outputs().size());
/* Generate the memory bits */
vtr::vector<MuxMemId, bool> raw_bitstream = mux_graph.decode_memory_bits(MuxInputId(datapath_id), mux_graph.output_id(mux_graph.outputs()[0]));
std::vector<bool> mux_bitstream;
for (const bool& bit : raw_bitstream) {
mux_bitstream.push_back(bit);
}
/* Consider local encoder support, we need further encode the bitstream */
if (false == circuit_lib.mux_use_local_encoder(mux_model)) {
return mux_bitstream;
}
/* Clear the mux_bitstream, we need to apply encoding */
mux_bitstream.clear();
/* Encode the memory bits level by level,
* One local encoder is used for each level of multiplexers
*/
for (const size_t& level : mux_graph.levels()) {
/* The encoder will convert the path_id to a binary number
* For example: when path_id=3 (use the 4th input), using a 2-input encoder
* the sram_bits will be the 2-digit binary number of 3: 10
*/
std::vector<size_t> encoder_data;
/* Exception: there is only 1 memory at this level, bitstream will not be changed!!! */
if (1 == mux_graph.memories_at_level(level).size()) {
mux_bitstream.push_back(raw_bitstream[mux_graph.memories_at_level(level)[0]]);
continue;
}
/* Otherwise: we follow a regular recipe */
for (size_t mem_index = 0; mem_index < mux_graph.memories_at_level(level).size(); ++mem_index) {
/* Conversion rule: true = 1, false = 0 */
if (true == raw_bitstream[mux_graph.memories_at_level(level)[mem_index]]) {
encoder_data.push_back(mem_index);
}
}
/* There should be at most one '1' */
VTR_ASSERT( (0 == encoder_data.size()) || (1 == encoder_data.size()));
/* Convert to encoded bits */
std::vector<size_t> encoder_addr;
if (0 == encoder_data.size()) {
encoder_addr = itobin_vec(0, find_mux_local_decoder_addr_size(mux_graph.memories_at_level(level).size()));
} else {
VTR_ASSERT(1 == encoder_data.size());
encoder_addr = itobin_vec(encoder_data[0], find_mux_local_decoder_addr_size(mux_graph.memories_at_level(level).size()));
}
/* Build final mux bitstream */
for (const size_t& bit : encoder_addr) {
mux_bitstream.push_back(1 == bit);
}
}
return mux_bitstream;
}
/********************************************************************
* This function generates bitstream for a routing multiplexer
* supporting both CMOS and ReRAM multiplexer designs
*******************************************************************/
std::vector<bool> build_mux_bitstream(const CircuitLibrary& circuit_lib,
const CircuitModelId& mux_model,
const MuxLibrary& mux_lib,
const size_t& mux_size,
const int& path_id) {
std::vector<bool> mux_bitstream;
switch (circuit_lib.design_tech_type(mux_model)) {
case CIRCUIT_MODEL_DESIGN_CMOS:
mux_bitstream = build_cmos_mux_bitstream(circuit_lib, mux_model, mux_lib, mux_size, path_id);
break;
case CIRCUIT_MODEL_DESIGN_RRAM:
/* TODO: ReRAM MUX needs a different bitstream generation strategy */
break;
default:
VTR_LOGF_ERROR(__FILE__, __LINE__,
"Invalid design technology for circuit model '%s'!\n",
circuit_lib.model_name(mux_model).c_str());
exit(1);
}
return mux_bitstream;
}
} /* end namespace openfpga */

View File

@ -0,0 +1,30 @@
#ifndef BUILD_MUX_BITSTREAM_H
#define BUILD_MUX_BITSTREAM_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <vector>
#include "circuit_library.h"
#include "mux_library.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
size_t find_mux_default_path_id(const CircuitLibrary& circuit_lib,
const CircuitModelId& mux_model,
const size_t& mux_size);
std::vector<bool> build_mux_bitstream(const CircuitLibrary& circuit_lib,
const CircuitModelId& mux_model,
const MuxLibrary& mux_lib,
const size_t& mux_size,
const int& path_id);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,442 @@
/********************************************************************
* This file includes functions to build bitstream from global routing
* architecture of a mapped FPGA fabric
* We decode the bitstream from configuration of routing multiplexers
* which locate in global routing architecture
*******************************************************************/
#include <vector>
/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "vtr_log.h"
/* Headers from openfpgautil library */
#include "openfpga_side_manager.h"
#include "mux_utils.h"
#include "rr_gsb_utils.h"
#include "openfpga_reserved_words.h"
#include "openfpga_naming.h"
#include "openfpga_rr_graph_utils.h"
#include "mux_bitstream_constants.h"
#include "build_mux_bitstream.h"
#include "build_routing_bitstream.h"
/* begin namespace openfpga */
namespace openfpga {
/********************************************************************
* This function generates bitstream for a routing multiplexer
* This function will identify if a node indicates a routing multiplexer
* If not a routing multiplexer, no bitstream is needed here
* If yes, we will generate the bitstream for the routing multiplexer
*******************************************************************/
static
void build_switch_block_mux_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& mux_mem_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const RRGraph& rr_graph,
const RRNodeId& cur_rr_node,
const std::vector<RRNodeId>& drive_rr_nodes,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation) {
/* Check current rr_node is CHANX or CHANY*/
VTR_ASSERT( (CHANX == rr_graph.node_type(cur_rr_node))
|| (CHANY == rr_graph.node_type(cur_rr_node)));
/* Find the input size of the implementation of a routing multiplexer */
size_t datapath_mux_size = drive_rr_nodes.size();
/* Find out which routing path is used in this MUX */
int path_id = DEFAULT_PATH_ID;
for (size_t inode = 0; inode < drive_rr_nodes.size(); ++inode) {
if (routing_annotation.rr_node_net(drive_rr_nodes[inode]) == routing_annotation.rr_node_net(cur_rr_node)) {
path_id = (int)inode;
break;
}
}
/* Ensure that our path id makes sense! */
VTR_ASSERT( (DEFAULT_PATH_ID == path_id)
|| ( (DEFAULT_PATH_ID < path_id) && (path_id < (int)datapath_mux_size) )
);
/* Find the circuit model id of the mux, we need its design technology which matters the bitstream generation */
std::vector<RRSwitchId> driver_switches = get_rr_graph_driver_switches(rr_graph, cur_rr_node);
VTR_ASSERT(1 == driver_switches.size());
CircuitModelId mux_model = device_annotation.rr_switch_circuit_model(driver_switches[0]);
/* Generate bitstream depend on both technology and structure of this MUX */
std::vector<bool> mux_bitstream = build_mux_bitstream(circuit_lib, mux_model, mux_lib, datapath_mux_size, path_id);
/* Find the module in module manager and ensure the bitstream size matches! */
std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX));
ModuleId mux_mem_module = module_manager.find_module(mem_module_name);
VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module));
ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name());
VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width());
/* Add the bistream to the bitstream manager */
for (const bool& bit : mux_bitstream) {
ConfigBitId config_bit = bitstream_manager.add_bit(bit);
/* Link the memory bits to the mux mem block */
bitstream_manager.add_bit_to_block(mux_mem_block, config_bit);
}
}
/********************************************************************
* This function generates bitstream for an interconnection,
* i.e., a routing multiplexer, in a Switch Block
* This function will identify if a node indicates a routing multiplexer
* If not a routing multiplexer, no bitstream is needed here
* If yes, we will generate the bitstream for the routing multiplexer
*******************************************************************/
static
void build_switch_block_interc_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& sb_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const RRGraph& rr_graph,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGSB& rr_gsb,
const e_side& chan_side,
const size_t& chan_node_id) {
std::vector<RRNodeId> driver_rr_nodes;
/* Get the node */
const RRNodeId& cur_rr_node = rr_gsb.get_chan_node(chan_side, chan_node_id);
/* Determine if the interc lies inside a channel wire, that is interc between segments */
if (false == rr_gsb.is_sb_node_passing_wire(rr_graph, chan_side, chan_node_id)) {
driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, cur_rr_node);
/* Special: if there are zero-driver nodes. We skip here */
if (0 == driver_rr_nodes.size()) {
return;
}
}
if ( (0 == driver_rr_nodes.size())
|| (0 == driver_rr_nodes.size()) ) {
/* No bitstream generation required by a special direct connection*/
return;
} else if (1 < driver_rr_nodes.size()) {
/* Create the block denoting the memory instances that drives this node in Switch Block */
std::string mem_block_name = generate_sb_memory_instance_name(SWITCH_BLOCK_MEM_INSTANCE_PREFIX, chan_side, chan_node_id, std::string(""));
ConfigBlockId mux_mem_block = bitstream_manager.add_block(mem_block_name);
bitstream_manager.add_child_block(sb_configurable_block, mux_mem_block);
/* This is a routing multiplexer! Generate bitstream */
build_switch_block_mux_bitstream(bitstream_manager, mux_mem_block, module_manager,
circuit_lib, mux_lib, rr_graph,
cur_rr_node, driver_rr_nodes,
device_annotation, routing_annotation);
} /*Nothing should be done else*/
}
/********************************************************************
* This function generates bitstream for a Switch Block
* and add it to the bitstream manager
* This function will spot all the routing multiplexers in a Switch Block
* using a simple but effective rule:
* The fan-in of each output node.
* If there are more than 2 fan-in, there is a routing multiplexer
*
* Note that the output nodes typically spread over all the sides of a Switch Block
* So, we will iterate over that.
*******************************************************************/
static
void build_switch_block_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& sb_config_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const RRGSB& rr_gsb) {
/* Iterate over all the multiplexers */
for (size_t side = 0; side < rr_gsb.get_num_sides(); ++side) {
SideManager side_manager(side);
for (size_t itrack = 0; itrack < rr_gsb.get_chan_width(side_manager.get_side()); ++itrack) {
VTR_ASSERT( (CHANX == rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack)))
|| (CHANY == rr_graph.node_type(rr_gsb.get_chan_node(side_manager.get_side(), itrack))) );
/* Only output port indicates a routing multiplexer */
if (OUT_PORT != rr_gsb.get_chan_node_direction(side_manager.get_side(), itrack)) {
continue;
}
build_switch_block_interc_bitstream(bitstream_manager, sb_config_block,
module_manager,
circuit_lib, mux_lib, rr_graph,
device_annotation, routing_annotation,
rr_gsb, side_manager.get_side(), itrack);
}
}
}
/********************************************************************
* This function generates bitstream for a routing multiplexer
* in a Connection block
* This function will identify if a node indicates a routing multiplexer
* If not a routing multiplexer, no bitstream is needed here
* If yes, we will generate the bitstream for the routing multiplexer
*******************************************************************/
static
void build_connection_block_mux_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& mux_mem_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const RRNodeId& src_rr_node) {
/* Find drive_rr_nodes*/
size_t datapath_mux_size = rr_graph.node_fan_in(src_rr_node);
/* Configuration bits for MUX*/
int path_id = DEFAULT_PATH_ID;
int edge_index = 0;
for (const RREdgeId& edge : rr_graph.node_in_edges(src_rr_node)) {
RRNodeId driver_node = rr_graph.edge_src_node(edge);
if (routing_annotation.rr_node_net(driver_node) == routing_annotation.rr_node_net(src_rr_node)) {
path_id = edge_index;
break;
}
edge_index++;
}
/* Ensure that our path id makes sense! */
VTR_ASSERT( (DEFAULT_PATH_ID == path_id)
|| ( (DEFAULT_PATH_ID < path_id) && (path_id < (int)datapath_mux_size) )
);
/* Find the circuit model id of the mux, we need its design technology which matters the bitstream generation */
std::vector<RRSwitchId> driver_switches = get_rr_graph_driver_switches(rr_graph, src_rr_node);
VTR_ASSERT(1 == driver_switches.size());
CircuitModelId mux_model = device_annotation.rr_switch_circuit_model(driver_switches[0]);
/* Generate bitstream depend on both technology and structure of this MUX */
std::vector<bool> mux_bitstream = build_mux_bitstream(circuit_lib, mux_model, mux_lib, datapath_mux_size, path_id);
/* Find the module in module manager and ensure the bitstream size matches! */
std::string mem_module_name = generate_mux_subckt_name(circuit_lib, mux_model, datapath_mux_size, std::string(MEMORY_MODULE_POSTFIX));
ModuleId mux_mem_module = module_manager.find_module(mem_module_name);
VTR_ASSERT (true == module_manager.valid_module_id(mux_mem_module));
ModulePortId mux_mem_out_port_id = module_manager.find_module_port(mux_mem_module, generate_configuration_chain_data_out_name());
VTR_ASSERT(mux_bitstream.size() == module_manager.module_port(mux_mem_module, mux_mem_out_port_id).get_width());
/* Add the bistream to the bitstream manager */
for (const bool& bit : mux_bitstream) {
ConfigBitId config_bit = bitstream_manager.add_bit(bit);
/* Link the memory bits to the mux mem block */
bitstream_manager.add_bit_to_block(mux_mem_block, config_bit);
}
}
/********************************************************************
* This function generates bitstream for an interconnection,
* i.e., a routing multiplexer, in a Connection Block
* This function will identify if a node indicates a routing multiplexer
* If not a routing multiplexer, no bitstream is needed here
* If yes, we will generate the bitstream for the routing multiplexer
*******************************************************************/
static
void build_connection_block_interc_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& cb_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const RRGSB& rr_gsb,
const e_side& cb_ipin_side,
const size_t& ipin_index) {
RRNodeId src_rr_node = rr_gsb.get_ipin_node(cb_ipin_side, ipin_index);
/* Consider configurable edges only */
std::vector<RRNodeId> driver_rr_nodes = get_rr_graph_configurable_driver_nodes(rr_graph, src_rr_node);
if (1 == driver_rr_nodes.size()) {
/* No bitstream generation required by a special direct connection*/
} else if (1 < driver_rr_nodes.size()) {
/* Create the block denoting the memory instances that drives this node in Switch Block */
std::string mem_block_name = generate_cb_memory_instance_name(CONNECTION_BLOCK_MEM_INSTANCE_PREFIX, rr_graph.node_side(src_rr_node), ipin_index, std::string(""));
ConfigBlockId mux_mem_block = bitstream_manager.add_block(mem_block_name);
bitstream_manager.add_child_block(cb_configurable_block, mux_mem_block);
/* This is a routing multiplexer! Generate bitstream */
build_connection_block_mux_bitstream(bitstream_manager, mux_mem_block,
module_manager, circuit_lib, mux_lib,
device_annotation, routing_annotation,
rr_graph, src_rr_node);
} /*Nothing should be done else*/
}
/********************************************************************
* This function generates bitstream for a Connection Block
* and add it to the bitstream manager
* This function will spot all the routing multiplexers in a Connection Block
* using a simple but effective rule:
* The fan-in of each output node.
* If there are more than 2 fan-in, there is a routing multiplexer
*
* Note that the output nodes are the IPIN rr node in a Connection Block
* So, we will iterate over that.
*******************************************************************/
static
void build_connection_block_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& cb_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const RRGSB& rr_gsb,
const t_rr_type& cb_type) {
/* Find routing multiplexers on the sides of a Connection block where IPIN nodes locate */
std::vector<enum e_side> cb_sides = rr_gsb.get_cb_ipin_sides(cb_type);
for (size_t side = 0; side < cb_sides.size(); ++side) {
enum e_side cb_ipin_side = cb_sides[side];
SideManager side_manager(cb_ipin_side);
for (size_t inode = 0; inode < rr_gsb.get_num_ipin_nodes(cb_ipin_side); ++inode) {
build_connection_block_interc_bitstream(bitstream_manager, cb_configurable_block,
module_manager, circuit_lib, mux_lib,
device_annotation, routing_annotation,
rr_graph, rr_gsb,
cb_ipin_side, inode);
}
}
}
/********************************************************************
* Create bitstream for a X-direction or Y-direction Connection Blocks
*******************************************************************/
static
void build_connection_block_bitstreams(BitstreamManager& bitstream_manager,
const ConfigBlockId& top_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const DeviceRRGSB& device_rr_gsb,
const t_rr_type& cb_type) {
vtr::Point<size_t> cb_range = device_rr_gsb.get_gsb_range();
for (size_t ix = 0; ix < cb_range.x(); ++ix) {
for (size_t iy = 0; iy < cb_range.y(); ++iy) {
const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy);
/* Check if the connection block exists in the device!
* Some of them do NOT exist due to heterogeneous blocks (height > 1)
* We will skip those modules
*/
if (false == rr_gsb.is_cb_exist(cb_type)) {
continue;
}
/* Skip if the cb does not contain any configuration bits! */
if (true == connection_block_contain_only_routing_tracks(rr_gsb, cb_type)) {
continue;
}
/* Create a block for the bitstream which corresponds to the Switch block */
vtr::Point<size_t> cb_coord(rr_gsb.get_cb_x(cb_type), rr_gsb.get_cb_y(cb_type));
ConfigBlockId cb_configurable_block = bitstream_manager.add_block(generate_connection_block_module_name(cb_type, cb_coord));
/* Set switch block as a child of top block */
bitstream_manager.add_child_block(top_configurable_block, cb_configurable_block);
build_connection_block_bitstream(bitstream_manager, cb_configurable_block, module_manager,
circuit_lib, mux_lib,
device_annotation, routing_annotation,
rr_graph,
rr_gsb, cb_type);
}
}
}
/********************************************************************
* Top-level function to create bitstream for global routing architecture
* Two major tasks:
* 1. Generate bitstreams for Switch Blocks
* 2. Generate bitstreams for both X-direction and Y-direction Connection Blocks
*******************************************************************/
void build_routing_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& top_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const DeviceRRGSB& device_rr_gsb) {
/* Generate bitstream for each switch blocks
* To organize the bitstream in blocks, we create a block for each switch block
* and give names which are same as they are in top-level module managers
*/
VTR_LOG("Generating bitstream for Switch blocks...");
vtr::Point<size_t> sb_range = device_rr_gsb.get_gsb_range();
for (size_t ix = 0; ix < sb_range.x(); ++ix) {
for (size_t iy = 0; iy < sb_range.y(); ++iy) {
const RRGSB& rr_gsb = device_rr_gsb.get_gsb(ix, iy);
/* Check if the switch block exists in the device!
* Some of them do NOT exist due to heterogeneous blocks (width > 1)
* We will skip those modules
*/
if (false == rr_gsb.is_sb_exist()) {
continue;
}
/* Create a block for the bitstream which corresponds to the Switch block */
vtr::Point<size_t> sb_coord(rr_gsb.get_sb_x(), rr_gsb.get_sb_y());
ConfigBlockId sb_configurable_block = bitstream_manager.add_block(generate_switch_block_module_name(sb_coord));
/* Set switch block as a child of top block */
bitstream_manager.add_child_block(top_configurable_block, sb_configurable_block);
build_switch_block_bitstream(bitstream_manager, sb_configurable_block, module_manager,
circuit_lib, mux_lib,
device_annotation, routing_annotation,
rr_graph,
rr_gsb);
}
}
VTR_LOG("Done\n");
/* Generate bitstream for each connection blocks
* To organize the bitstream in blocks, we create a block for each connection block
* and give names which are same as they are in top-level module managers
*/
VTR_LOG("Generating bitstream for X-direction Connection blocks ...");
build_connection_block_bitstreams(bitstream_manager, top_configurable_block, module_manager,
circuit_lib, mux_lib,
device_annotation, routing_annotation,
rr_graph,
device_rr_gsb, CHANX);
VTR_LOG("Done\n");
VTR_LOG("Generating bitstream for Y-direction Connection blocks ...");
build_connection_block_bitstreams(bitstream_manager, top_configurable_block, module_manager,
circuit_lib, mux_lib,
device_annotation, routing_annotation,
rr_graph,
device_rr_gsb, CHANY);
VTR_LOG("Done\n");
}
} /* end namespace openfpga */

View File

@ -0,0 +1,39 @@
/********************************************************************
* Header file for build_routing_bitstream.cpp
*******************************************************************/
#ifndef BUILD_ROUTING_BITSTREAM_H
#define BUILD_ROUTING_BITSTREAM_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include <vector>
#include "bitstream_manager.h"
#include "vpr_context.h"
#include "module_manager.h"
#include "circuit_library.h"
#include "mux_library.h"
#include "device_rr_gsb.h"
#include "vpr_device_annotation.h"
#include "vpr_routing_annotation.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
void build_routing_bitstream(BitstreamManager& bitstream_manager,
const ConfigBlockId& top_configurable_block,
const ModuleManager& module_manager,
const CircuitLibrary& circuit_lib,
const MuxLibrary& mux_lib,
const VprDeviceAnnotation& device_annotation,
const VprRoutingAnnotation& routing_annotation,
const RRGraph& rr_graph,
const DeviceRRGSB& device_rr_gsb);
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,15 @@
#ifndef MUX_BITSTREAM_CONSTANTS_H
#define MUX_BITSTREAM_CONSTANTS_H
/* begin namespace openfpga */
namespace openfpga {
/* Default path ID of a unused multiplexer */
#define DEFAULT_PATH_ID -1
/* Default path ID of a unused multiplexer when there are no constant inputs*/
#define DEFAULT_MUX_PATH_ID 0
} /* end namespace openfpga */
#endif

View File

@ -0,0 +1,169 @@
/***************************************************************************************
* This file includes functions that are used to build truth tables of
* the physical implementation of LUTs
***************************************************************************************/
/* Headers from vtrutil library */
#include "vtr_log.h"
#include "vtr_assert.h"
#include "vtr_time.h"
#include "openfpga_naming.h"
#include "lut_utils.h"
#include "physical_pb.h"
#include "build_physical_truth_table.h"
/* begin namespace openfpga */
namespace openfpga {
/***************************************************************************************
* 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
***************************************************************************************/
static
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;
}
/***************************************************************************************
* Create pin rotation map for a LUT
***************************************************************************************/
static
std::vector<int> generate_lut_rotated_input_pin_map(const std::vector<AtomNetId>& input_nets,
const AtomContext& atom_ctx,
const AtomBlockId& atom_blk,
const t_pb_graph_node* pb_graph_node) {
/* Find the pin rotation status and record it ,
* Note that some LUT inputs may not be used, we set them to be open by default
*/
std::vector<int> rotated_pin_map(input_nets.size(), -1);
VTR_ASSERT(1 == pb_graph_node->num_input_ports);
for (int ipin = 0; ipin < pb_graph_node->num_input_pins[0]; ++ipin) {
/* Port exists (some LUTs may have no input and hence no port in the atom netlist) */
AtomPortId atom_port = atom_ctx.nlist.find_atom_port(atom_blk, pb_graph_node->input_pins[0][ipin].port->model_port);
if (!atom_port) {
continue;
}
for (AtomPinId atom_pin : atom_ctx.nlist.port_pins(atom_port)) {
AtomNetId atom_pin_net = atom_ctx.nlist.pin_net(atom_pin);
if (atom_pin_net == input_nets[ipin]) {
rotated_pin_map[ipin] = atom_ctx.nlist.pin_port_bit(atom_pin);
break;
}
}
}
return rotated_pin_map;
}
/***************************************************************************************
* This function will iterate over all the inputs and outputs of the LUT pb
* and find truth tables that are mapped to each output pins
* Note that a physical LUT may have multiple truth tables to be considered
* as they may be fracturable
***************************************************************************************/
static
void build_physical_pb_lut_truth_tables(PhysicalPb& physical_pb,
const PhysicalPbId& lut_pb_id,
const AtomContext& atom_ctx,
const VprDeviceAnnotation& device_annotation,
const CircuitLibrary& circuit_lib) {
const t_pb_graph_node* pb_graph_node = physical_pb.pb_graph_node(lut_pb_id);
CircuitModelId lut_model = device_annotation.pb_type_circuit_model(physical_pb.pb_graph_node(lut_pb_id)->pb_type);
VTR_ASSERT(CIRCUIT_MODEL_LUT == circuit_lib.model_type(lut_model));
/* Find all the nets mapped to each inputs */
std::vector<AtomNetId> input_nets;
VTR_ASSERT(1 == pb_graph_node->num_input_ports);
for (int ipin = 0; ipin < pb_graph_node->num_input_pins[0]; ++ipin) {
input_nets.push_back(physical_pb.pb_graph_pin_atom_net(lut_pb_id, &(pb_graph_node->input_pins[0][ipin])));
}
/* Find all the nets mapped to each outputs */
for (int iport = 0; iport < pb_graph_node->num_output_ports; ++iport) {
for (int ipin = 0; ipin < pb_graph_node->num_output_pins[iport]; ++ipin) {
const t_pb_graph_pin* output_pin = &(pb_graph_node->output_pins[iport][ipin]);
AtomNetId output_net = physical_pb.pb_graph_pin_atom_net(lut_pb_id, output_pin);
/* Bypass unmapped pins */
if (AtomNetId::INVALID() == output_net) {
continue;
}
/* Check if this is a LUT used as wiring */
if (true == is_wired_lut(input_nets, output_net)) {
AtomNetlist::TruthTable wire_tt = build_wired_lut_truth_table(input_nets.size(), std::find(input_nets.begin(), input_nets.end(), output_net) - input_nets.begin());
physical_pb.set_truth_table(lut_pb_id, output_pin, wire_tt);
continue;
}
/* Find the truth table from atom block which drives the atom net */
const AtomBlockId& atom_blk = atom_ctx.nlist.net_driver_block(output_net);
VTR_ASSERT(true == atom_ctx.nlist.valid_block_id(atom_blk));
const AtomNetlist::TruthTable& orig_tt = atom_ctx.nlist.block_truth_table(atom_blk);
std::vector<int> rotated_pin_map = generate_lut_rotated_input_pin_map(input_nets, atom_ctx, atom_blk, pb_graph_node);
const AtomNetlist::TruthTable& adapt_tt = lut_truth_table_adaption(orig_tt, rotated_pin_map);
/* Adapt the truth table for fracturable lut implementation and add to physical pb */
CircuitPortId lut_model_output_port = device_annotation.pb_circuit_port(output_pin->port);
size_t lut_frac_level = circuit_lib.port_lut_frac_level(lut_model_output_port);
if (size_t(-1) == lut_frac_level) {
lut_frac_level = input_nets.size();
}
size_t lut_output_mask = circuit_lib.port_lut_output_mask(lut_model_output_port)[output_pin->pin_number];
const AtomNetlist::TruthTable& frac_lut_tt = adapt_truth_table_for_frac_lut(lut_frac_level, lut_output_mask, adapt_tt);
physical_pb.set_truth_table(lut_pb_id, output_pin, frac_lut_tt);
}
}
}
/***************************************************************************************
* This function will iterate over all the physical pb that are
* binded to clustered blocks and build the truth tables for the
* physical Look-Up Table (LUT) implementations.
* Note that the truth table built here is different from the atom
* netlists in VPR context. We consider fracturable LUT features
* and LUTs operating as wires
***************************************************************************************/
void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation,
const AtomContext& atom_ctx,
const ClusteringContext& cluster_ctx,
const VprDeviceAnnotation& device_annotation,
const CircuitLibrary& circuit_lib) {
vtr::ScopedStartFinishTimer timer("Build truth tables for physical LUTs");
for (auto blk_id : cluster_ctx.clb_nlist.blocks()) {
PhysicalPb& physical_pb = cluster_annotation.mutable_physical_pb(blk_id);
/* Find the LUT physical pb id */
for (const PhysicalPbId& primitive_pb : physical_pb.primitive_pbs()) {
CircuitModelId circuit_model = device_annotation.pb_type_circuit_model(physical_pb.pb_graph_node(primitive_pb)->pb_type);
VTR_ASSERT(true == circuit_lib.valid_model_id(circuit_model));
if (CIRCUIT_MODEL_LUT != circuit_lib.model_type(circuit_model)) {
continue;
}
/* Reach here, we have a LUT to deal with. Find the truth tables that mapped to the LUT */
build_physical_pb_lut_truth_tables(physical_pb, primitive_pb, atom_ctx, device_annotation, circuit_lib);
}
}
}
} /* end namespace openfpga */

View File

@ -0,0 +1,27 @@
#ifndef BUILD_PHYSICAL_TRUTH_TABLE_H
#define BUILD_PHYSICAL_TRUTH_TABLE_H
/********************************************************************
* Include header files that are required by function declaration
*******************************************************************/
#include "vpr_context.h"
#include "vpr_device_annotation.h"
#include "vpr_clustering_annotation.h"
#include "circuit_library.h"
/********************************************************************
* Function declaration
*******************************************************************/
/* begin namespace openfpga */
namespace openfpga {
void build_physical_lut_truth_tables(VprClusteringAnnotation& cluster_annotation,
const AtomContext& atom_ctx,
const ClusteringContext& cluster_ctx,
const VprDeviceAnnotation& device_annotation,
const CircuitLibrary& circuit_lib);
} /* end namespace openfpga */
#endif

View File

@ -16,11 +16,27 @@ PhysicalPb::physical_pb_range PhysicalPb::pbs() const {
return vtr::make_range(pb_ids_.begin(), pb_ids_.end());
}
std::vector<PhysicalPbId> PhysicalPb::primitive_pbs() const {
std::vector<PhysicalPbId> results;
/* The primitive pbs are those without any children */
for (auto pb : pbs()) {
if (true == child_pbs_[pb].empty()) {
results.push_back(pb);
}
}
return results;
}
std::string PhysicalPb::name(const PhysicalPbId& pb) const {
VTR_ASSERT(true == valid_pb_id(pb));
return names_[pb];
}
const t_pb_graph_node* PhysicalPb::pb_graph_node(const PhysicalPbId& pb) const {
VTR_ASSERT(true == valid_pb_id(pb));
return pb_graph_nodes_[pb];
}
/* Find the module id by a given name, return invalid if not found */
PhysicalPbId PhysicalPb::find_pb(const t_pb_graph_node* pb_graph_node) const {
if (type2id_map_.find(pb_graph_node) != type2id_map_.end()) {
@ -36,6 +52,24 @@ PhysicalPbId PhysicalPb::parent(const PhysicalPbId& pb) const {
return parent_pbs_[pb];
}
PhysicalPbId PhysicalPb::child(const PhysicalPbId& pb,
const t_pb_type* pb_type,
const size_t& index) const {
VTR_ASSERT(true == valid_pb_id(pb));
if (0 < child_pbs_[pb].count(pb_type)) {
if (index < child_pbs_[pb].at(pb_type).size()) {
return child_pbs_[pb].at(pb_type)[index];
}
}
return PhysicalPbId::INVALID();
}
std::vector<AtomBlockId> PhysicalPb::atom_blocks(const PhysicalPbId& pb) const {
VTR_ASSERT(true == valid_pb_id(pb));
return atom_blocks_[pb];
}
AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb,
const t_pb_graph_pin* pb_graph_pin) const {
VTR_ASSERT(true == valid_pb_id(pb));
@ -47,6 +81,16 @@ AtomNetId PhysicalPb::pb_graph_pin_atom_net(const PhysicalPbId& pb,
return AtomNetId::INVALID();
}
std::map<const t_pb_graph_pin*, AtomNetlist::TruthTable> PhysicalPb::truth_tables(const PhysicalPbId& pb) const {
VTR_ASSERT(true == valid_pb_id(pb));
return truth_tables_[pb];
}
std::vector<size_t> PhysicalPb::mode_bits(const PhysicalPbId& pb) const {
VTR_ASSERT(true == valid_pb_id(pb));
return mode_bits_[pb];
}
/******************************************************************************
* Private Mutators
******************************************************************************/
@ -70,6 +114,7 @@ PhysicalPbId PhysicalPb::create_pb(const t_pb_graph_node* pb_graph_node) {
child_pbs_.emplace_back();
parent_pbs_.emplace_back();
truth_tables_.emplace_back();
mode_bits_.emplace_back();
/* Register in the name2id map */
@ -96,6 +141,19 @@ void PhysicalPb::add_child(const PhysicalPbId& parent,
parent_pbs_[child] = parent;
}
void PhysicalPb::set_truth_table(const PhysicalPbId& pb,
const t_pb_graph_pin* pb_graph_pin,
const AtomNetlist::TruthTable& truth_table) {
VTR_ASSERT(true == valid_pb_id(pb));
if (0 < truth_tables_[pb].count(pb_graph_pin)) {
VTR_LOG_WARN("Overwrite truth tables mapped to pb_graph_pin '%s[%ld]!\n",
pb_graph_pin->port->name, pb_graph_pin->pin_number);
}
truth_tables_[pb][pb_graph_pin] = truth_table;
}
void PhysicalPb::set_mode_bits(const PhysicalPbId& pb,
const std::vector<size_t>& mode_bits) {
VTR_ASSERT(true == valid_pb_id(pb));
@ -106,7 +164,7 @@ void PhysicalPb::set_mode_bits(const PhysicalPbId& pb,
void PhysicalPb::add_atom_block(const PhysicalPbId& pb,
const AtomBlockId& atom_block) {
VTR_ASSERT(true == valid_pb_id(pb));
atom_blocks_[pb].push_back(atom_block);
}

View File

@ -12,7 +12,7 @@
#include "physical_types.h"
/* Headers from vpr library */
#include "atom_netlist_fwd.h"
#include "atom_netlist.h"
#include "physical_pb_fwd.h"
@ -39,11 +39,19 @@ class PhysicalPb {
typedef vtr::Range<physical_pb_iterator> physical_pb_range;
public: /* Public aggregators */
physical_pb_range pbs() const;
std::vector<PhysicalPbId> primitive_pbs() const;
std::string name(const PhysicalPbId& pb) const;
const t_pb_graph_node* pb_graph_node(const PhysicalPbId& pb) const;
PhysicalPbId find_pb(const t_pb_graph_node* name) const;
PhysicalPbId parent(const PhysicalPbId& pb) const;
PhysicalPbId child(const PhysicalPbId& pb,
const t_pb_type* pb_type,
const size_t& index) const;
std::vector<AtomBlockId> atom_blocks(const PhysicalPbId& pb) const;
AtomNetId pb_graph_pin_atom_net(const PhysicalPbId& pb,
const t_pb_graph_pin* pb_graph_pin) const;
std::map<const t_pb_graph_pin*, AtomNetlist::TruthTable> truth_tables(const PhysicalPbId& pb) const;
std::vector<size_t> mode_bits(const PhysicalPbId& pb) const;
public: /* Public mutators */
PhysicalPbId create_pb(const t_pb_graph_node* pb_graph_node);
void add_child(const PhysicalPbId& parent,
@ -51,6 +59,9 @@ class PhysicalPb {
const t_pb_type* child_type);
void add_atom_block(const PhysicalPbId& pb,
const AtomBlockId& atom_block);
void set_truth_table(const PhysicalPbId& pb,
const t_pb_graph_pin* pb_graph_pin,
const AtomNetlist::TruthTable& truth_table);
void set_mode_bits(const PhysicalPbId& pb,
const std::vector<size_t>& mode_bits);
void set_pb_graph_pin_atom_net(const PhysicalPbId& pb,
@ -70,6 +81,11 @@ class PhysicalPb {
vtr::vector<PhysicalPbId, std::map<const t_pb_type*, std::vector<PhysicalPbId>>> child_pbs_;
vtr::vector<PhysicalPbId, PhysicalPbId> parent_pbs_;
/* configuration bits
* Truth tables and mode selection
*/
vtr::vector<PhysicalPbId, std::map<const t_pb_graph_pin*, AtomNetlist::TruthTable>> truth_tables_;
vtr::vector<PhysicalPbId, std::vector<size_t>> mode_bits_;
/* Fast lookup */

View File

@ -292,7 +292,7 @@ void repack_cluster(const AtomContext& atom_ctx,
clustering_ctx.clb_nlist.block_pb(block_id)->pb_route,
atom_ctx,
device_annotation);
/* TODO: save routing results */
/* Save routing results */
save_lb_router_results_to_physical_pb(phy_pb, lb_router, lb_rr_graph);
VTR_LOGV(verbose, "Saved results in physical pb\n");

View File

@ -2,10 +2,15 @@
* 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 "openfpga_decode.h"
#include "lut_utils.h"
/* begin namespace openfpga */
@ -29,6 +34,21 @@ namespace openfpga {
* 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) {
@ -89,5 +109,378 @@ std::vector<std::string> truth_table_to_string(const AtomNetlist::TruthTable& tt
return tt_str;
}
} /* end namespace openfpga */
/********************************************************************
* 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 (const 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());
/* 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;
}
} /* end namespace openfpga */

View File

@ -6,7 +6,11 @@
*******************************************************************/
#include <string>
#include <vector>
#include <map>
#include "atom_netlist.h"
#include "mux_graph.h"
#include "physical_types.h"
#include "vpr_device_annotation.h"
/********************************************************************
* Function declaration
@ -20,6 +24,21 @@ AtomNetlist::TruthTable lut_truth_table_adaption(const AtomNetlist::TruthTable&
std::vector<std::string> truth_table_to_string(const AtomNetlist::TruthTable& tt);
AtomNetlist::TruthTable build_wired_lut_truth_table(const size_t& lut_size,
const size_t& wire_input_id);
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);
bool lut_truth_table_use_on_set(const AtomNetlist::TruthTable& truth_table);
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);
} /* end namespace openfpga */
#endif

View File

@ -32,6 +32,8 @@ void rec_alloc_physical_pb_from_pb_graph(PhysicalPb& phy_pb,
/* Finish for primitive node */
if (true == is_primitive_pb_type(pb_type)) {
/* Deposite mode bits here */
phy_pb.set_mode_bits(cur_phy_pb_id, device_annotation.pb_type_mode_bits(pb_type));
return;
}
@ -236,7 +238,7 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb,
VTR_ASSERT(true == phy_pb.valid_pb_id(physical_pb));
/* Set the mode bits */
phy_pb.set_mode_bits(physical_pb, device_annotation.pb_type_mode_bits(physical_pb_graph_node->pb_type));
phy_pb.set_mode_bits(physical_pb, device_annotation.pb_type_mode_bits(pb_type));
/* Find mapped atom block and add to this physical pb */
AtomBlockId atom_blk = atom_ctx.nlist.find_block(op_pb->name);
@ -244,7 +246,7 @@ void rec_update_physical_pb_from_operating_pb(PhysicalPb& phy_pb,
phy_pb.add_atom_block(physical_pb, atom_blk);
/* TODO: Iterate over ports and annotate the atom pins */
/* Iterate over ports and annotate the atom pins */
synchronize_primitive_physical_pb_atom_nets(phy_pb, physical_pb,
pb_graph_node,
pb_route,

View File

@ -8,7 +8,7 @@ read_openfpga_arch -f ./test_openfpga_arch/k6_frac_N10_40nm_openfpga.xml
#write_openfpga_arch -f ./arch_echo.xml
# Annotate the OpenFPGA architecture to VPR data base
link_openfpga_arch --verbose
link_openfpga_arch #--verbose
# Check and correct any naming conflicts in the BLIF netlist
check_netlist_naming_conflict --fix --report ./netlist_renaming.xml
@ -29,6 +29,10 @@ build_fabric --compress_routing --duplicate_grid_pin #--verbose
# Strongly recommend it is done after all the fix-up have been applied
repack --verbose
# Build the bitstream
# - Output the fabric-independent bitstream to a file
fpga_bitstream --verbose --file /var/tmp/xtang/openfpga_test_src/fabric_indepenent_bitstream.xml
# Write the Verilog netlit for FPGA fabric
# - Enable the use of explicit port mapping in Verilog netlist
write_fabric_verilog --file /var/tmp/xtang/openfpga_test_src --explicit_port_mapping --include_timing --include_signal_init --support_icarus_simulator --print_user_defined_template --verbose