Merge branch 'refactoring' into dev
This commit is contained in:
commit
1fa36c22d3
|
@ -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
|
||||
|
|
|
@ -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 */
|
|
@ -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
|
|
@ -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*/
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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*/
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 */
|
|
@ -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
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue