Support extracting data that is not affecting fabric bitstream (#1566)

* BRAM preload data - generic way to extract data from design

* Add docs and support special __layout__ case

* Add test

* Fix warning

* Change none-fabric to non-fabric
This commit is contained in:
chungshien 2024-03-09 17:38:31 -08:00 committed by GitHub
parent 2a46a9902c
commit 4365d160ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 499 additions and 5 deletions

View File

@ -13,6 +13,9 @@ This can define a hard-coded bitstream for a reconfigurable resource in FPGA fab
<openfpga_bitstream_setting>
<pb_type name="<string>" source="eblif" content=".param LUT" is_mode_select_bistream="true" bitstream_offset="1"/>
<interconnect name="<string>" default_path="<string>"/>
<non_fabric name="<string>" file="<string>">
<pb name="<string>" type="<string>" content="<string>"/>
</non_fabric>
</openfpga_bitstream_setting>
pb_type-related Settings
@ -39,7 +42,6 @@ The following syntax are applicable to the XML definition tagged by ``pb_type``
.. option:: content="<string>"
The content of the ``pb_type`` bitstream, which could be a keyword in a ``.eblif`` file. For example, ``content=".attr LUT"`` means that the bitstream will be extracted from the ``.attr LUT`` line which is defined under the ``.blif model`` (that is defined under the ``pb_type`` in VPR architecture file).
.. option:: is_mode_select_bitstream="<bool>"
@ -71,3 +73,45 @@ The following syntax are applicable to the XML definition tagged by ``interconne
<mux name="mux1" input="iopad.inpad ff.Q" output="io.inpad"/>
The default path can be either ``iopad.inpad`` or ``ff.Q`` which corresponds to the first input and the second input respectively.
non_fabric-related Settings
^^^^^^^^^^^^^^^^^^^^^^^^
This is special syntax to extract PB defined parameter or attribute and save the data into dedicated JSON file outside of fabric bitstream
The following syntax are applicable to the XML definition tagged by ``non_fabric`` in bitstream setting files.
.. option:: name="<string: pb_type top level name>"
The ``pb_type`` top level name that the data to be extracted. For example,
.. code-block:: xml
name="bram"
.. option:: file="<string: JSON filepath>"
The filepath the data is saved to. For example,
.. code-block:: xml
file="bram.json"
.. option:: ``pb`` child element name="<string: pb_type child name>"
Together with ``pb_type`` top level name, that is the source of the ``pb_type`` bitstream
The final ``pb_type`` name is "<pb_type top level name>" + "<pb_type child name>"
For example,
.. code-block:: xml
<non_fabric name="bram" file="bram_bitstream.json">
<pb name=".bram_lr[mem_36K_tdp].mem_36K" content=".param INIT_i"/>
</non_fabric>
The final ``pb_type`` name is "bram.bram_lr[mem_36K_tdp].mem_36K"
.. option:: ``pb`` child element content="<string>"
The content of the ``pb_type`` data to be extracted. For example, ``content=".param INIT_i"`` means that the data will be extracted from the ``.param INIT_i`` line defined under the ``.blif model``.

View File

@ -102,6 +102,10 @@ std::string BitstreamSetting::default_path(
return interconnect_default_paths_[interconnect_setting_id];
}
std::vector<NonFabricBitstreamSetting> BitstreamSetting::non_fabric() const {
return non_fabric_;
}
/************************************************************************
* Public Mutators
***********************************************************************/
@ -154,6 +158,26 @@ BitstreamSetting::add_bitstream_interconnect_setting(
return interc_setting_id;
}
void BitstreamSetting::add_non_fabric(const std::string& name,
const std::string& file) {
VTR_ASSERT(name.size());
VTR_ASSERT(file.size());
non_fabric_.push_back(NonFabricBitstreamSetting(name, file));
}
void BitstreamSetting::add_non_fabric_pb(const std::string& pb,
const std::string& content) {
VTR_ASSERT(non_fabric_.size());
VTR_ASSERT(content.find(".param ") == 0 || content.find(".attr ") == 0);
if (content.find(".param ") == 0) {
VTR_ASSERT(content.size() > 7);
non_fabric_.back().add_pb(pb, "param", content.substr(7));
} else {
VTR_ASSERT(content.size() > 6);
non_fabric_.back().add_pb(pb, "attr", content.substr(6));
}
}
/************************************************************************
* Public Validators
***********************************************************************/

View File

@ -6,6 +6,7 @@
* which are used by OpenFPGA
*******************************************************************/
#include <string>
#include <vector>
#include "bitstream_setting_fwd.h"
#include "vtr_vector.h"
@ -13,6 +14,29 @@
/* namespace openfpga begins */
namespace openfpga {
struct NonFabricBitstreamPBSetting {
NonFabricBitstreamPBSetting(const std::string& p = "",
const std::string& t = "",
const std::string& c = "")
: pb(p), type(t), content(c) {}
const std::string pb = "";
const std::string type = "";
const std::string content = "";
};
struct NonFabricBitstreamSetting {
NonFabricBitstreamSetting(const std::string& n = "",
const std::string& f = "")
: name(n), file(f) {}
void add_pb(const std::string& p, const std::string& t,
const std::string& c) {
pbs.push_back(NonFabricBitstreamPBSetting(p, t, c));
}
const std::string name = "";
const std::string file = "";
std::vector<NonFabricBitstreamPBSetting> pbs;
};
/********************************************************************
* A data structure to describe bitstream settings
*
@ -73,6 +97,7 @@ class BitstreamSetting {
const BitstreamInterconnectSettingId& interconnect_setting_id) const;
std::string default_path(
const BitstreamInterconnectSettingId& interconnect_setting_id) const;
std::vector<NonFabricBitstreamSetting> non_fabric() const;
public: /* Public Mutators */
BitstreamPbTypeSettingId add_bitstream_pb_type_setting(
@ -92,6 +117,9 @@ class BitstreamSetting {
const std::vector<std::string>& parent_mode_names,
const std::string& default_path);
void add_non_fabric(const std::string& name, const std::string& file);
void add_non_fabric_pb(const std::string& pb, const std::string& content);
public: /* Public Validators */
bool valid_bitstream_pb_type_setting_id(
const BitstreamPbTypeSettingId& pb_type_setting_id) const;
@ -133,6 +161,7 @@ class BitstreamSetting {
interconnect_parent_mode_names_;
vtr::vector<BitstreamInterconnectSettingId, std::string>
interconnect_default_paths_;
std::vector<NonFabricBitstreamSetting> non_fabric_;
};
} // namespace openfpga

View File

@ -76,6 +76,32 @@ static void read_xml_bitstream_interconnect_setting(
operating_pb_parser.modes(), default_path_attr);
}
/********************************************************************
* Parse XML description for a non_fabric annotation under a <non_fabric> XML
*node
*******************************************************************/
static void read_xml_non_fabric_bitstream_setting(
pugi::xml_node& xml_non_fabric, const pugiutil::loc_data& loc_data,
openfpga::BitstreamSetting& bitstream_setting) {
const std::string& name_attr =
get_attribute(xml_non_fabric, "name", loc_data).as_string();
const std::string& file_attr =
get_attribute(xml_non_fabric, "file", loc_data).as_string();
/* Add to non-fabric */
bitstream_setting.add_non_fabric(name_attr, file_attr);
for (pugi::xml_node xml_child : xml_non_fabric.children()) {
if (xml_child.name() != std::string("pb")) {
bad_tag(xml_child, loc_data, xml_non_fabric, {"pb"});
}
const std::string& pb_name_attr =
get_attribute(xml_child, "name", loc_data).as_string();
const std::string& content_attr =
get_attribute(xml_child, "content", loc_data).as_string();
/* Add PB to non-fabric */
bitstream_setting.add_non_fabric_pb(pb_name_attr, content_attr);
}
}
/********************************************************************
* Parse XML codes about <openfpga_bitstream_setting> to an object
*******************************************************************/
@ -89,17 +115,22 @@ openfpga::BitstreamSetting read_xml_bitstream_setting(
for (pugi::xml_node xml_child : Node.children()) {
/* Error out if the XML child has an invalid name! */
if ((xml_child.name() != std::string("pb_type")) &&
(xml_child.name() != std::string("interconnect"))) {
bad_tag(xml_child, loc_data, Node, {"pb_type | interconnect"});
(xml_child.name() != std::string("interconnect")) &&
(xml_child.name() != std::string("non_fabric"))) {
bad_tag(xml_child, loc_data, Node,
{"pb_type | interconnect | non_fabric"});
}
if (xml_child.name() == std::string("pb_type")) {
read_xml_bitstream_pb_type_setting(xml_child, loc_data,
bitstream_setting);
} else {
VTR_ASSERT_SAFE(xml_child.name() == std::string("interconnect"));
} else if (xml_child.name() == std::string("interconnect")) {
read_xml_bitstream_interconnect_setting(xml_child, loc_data,
bitstream_setting);
} else {
VTR_ASSERT_SAFE(xml_child.name() == std::string("non_fabric"));
read_xml_non_fabric_bitstream_setting(xml_child, loc_data,
bitstream_setting);
}
}

View File

@ -11,6 +11,7 @@
#include "command.h"
#include "command_context.h"
#include "command_exit_codes.h"
#include "extract_device_non_fabric_bitstream.h"
#include "globals.h"
#include "openfpga_digest.h"
#include "openfpga_naming.h"
@ -59,6 +60,9 @@ int fpga_bitstream_template(T& openfpga_ctx, const Command& cmd,
!cmd_context.option_enable(cmd, opt_no_time_stamp));
}
extract_device_non_fabric_bitstream(
g_vpr_ctx, openfpga_ctx, cmd_context.option_enable(cmd, opt_verbose));
/* TODO: should identify the error code from internal function execution */
return CMD_EXEC_SUCCESS;
}

View File

@ -0,0 +1,283 @@
/********************************************************************
* 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 <fstream>
#include <vector>
/* Headers from vtrutil library */
#include "extract_device_non_fabric_bitstream.h"
#include "openfpga_pb_parser.h"
#include "pb_type_utils.h"
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"
/* begin namespace openfpga */
namespace openfpga {
#define PRINT_LAYOUT_NAME "__layout__"
/********************************************************************
* Extract data from the targetted PB
* 1. If it is primitive
* a. If it match the targetted PB, try to get data from
* param of attr depends on what being defined in XML
* b. If it is does not match, do nothing
* 2. If it is not primitive, then we loop for the child
*******************************************************************/
static bool extract_pb_data(std::fstream& fp, const AtomContext& atom_ctx,
const t_pb* op_pb, const t_pb_type* target_pb_type,
const NonFabricBitstreamPBSetting& setting) {
t_pb_graph_node* pb_graph_node = op_pb->pb_graph_node;
t_pb_type* pb_type = pb_graph_node->pb_type;
bool found_pb = false;
if (true == is_primitive_pb_type(pb_type)) {
if (target_pb_type == pb_type) {
AtomBlockId atom_blk = atom_ctx.nlist.find_block(op_pb->name);
VTR_ASSERT(atom_blk);
if (setting.type == "param") {
for (const auto& param_search : atom_ctx.nlist.block_params(atom_blk)) {
std::string param = param_search.first;
std::string content = param_search.second;
if (setting.content == param) {
fp << ",\n \"data\" : \"" << content.c_str() << "\"";
break;
}
}
} else {
VTR_ASSERT(setting.type == "attr");
for (const auto& attr_search : atom_ctx.nlist.block_attrs(atom_blk)) {
std::string attr = attr_search.first;
std::string content = attr_search.second;
if (setting.content == attr) {
fp << ",\n \"data\" : \"" << content.c_str() << "\"";
break;
}
}
}
found_pb = true;
}
} else {
t_mode* mapped_mode = &(pb_graph_node->pb_type->modes[op_pb->mode]);
for (int ipb = 0; ipb < mapped_mode->num_pb_type_children && !found_pb;
++ipb) {
/* Each child may exist multiple times in the hierarchy*/
for (int jpb = 0;
jpb < mapped_mode->pb_type_children[ipb].num_pb && !found_pb;
++jpb) {
if ((nullptr != op_pb->child_pbs[ipb]) &&
(nullptr != op_pb->child_pbs[ipb][jpb].name)) {
found_pb =
extract_pb_data(fp, atom_ctx, &(op_pb->child_pbs[ipb][jpb]),
target_pb_type, setting);
}
}
}
}
return found_pb;
}
/********************************************************************
* Extract data from the targetted PB (from that particular grid)
*******************************************************************/
static void extract_grid_non_fabric_bitstream(
std::fstream& fp, const VprContext& vpr_ctx,
const ClusterBlockId& cluster_block_id, const t_pb_type* target_pb_type,
const NonFabricBitstreamPBSetting setting) {
const ClusteringContext& clustering_ctx = vpr_ctx.clustering();
const AtomContext& atom_ctx = vpr_ctx.atom();
if (ClusterBlockId::INVALID() != cluster_block_id) {
const t_pb* op_pb = clustering_ctx.clb_nlist.block_pb(cluster_block_id);
extract_pb_data(fp, atom_ctx, op_pb, target_pb_type, setting);
} else {
// Grid is valid, but this resource is not being used
}
}
/********************************************************************
* Extract data from the targetted PB (from the device)
*******************************************************************/
static void extract_device_non_fabric_pb_bitstream(
std::fstream& fp, const NonFabricBitstreamPBSetting setting,
const std::string& target_parent_pb_name, const t_pb_type* target_pb_type,
const VprContext& vpr_ctx) {
const DeviceContext& device_ctx = vpr_ctx.device();
const PlacementContext& placement_ctx = vpr_ctx.placement();
const DeviceGrid& grids = device_ctx.grid;
const size_t& layer = 0;
// Loop logic block one by one
if (target_parent_pb_name != PRINT_LAYOUT_NAME) {
fp << ",\n \"grid\" : [";
}
size_t grid_count = 0;
for (size_t ix = 1; ix < grids.width() - 1; ++ix) {
for (size_t iy = 1; iy < grids.height() - 1; ++iy) {
t_physical_tile_loc phy_tile_loc(ix, iy, layer);
t_physical_tile_type_ptr grid_type =
grids.get_physical_type(phy_tile_loc);
// Bypass EMPTY grid
if (true == is_empty_type(grid_type)) {
continue;
}
// Skip width > 1 or height > 1 tiles (mostly heterogeneous blocks)
if ((0 < grids.get_width_offset(phy_tile_loc)) ||
(0 < grids.get_height_offset(phy_tile_loc))) {
continue;
}
// Skip if this grid is not what we are looking for
if (target_parent_pb_name == PRINT_LAYOUT_NAME) {
if (grid_count) {
fp << ",\n";
}
fp << " {\n";
fp << " \"x\" : " << (uint32_t)(ix) << ",\n";
fp << " \"y\" : " << (uint32_t)(iy) << ",\n";
fp << " \"name\" : \"" << grid_type->name << "\"\n";
fp << " }";
grid_count++;
continue;
}
// Skip if this grid is not what we are looking for
if (target_parent_pb_name != std::string(grid_type->name)) {
continue;
}
// Get the mapped blocks to this grid
for (int isubtile = 0; isubtile < grid_type->capacity; ++isubtile) {
ClusterBlockId cluster_blk_id =
placement_ctx.grid_blocks.block_at_location(
{(int)ix, (int)iy, (int)isubtile, (int)layer});
if (grid_count) {
fp << ",";
}
fp << "\n";
fp << " {\n";
fp << " \"x\" : " << (uint32_t)(ix) << ",\n";
fp << " \"y\" : " << (uint32_t)(iy);
extract_grid_non_fabric_bitstream(fp, vpr_ctx, cluster_blk_id,
target_pb_type, setting);
fp << "\n }";
grid_count++;
}
}
}
if (target_parent_pb_name == PRINT_LAYOUT_NAME) {
fp << "\n";
} else {
fp << "\n ]";
}
}
/********************************************************************
* Search the PB type based on the given name defined in XML
*******************************************************************/
static t_pb_type* find_pb_type(const DeviceContext& device_ctx,
const std::string& parent_pb,
const std::string& pb) {
t_pb_type* pb_type = nullptr;
openfpga::PbParser pb_parser(pb);
std::vector<std::string> names = pb_parser.parents();
names.push_back(pb_parser.leaf());
for (const t_logical_block_type& lb_type : device_ctx.logical_block_types) {
/* Bypass nullptr for pb_type head */
if (nullptr == lb_type.pb_type) {
continue;
}
/* Check the name of the top-level pb_type, if it does not match, we can
* bypass */
if (parent_pb != std::string(lb_type.pb_type->name)) {
continue;
}
/* Match the name in the top-level, we go further to search the pb_type in
* the graph */
pb_type = try_find_pb_type_with_given_path(lb_type.pb_type, names,
pb_parser.modes());
if (nullptr == pb_type) {
continue;
}
break;
}
return pb_type;
}
/********************************************************************
* A top-level function to extract data based on non-fabric bitstream setting
*******************************************************************/
void extract_device_non_fabric_bitstream(const VprContext& vpr_ctx,
const OpenfpgaContext& openfpga_ctx,
const bool& verbose) {
std::string timer_message =
std::string("\nBuild non-fabric bitstream for implementation '") +
vpr_ctx.atom().nlist.netlist_name() + std::string("'\n");
vtr::ScopedStartFinishTimer timer(timer_message);
const openfpga::BitstreamSetting& bitstream_setting =
openfpga_ctx.bitstream_setting();
std::vector<NonFabricBitstreamSetting> non_fabric_setting =
bitstream_setting.non_fabric();
// Only proceed if it is defined in bitstream_setting.xml
if (non_fabric_setting.size()) {
// Go through each non_fabric settting
for (auto setting : non_fabric_setting) {
std::fstream fp;
fp.open(setting.file.c_str(), std::fstream::out);
fp << "{\n";
fp << " \"" << setting.name.c_str() << "\" : [\n";
if (setting.name == PRINT_LAYOUT_NAME) {
extract_device_non_fabric_pb_bitstream(
fp, NonFabricBitstreamPBSetting{}, setting.name, nullptr, vpr_ctx);
} else {
int pb_count = 0;
// Extract each needed PB data
for (auto pb_setting : setting.pbs) {
std::string pb_type = setting.name + pb_setting.pb;
t_pb_type* target_pb_type =
find_pb_type(vpr_ctx.device(), setting.name, pb_type);
if (pb_count) {
fp << ",\n";
}
fp << " {\n";
fp << " \"pb\" : \"" << pb_type.c_str() << "\",\n";
if (target_pb_type == nullptr) {
fp << " \"is_primitive_pb_type\" : \"invalid\",\n";
} else {
if (is_primitive_pb_type(target_pb_type)) {
fp << " \"is_primitive_pb_type\" : \"true\",\n";
} else {
fp << " \"is_primitive_pb_type\" : \"false\",\n";
}
}
fp << " \"type\" : \"" << pb_setting.type.c_str() << "\",\n";
fp << " \"content\" : \"" << pb_setting.content.c_str() << "\"";
if (target_pb_type != nullptr &&
is_primitive_pb_type(target_pb_type)) {
extract_device_non_fabric_pb_bitstream(fp, pb_setting, setting.name,
target_pb_type, vpr_ctx);
}
fp << "\n }";
pb_count++;
}
if (pb_count) {
fp << "\n";
}
}
fp << " ]\n";
fp << "}\n";
fp.close();
}
}
VTR_LOGV(verbose, "Done\n");
}
} /* end namespace openfpga */

View File

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

View File

@ -52,3 +52,6 @@ run-task fpga_bitstream/filter_value0 $@
run-task fpga_bitstream/filter_value1 $@
run-task fpga_bitstream/path_only $@
run-task fpga_bitstream/value_only $@
echo -e "Testing extracting mode bits for DSP blocks when generating bitstream";
run-task fpga_bitstream/extract_dsp_mode_bit $@

View File

@ -0,0 +1,7 @@
<openfpga_bitstream_setting>
<pb_type name="mult_16[mult_16x16].mult_16x16_slice.mult_16x16" source="eblif" content=".param MODE" is_mode_select_bitstream="true"/>
<pb_type name="mult_16[mult_8x8].mult_8x8_slice.mult_8x8" source="eblif" content=".param MODE" is_mode_select_bitstream="true"/>
<non_fabric name="mult_16" file="dsp.json">
<pb name="[mult_8x8].mult_8x8_slice.mult_8x8" content=".param MODE"/>
</non_fabric>
</openfpga_bitstream_setting>

View File

@ -0,0 +1,44 @@
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Configuration file for running experiments
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# timeout_each_job : FPGA Task script splits fpga flow into multiple jobs
# Each job execute fpga_flow script on combination of architecture & benchmark
# timeout_each_job is timeout for each job
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
[GENERAL]
run_engine=openfpga_shell
power_tech_file = ${PATH:OPENFPGA_PATH}/openfpga_flow/tech/PTM_45nm/45nm.xml
power_analysis = false
spice_output=false
verilog_output=true
timeout_each_job = 20*60
fpga_flow=yosys_vpr
[OpenFPGA_SHELL]
openfpga_shell_template=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_shell_scripts/bitstream_setting_example_script.openfpga
openfpga_arch_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_arch/k4_N4_frac_dsp16_40nm_cc_openfpga.xml
openfpga_sim_setting_file=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_simulation_settings/fixed_sim_openfpga.xml
openfpga_bitstream_setting_file=${PATH:TASK_DIR}/config/bitstream_annotation.xml
# VPR parameter
openfpga_vpr_circuit_format=eblif
[ARCHITECTURES]
arch0=${PATH:OPENFPGA_PATH}/openfpga_flow/vpr_arch/k4_N4_tileable_frac_dsp16_40nm.xml
[BENCHMARKS]
bench0=${PATH:OPENFPGA_PATH}/openfpga_flow/benchmarks/micro_benchmark/mult/mult8/mult8.v
[SYNTHESIS_PARAM]
# Yosys script parameters
bench_yosys_cell_sim_verilog_common=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_yosys_techlib/k4_N4_tileable_frac_dsp16_40nm_cell_sim.v
bench_yosys_dsp_map_verilog_common=${PATH:OPENFPGA_PATH}/openfpga_flow/openfpga_yosys_techlib/k4_N4_tileable_frac_dsp16_40nm_dsp_map.v
bench_yosys_dsp_map_parameters_common=-D DSP_A_MAXWIDTH=8 -D DSP_B_MAXWIDTH=8 -D DSP_A_MINWIDTH=2 -D DSP_B_MINWIDTH=2 -D DSP_NAME=mult_8x8
bench_read_verilog_options_common = -nolatches
bench_yosys_common=${PATH:OPENFPGA_PATH}/openfpga_flow/misc/ys_tmpl_yosys_vpr_dsp_flow.ys
bench_yosys_rewrite_common=${PATH:OPENFPGA_PATH}/openfpga_flow/misc/ys_tmpl_yosys_vpr_dsp_flow_with_rewrite.ys;${PATH:OPENFPGA_PATH}/openfpga_flow/misc/ys_tmpl_rewrite_flow.ys
bench0_top = mult8
[SCRIPT_PARAM_MIN_ROUTE_CHAN_WIDTH]
end_flow_with_test=
vpr_fpga_verilog_formal_verification_top_netlist=