From e7f55d33b976490648194172ca4f41df4065be86 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 29 Nov 2021 14:02:21 +0100 Subject: [PATCH 01/22] libs: arch: move common functions to util and check source files This commit also reworks the way the name is assigned to the null types Signed-off-by: Alessandro Comodi --- libs/libarchfpga/src/arch_util.cpp | 211 ++++++++++++++++++- libs/libarchfpga/src/arch_util.h | 8 +- libs/libarchfpga/src/read_xml_arch_file.cpp | 214 +------------------- vpr/src/base/SetupVPR.cpp | 7 +- vpr/src/base/vpr_types.h | 2 - 5 files changed, 221 insertions(+), 221 deletions(-) diff --git a/libs/libarchfpga/src/arch_util.cpp b/libs/libarchfpga/src/arch_util.cpp index 64c16bbd093..4932d9fb2dc 100644 --- a/libs/libarchfpga/src/arch_util.cpp +++ b/libs/libarchfpga/src/arch_util.cpp @@ -555,9 +555,9 @@ t_port* findPortByName(const char* name, t_pb_type* pb_type, int* high_index, in return port; } -t_physical_tile_type SetupEmptyPhysicalType() { +t_physical_tile_type get_empty_physical_type(std::string name) { t_physical_tile_type type; - type.name = vtr::strdup("EMPTY"); + type.name = vtr::strdup(name.c_str()); type.num_pins = 0; type.width = 1; type.height = 1; @@ -573,9 +573,9 @@ t_physical_tile_type SetupEmptyPhysicalType() { return type; } -t_logical_block_type SetupEmptyLogicalType() { +t_logical_block_type get_empty_logical_type(std::string name) { t_logical_block_type type; - type.name = vtr::strdup("EMPTY"); + type.name = vtr::strdup(name.c_str()); type.pb_type = nullptr; return type; @@ -1392,5 +1392,208 @@ const t_pin_to_pin_annotation* find_combinational_annotation(const t_pb_type* pb } } } + return nullptr; } + +void link_physical_logical_types(std::vector& PhysicalTileTypes, + std::vector& LogicalBlockTypes) { + for (auto& physical_tile : PhysicalTileTypes) { + if (physical_tile.index == EMPTY_TYPE_INDEX) continue; + + auto eq_sites_set = get_equivalent_sites_set(&physical_tile); + auto equivalent_sites = std::vector(eq_sites_set.begin(), eq_sites_set.end()); + + auto criteria = [&physical_tile](const t_logical_block_type* lhs, const t_logical_block_type* rhs) { + int num_pins = physical_tile.num_inst_pins; + + int lhs_num_logical_pins = lhs->pb_type->num_pins; + int rhs_num_logical_pins = rhs->pb_type->num_pins; + + int lhs_diff_num_pins = num_pins - lhs_num_logical_pins; + int rhs_diff_num_pins = num_pins - rhs_num_logical_pins; + + return lhs_diff_num_pins < rhs_diff_num_pins; + }; + + std::sort(equivalent_sites.begin(), equivalent_sites.end(), criteria); + + for (auto& logical_block : LogicalBlockTypes) { + for (auto site : equivalent_sites) { + if (0 == strcmp(logical_block.name, site->pb_type->name)) { + logical_block.equivalent_tiles.push_back(&physical_tile); + break; + } + } + } + } + + for (auto& logical_block : LogicalBlockTypes) { + if (logical_block.index == EMPTY_TYPE_INDEX) continue; + + auto& equivalent_tiles = logical_block.equivalent_tiles; + + if ((int)equivalent_tiles.size() <= 0) { + archfpga_throw(__FILE__, __LINE__, + "Logical Block %s does not have any equivalent tiles.\n", logical_block.name); + } + + std::unordered_map ignored_pins_check_map; + std::unordered_map global_pins_check_map; + + auto criteria = [&logical_block](const t_physical_tile_type* lhs, const t_physical_tile_type* rhs) { + int num_logical_pins = logical_block.pb_type->num_pins; + + int lhs_num_pins = lhs->num_inst_pins; + int rhs_num_pins = rhs->num_inst_pins; + + int lhs_diff_num_pins = lhs_num_pins - num_logical_pins; + int rhs_diff_num_pins = rhs_num_pins - num_logical_pins; + + return lhs_diff_num_pins < rhs_diff_num_pins; + }; + + std::sort(equivalent_tiles.begin(), equivalent_tiles.end(), criteria); + + for (int pin = 0; pin < logical_block.pb_type->num_pins; pin++) { + for (auto& tile : equivalent_tiles) { + auto direct_maps = tile->tile_block_pin_directs_map.at(logical_block.index); + + for (auto& sub_tile : tile->sub_tiles) { + auto equiv_sites = sub_tile.equivalent_sites; + if (std::find(equiv_sites.begin(), equiv_sites.end(), &logical_block) == equiv_sites.end()) { + continue; + } + + auto direct_map = direct_maps.at(sub_tile.index); + + auto result = direct_map.find(t_logical_pin(pin)); + if (result == direct_map.end()) { + archfpga_throw(__FILE__, __LINE__, + "Logical pin %d not present in pin mapping between Tile %s and Block %s.\n", + pin, tile->name, logical_block.name); + } + + int sub_tile_pin_index = result->second.pin; + int phy_index = sub_tile.sub_tile_to_tile_pin_indices[sub_tile_pin_index]; + + bool is_ignored = tile->is_ignored_pin[phy_index]; + bool is_global = tile->is_pin_global[phy_index]; + + auto ignored_result = ignored_pins_check_map.insert(std::pair(pin, is_ignored)); + if (!ignored_result.second && ignored_result.first->second != is_ignored) { + archfpga_throw(__FILE__, __LINE__, + "Physical Tile %s has a different value for the ignored pin (physical pin: %d, logical pin: %d) " + "different from the corresponding pins of the other equivalent site %s\n.", + tile->name, phy_index, pin, logical_block.name); + } + + auto global_result = global_pins_check_map.insert(std::pair(pin, is_global)); + if (!global_result.second && global_result.first->second != is_global) { + archfpga_throw(__FILE__, __LINE__, + "Physical Tile %s has a different value for the global pin (physical pin: %d, logical pin: %d) " + "different from the corresponding pins of the other equivalent sites\n.", + tile->name, phy_index, pin); + } + } + } + } + } +} + +/* Sets up the pin classes for the type. */ +void setup_pin_classes(t_physical_tile_type* type) { + int i, k; + int pin_count; + int num_class; + + for (i = 0; i < type->num_pins; i++) { + type->pin_class.push_back(OPEN); + type->is_ignored_pin.push_back(true); + type->is_pin_global.push_back(true); + } + + pin_count = 0; + + t_class_range class_range; + + /* Equivalent pins share the same class, non-equivalent pins belong to different pin classes */ + for (auto& sub_tile : type->sub_tiles) { + int capacity = sub_tile.capacity.total(); + class_range.low = type->class_inf.size(); + class_range.high = class_range.low - 1; + for (i = 0; i < capacity; ++i) { + for (const auto& port : sub_tile.ports) { + if (port.equivalent != PortEquivalence::NONE) { + t_class class_inf; + num_class = (int)type->class_inf.size(); + class_inf.num_pins = port.num_pins; + class_inf.equivalence = port.equivalent; + + if (port.type == IN_PORT) { + class_inf.type = RECEIVER; + } else { + VTR_ASSERT(port.type == OUT_PORT); + class_inf.type = DRIVER; + } + + for (k = 0; k < port.num_pins; ++k) { + class_inf.pinlist.push_back(pin_count); + type->pin_class[pin_count] = num_class; + // clock pins and other specified global ports are initially specified + // as ignored pins (i.e. connections are not created in the rr_graph and + // nets connected to the port are ignored as well). + type->is_ignored_pin[pin_count] = port.is_clock || port.is_non_clock_global; + // clock pins and other specified global ports are flaged as global + type->is_pin_global[pin_count] = port.is_clock || port.is_non_clock_global; + + if (port.is_clock) { + type->clock_pin_indices.push_back(pin_count); + } + + pin_count++; + } + + type->class_inf.push_back(class_inf); + class_range.high++; + } else if (port.equivalent == PortEquivalence::NONE) { + for (k = 0; k < port.num_pins; ++k) { + t_class class_inf; + num_class = (int)type->class_inf.size(); + class_inf.num_pins = 1; + class_inf.pinlist.push_back(pin_count); + class_inf.equivalence = port.equivalent; + + if (port.type == IN_PORT) { + class_inf.type = RECEIVER; + } else { + VTR_ASSERT(port.type == OUT_PORT); + class_inf.type = DRIVER; + } + + type->pin_class[pin_count] = num_class; + // clock pins and other specified global ports are initially specified + // as ignored pins (i.e. connections are not created in the rr_graph and + // nets connected to the port are ignored as well). + type->is_ignored_pin[pin_count] = port.is_clock || port.is_non_clock_global; + // clock pins and other specified global ports are flaged as global + type->is_pin_global[pin_count] = port.is_clock || port.is_non_clock_global; + + if (port.is_clock) { + type->clock_pin_indices.push_back(pin_count); + } + + pin_count++; + + type->class_inf.push_back(class_inf); + class_range.high++; + } + } + } + } + + type->sub_tiles[sub_tile.index].class_range = class_range; + } + + VTR_ASSERT(pin_count == type->num_pins); +} diff --git a/libs/libarchfpga/src/arch_util.h b/libs/libarchfpga/src/arch_util.h index b67240b82d5..d5519f5684c 100644 --- a/libs/libarchfpga/src/arch_util.h +++ b/libs/libarchfpga/src/arch_util.h @@ -60,8 +60,8 @@ void free_type_descriptors(std::vector& type_descriptors); t_port* findPortByName(const char* name, t_pb_type* pb_type, int* high_index, int* low_index); -t_physical_tile_type SetupEmptyPhysicalType(); -t_logical_block_type SetupEmptyLogicalType(); +t_physical_tile_type get_empty_physical_type(std::string name); +t_logical_block_type get_empty_logical_type(std::string name); std::unordered_set get_equivalent_sites_set(t_physical_tile_type_ptr type); @@ -100,4 +100,8 @@ bool pb_type_contains_blif_model(const t_pb_type* pb_type, const std::string& bl const t_pin_to_pin_annotation* find_sequential_annotation(const t_pb_type* pb_type, const t_model_ports* port, enum e_pin_to_pin_delay_annotations annot_type); const t_pin_to_pin_annotation* find_combinational_annotation(const t_pb_type* pb_type, std::string in_port, std::string out_port); +void link_physical_logical_types(std::vector& PhysicalTileTypes, + std::vector& LogicalBlockTypes); + +void setup_pin_classes(t_physical_tile_type* type); #endif diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index 563c2be6bbb..ba6b82f28a9 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -109,7 +109,6 @@ struct t_pin_locs { /* Function prototypes */ /* Populate data */ -static void SetupPinClasses(t_physical_tile_type* PhysicalTileType); static void LoadPinLoc(pugi::xml_node Locations, t_physical_tile_type* type, @@ -264,9 +263,6 @@ int find_switch_by_name(const t_arch& arch, std::string switch_name); e_side string_to_side(std::string side_str); -static void link_physical_logical_types(std::vector& PhysicalTileTypes, - std::vector& LogicalBlockTypes); - template static T* get_type_by_name(const char* type_name, std::vector& types); @@ -450,105 +446,6 @@ void XmlReadArch(const char* ArchFile, * */ -/* Sets up the pin classes for the type. */ -static void SetupPinClasses(t_physical_tile_type* PhysicalTileType) { - int i, k; - int pin_count; - int num_class; - - pugi::xml_node Cur; - - for (i = 0; i < PhysicalTileType->num_pins; i++) { - PhysicalTileType->pin_class.push_back(OPEN); - PhysicalTileType->is_ignored_pin.push_back(true); - PhysicalTileType->is_pin_global.push_back(true); - } - - pin_count = 0; - - t_class_range class_range; - - /* Equivalent pins share the same class, non-equivalent pins belong to different pin classes */ - for (auto& sub_tile : PhysicalTileType->sub_tiles) { - int capacity = sub_tile.capacity.total(); - class_range.low = PhysicalTileType->class_inf.size(); - class_range.high = class_range.low - 1; - for (i = 0; i < capacity; ++i) { - for (const auto& port : sub_tile.ports) { - if (port.equivalent != PortEquivalence::NONE) { - t_class class_inf; - num_class = (int)PhysicalTileType->class_inf.size(); - class_inf.num_pins = port.num_pins; - class_inf.equivalence = port.equivalent; - - if (port.type == IN_PORT) { - class_inf.type = RECEIVER; - } else { - VTR_ASSERT(port.type == OUT_PORT); - class_inf.type = DRIVER; - } - - for (k = 0; k < port.num_pins; ++k) { - class_inf.pinlist.push_back(pin_count); - PhysicalTileType->pin_class[pin_count] = num_class; - // clock pins and other specified global ports are initially specified - // as ignored pins (i.e. connections are not created in the rr_graph and - // nets connected to the port are ignored as well). - PhysicalTileType->is_ignored_pin[pin_count] = port.is_clock || port.is_non_clock_global; - // clock pins and other specified global ports are flaged as global - PhysicalTileType->is_pin_global[pin_count] = port.is_clock || port.is_non_clock_global; - - if (port.is_clock) { - PhysicalTileType->clock_pin_indices.push_back(pin_count); - } - - pin_count++; - } - - PhysicalTileType->class_inf.push_back(class_inf); - class_range.high++; - } else if (port.equivalent == PortEquivalence::NONE) { - for (k = 0; k < port.num_pins; ++k) { - t_class class_inf; - num_class = (int)PhysicalTileType->class_inf.size(); - class_inf.num_pins = 1; - class_inf.pinlist.push_back(pin_count); - class_inf.equivalence = port.equivalent; - - if (port.type == IN_PORT) { - class_inf.type = RECEIVER; - } else { - VTR_ASSERT(port.type == OUT_PORT); - class_inf.type = DRIVER; - } - - PhysicalTileType->pin_class[pin_count] = num_class; - // clock pins and other specified global ports are initially specified - // as ignored pins (i.e. connections are not created in the rr_graph and - // nets connected to the port are ignored as well). - PhysicalTileType->is_ignored_pin[pin_count] = port.is_clock || port.is_non_clock_global; - // clock pins and other specified global ports are flaged as global - PhysicalTileType->is_pin_global[pin_count] = port.is_clock || port.is_non_clock_global; - - if (port.is_clock) { - PhysicalTileType->clock_pin_indices.push_back(pin_count); - } - - pin_count++; - - PhysicalTileType->class_inf.push_back(class_inf); - class_range.high++; - } - } - } - } - - PhysicalTileType->sub_tiles[sub_tile.index].class_range = class_range; - } - - VTR_ASSERT(pin_count == PhysicalTileType->num_pins); -} - static void LoadPinLoc(pugi::xml_node Locations, t_physical_tile_type* type, t_pin_locs* pin_locs, @@ -2883,7 +2780,7 @@ static void ProcessTiles(pugi::xml_node Node, /* Alloc the type list. Need one additional t_type_desctiptors: * 1: empty psuedo-type */ - t_physical_tile_type EMPTY_PHYSICAL_TILE_TYPE = SetupEmptyPhysicalType(); + t_physical_tile_type EMPTY_PHYSICAL_TILE_TYPE = get_empty_physical_type(std::string("EMPTY")); EMPTY_PHYSICAL_TILE_TYPE.index = 0; PhysicalTileTypes.push_back(EMPTY_PHYSICAL_TILE_TYPE); @@ -3542,7 +3439,7 @@ static void ProcessSubTiles(pugi::xml_node Node, int num_pins = PhysicalTileType->num_pins; PhysicalTileType->pinloc.resize({width, height, num_sides}, std::vector(num_pins, false)); - SetupPinClasses(PhysicalTileType); + setup_pin_classes(PhysicalTileType); LoadPinLoc(Cur, PhysicalTileType, &pin_locs, loc_data); } @@ -3556,7 +3453,7 @@ static void ProcessComplexBlocks(vtr::string_internment* strings, pugi::xml_node /* Alloc the type list. Need one additional t_type_desctiptors: * 1: empty psuedo-type */ - t_logical_block_type EMPTY_LOGICAL_BLOCK_TYPE = SetupEmptyLogicalType(); + t_logical_block_type EMPTY_LOGICAL_BLOCK_TYPE = get_empty_logical_type(std::string("EMPTY")); EMPTY_LOGICAL_BLOCK_TYPE.index = 0; LogicalBlockTypes.push_back(EMPTY_LOGICAL_BLOCK_TYPE); @@ -4669,111 +4566,6 @@ e_side string_to_side(std::string side_str) { return side; } -static void link_physical_logical_types(std::vector& PhysicalTileTypes, - std::vector& LogicalBlockTypes) { - for (auto& physical_tile : PhysicalTileTypes) { - if (physical_tile.index == EMPTY_TYPE_INDEX) continue; - - auto eq_sites_set = get_equivalent_sites_set(&physical_tile); - auto equivalent_sites = std::vector(eq_sites_set.begin(), eq_sites_set.end()); - - auto criteria = [&physical_tile](const t_logical_block_type* lhs, const t_logical_block_type* rhs) { - int num_pins = physical_tile.num_inst_pins; - - int lhs_num_logical_pins = lhs->pb_type->num_pins; - int rhs_num_logical_pins = rhs->pb_type->num_pins; - - int lhs_diff_num_pins = num_pins - lhs_num_logical_pins; - int rhs_diff_num_pins = num_pins - rhs_num_logical_pins; - - return lhs_diff_num_pins < rhs_diff_num_pins; - }; - - std::sort(equivalent_sites.begin(), equivalent_sites.end(), criteria); - - for (auto& logical_block : LogicalBlockTypes) { - for (auto site : equivalent_sites) { - if (0 == strcmp(logical_block.name, site->pb_type->name)) { - logical_block.equivalent_tiles.push_back(&physical_tile); - break; - } - } - } - } - - for (auto& logical_block : LogicalBlockTypes) { - if (logical_block.index == EMPTY_TYPE_INDEX) continue; - - auto& equivalent_tiles = logical_block.equivalent_tiles; - - if ((int)equivalent_tiles.size() <= 0) { - archfpga_throw(__FILE__, __LINE__, - "Logical Block %s does not have any equivalent tiles.\n", logical_block.name); - } - - std::unordered_map ignored_pins_check_map; - std::unordered_map global_pins_check_map; - - auto criteria = [&logical_block](const t_physical_tile_type* lhs, const t_physical_tile_type* rhs) { - int num_logical_pins = logical_block.pb_type->num_pins; - - int lhs_num_pins = lhs->num_inst_pins; - int rhs_num_pins = rhs->num_inst_pins; - - int lhs_diff_num_pins = lhs_num_pins - num_logical_pins; - int rhs_diff_num_pins = rhs_num_pins - num_logical_pins; - - return lhs_diff_num_pins < rhs_diff_num_pins; - }; - - std::sort(equivalent_tiles.begin(), equivalent_tiles.end(), criteria); - - for (int pin = 0; pin < logical_block.pb_type->num_pins; pin++) { - for (auto& tile : equivalent_tiles) { - auto direct_maps = tile->tile_block_pin_directs_map.at(logical_block.index); - - for (auto& sub_tile : tile->sub_tiles) { - auto equiv_sites = sub_tile.equivalent_sites; - if (std::find(equiv_sites.begin(), equiv_sites.end(), &logical_block) == equiv_sites.end()) { - continue; - } - - auto direct_map = direct_maps.at(sub_tile.index); - - auto result = direct_map.find(t_logical_pin(pin)); - if (result == direct_map.end()) { - archfpga_throw(__FILE__, __LINE__, - "Logical pin %d not present in pin mapping between Tile %s and Block %s.\n", - pin, tile->name, logical_block.name); - } - - int sub_tile_pin_index = result->second.pin; - int phy_index = sub_tile.sub_tile_to_tile_pin_indices[sub_tile_pin_index]; - - bool is_ignored = tile->is_ignored_pin[phy_index]; - bool is_global = tile->is_pin_global[phy_index]; - - auto ignored_result = ignored_pins_check_map.insert(std::pair(pin, is_ignored)); - if (!ignored_result.second && ignored_result.first->second != is_ignored) { - archfpga_throw(__FILE__, __LINE__, - "Physical Tile %s has a different value for the ignored pin (physical pin: %d, logical pin: %d) " - "different from the corresponding pins of the other equivalent site %s\n.", - tile->name, phy_index, pin, logical_block.name); - } - - auto global_result = global_pins_check_map.insert(std::pair(pin, is_global)); - if (!global_result.second && global_result.first->second != is_global) { - archfpga_throw(__FILE__, __LINE__, - "Physical Tile %s has a different value for the global pin (physical pin: %d, logical pin: %d) " - "different from the corresponding pins of the other equivalent sites\n.", - tile->name, phy_index, pin); - } - } - } - } - } -} - template static T* get_type_by_name(const char* type_name, std::vector& types) { for (auto& type : types) { diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index 794abdbe6d2..8d3d415340d 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -139,8 +139,9 @@ void SetupVPR(const t_options* Options, int num_inputs = 0; int num_outputs = 0; for (auto& type : device_ctx.physical_tile_types) { - if (strcmp(type.name, EMPTY_BLOCK_NAME) == 0) { + if (type.index == 0) { VTR_ASSERT(device_ctx.EMPTY_PHYSICAL_TILE_TYPE == nullptr); + VTR_ASSERT(type.num_pins == 0); device_ctx.EMPTY_PHYSICAL_TILE_TYPE = &type; } @@ -156,7 +157,9 @@ void SetupVPR(const t_options* Options, device_ctx.EMPTY_LOGICAL_BLOCK_TYPE = nullptr; int max_equivalent_tiles = 0; for (const auto& type : device_ctx.logical_block_types) { - if (0 == strcmp(type.name, EMPTY_BLOCK_NAME)) { + if (type.index == 0) { + VTR_ASSERT(device_ctx.EMPTY_LOGICAL_BLOCK_TYPE == nullptr); + VTR_ASSERT(type.pb_type == nullptr); device_ctx.EMPTY_LOGICAL_BLOCK_TYPE = &type; } diff --git a/vpr/src/base/vpr_types.h b/vpr/src/base/vpr_types.h index 944cc9da9c2..322b1b711a0 100644 --- a/vpr/src/base/vpr_types.h +++ b/vpr/src/base/vpr_types.h @@ -90,8 +90,6 @@ enum class ScreenUpdatePriority { constexpr auto EMPTY_BLOCK_ID = ClusterBlockId(-1); constexpr auto INVALID_BLOCK_ID = ClusterBlockId(-2); -constexpr const char* EMPTY_BLOCK_NAME = "EMPTY"; - /* * Files */ From bd207261536ba8e36d78fd89388e5147e79564ff Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 29 Nov 2021 14:03:35 +0100 Subject: [PATCH 02/22] libs: arch: interchange: add logical blocks and physical tile parsing Signed-off-by: Alessandro Comodi --- libs/libarchfpga/src/physical_types.h | 19 + .../src/read_fpga_interchange_arch.cpp | 999 ++++++++++++++++-- 2 files changed, 948 insertions(+), 70 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index ebfe950afc9..41bf53eb1d4 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -1738,6 +1738,20 @@ struct t_lut_cell { std::vector inputs; }; +struct t_lut_bel { + std::string name; + + std::vector input_pins; + std::string output_pin; +}; + +struct t_package_pin { + std::string name; + + std::string site_name; + std::string bel_name; +}; + /* Detailed routing architecture */ struct t_arch { mutable vtr::string_internment strings; @@ -1787,6 +1801,11 @@ struct t_arch { // Luts std::vector lut_cells; + std::vector lut_bels; + + // Package pins + // TODO: add possibility to have multiple packages + std::vector pad_bels; //The name of the switch used for the input connection block (i.e. to //connect routing tracks to block pins). diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index fffd3e864d2..c0cd0dba881 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -11,6 +11,7 @@ #include "vtr_assert.h" #include "vtr_digest.h" #include "vtr_log.h" +#include "vtr_memory.h" #include "vtr_util.h" #include "arch_check.h" @@ -34,6 +35,8 @@ using namespace DeviceResources; using namespace LogicalNetlist; using namespace capnp; +/****************** Utility functions ******************/ + /** * @brief The FPGA interchange timing model includes three different corners (min, typ and max) for each of the two * speed_models (slow and fast). @@ -110,22 +113,115 @@ static float get_corner_value(Device::CornerModel::Reader model, const char* spe return 0.; } +static t_model_ports* get_model_port(t_arch* arch, std::string model, std::string port) { + for (t_model* m : {arch->models, arch->model_library}) { + for (; m != nullptr; m = m->next) { + if (std::string(m->name) != model) + continue; + + for (t_model_ports* p : {m->inputs, m->outputs}) + for (; p != nullptr; p = p->next) + if (std::string(p->name) == port) + return p; + } + } + + archfpga_throw(__FILE__, __LINE__, + "Could not find model port: %s (%s)\n", port.c_str(), model.c_str()); +} + +static t_model* get_model(t_arch* arch, std::string model) { + for (t_model* m : {arch->models, arch->model_library}) + for (; m != nullptr; m = m->next) + if (std::string(m->name) == model) + return m; + + archfpga_throw(__FILE__, __LINE__, + "Could not find model: %s\n", model.c_str()); +} + +template +static T* get_type_by_name(const char* type_name, std::vector& types) { + for (auto& type : types) { + if (0 == strcmp(type.name, type_name)) { + return &type; + } + } + + archfpga_throw(__FILE__, __LINE__, + "Could not find type: %s\n", type_name); +} + +static t_port get_generic_port(t_arch* arch, + t_pb_type* pb_type, + PORTS dir, + std::string name, + std::string model = "", + int num_pins = 1) { + t_port port; + port.parent_pb_type = pb_type; + port.name = vtr::strdup(name.c_str()); + port.num_pins = num_pins; + port.index = 0; + port.absolute_first_pin_index = 0; + port.port_index_by_type = 0; + port.equivalent = PortEquivalence::NONE; + port.type = dir; + port.is_clock = false; + port.model_port = nullptr; + port.port_class = vtr::strdup(nullptr); + port.port_power = (t_port_power*)vtr::calloc(1, sizeof(t_port_power)); + + if (!model.empty()) + port.model_port = get_model_port(arch, model, name); + + return port; +} + +/****************** End Utility functions ******************/ + struct ArchReader { public: - ArchReader(t_arch* arch, Device::Reader& arch_reader, const char* arch_file, std::vector& phys_types, std::vector& logical_types) + ArchReader(t_arch* arch, + Device::Reader& arch_reader, + const char* arch_file, + std::vector& phys_types, + std::vector& logical_types) : arch_(arch) , arch_file_(arch_file) , ar_(arch_reader) , ptypes_(phys_types) , ltypes_(logical_types) { set_arch_file_name(arch_file); + + for (auto cell_bel : ar_.getCellBelMap()) { + auto name = str(cell_bel.getCell()); + for (auto site_bels : cell_bel.getCommonPins()[0].getSiteTypes()) { + auto site_type = str(site_bels.getSiteType()); + for (auto bel : site_bels.getBels()) { + auto bel_name = str(bel); + std::pair key(site_type, bel_name); + bel_cell_mapping_[key].push_back(name); + } + } + } } void read_arch() { + // Preprocess arch information process_luts(); + process_package_pins(); + process_models(); process_device(); + process_blocks(); + process_tiles(); + link_physical_logical_types(ptypes_, ltypes_); + + SyncModelsPbTypes(arch_, ltypes_); + check_models(arch_); + process_layout(); process_switches(); process_segments(); @@ -140,6 +236,152 @@ struct ArchReader { t_default_fc_spec default_fc_; + // siteTypeName, belName , list of cell names + std::map, std::vector> bel_cell_mapping_; + + // Utils + std::string str(int idx) { + return std::string(ar_.getStrList()[idx].cStr()); + } + + int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category) { + int count = 0; + for (auto bel : site.getBels()) + if (bel.getCategory() == category) + count++; + + return count; + } + + Device::BEL::Reader get_bel_reader(Device::SiteType::Reader& site, std::string bel_name) { + for (auto bel : site.getBels()) { + if (str(bel.getName()) == bel_name) + return bel; + } + VTR_ASSERT(0); + } + + std::string get_ic_prefix(Device::SiteType::Reader& site, Device::BEL::Reader& bel) { + return bel.getCategory() == Device::BELCategory::SITE_PORT ? str(site.getName()) : str(bel.getName()); + } + + std::unordered_map> get_ics(Device::SiteType::Reader& site) { + // dictionary: + // - key: interconnect name + // - value: (inputs string, outputs string, interconnect type) + std::unordered_map> ics; + for (auto wire : site.getSiteWires()) { + std::string wire_name = str(wire.getName()); + + // pin name, bel name + std::tuple out_pin; + bool is_mux = false; + for (auto pin : wire.getPins()) { + auto bel_pin = site.getBelPins()[pin]; + auto bel = get_bel_reader(site, str(bel_pin.getBel())); + auto bel_name = get_ic_prefix(site, bel); + + bool is_output = bel_pin.getDir() == LogicalNetlist::Netlist::Direction::OUTPUT; + if (is_output) { + VTR_ASSERT(std::get<1>(out_pin).empty()); + out_pin = std::make_tuple(pin, str(bel_pin.getName()), bel_name); + is_mux = bel.getCategory() == Device::BELCategory::ROUTING; + } + } + VTR_ASSERT(!std::get<1>(out_pin).empty()); + + // Stores all output BELs connected to the same out_pin + std::string pad_bel_name; + std::string pad_bel_pin_name; + bool is_pad = false; + for (auto pin : wire.getPins()) { + if (pin == std::get<0>(out_pin)) + continue; + + auto bel_pin = site.getBelPins()[pin]; + std::string out_bel_pin_name = str(bel_pin.getName()); + + auto bel = get_bel_reader(site, str(bel_pin.getBel())); + auto out_bel_name = get_ic_prefix(site, bel); + + for (auto pad_bel : arch_->pad_bels) { + is_pad = pad_bel.bel_name == out_bel_name || is_pad; + pad_bel_name = pad_bel.bel_name == out_bel_name ? out_bel_name : pad_bel_name; + pad_bel_pin_name = pad_bel.bel_name == out_bel_name ? out_bel_pin_name : pad_bel_pin_name; + } + } + + for (auto pin : wire.getPins()) { + if (pin == std::get<0>(out_pin)) + continue; + + auto bel_pin = site.getBelPins()[pin]; + std::string out_bel_pin_name = str(bel_pin.getName()); + + auto bel = get_bel_reader(site, str(bel_pin.getBel())); + auto out_bel_name = get_ic_prefix(site, bel); + + auto in_bel_name = std::get<2>(out_pin); + auto in_bel_pin_name = std::get<1>(out_pin); + + std::string ostr = out_bel_name + "." + out_bel_pin_name; + std::string istr = in_bel_name + "." + in_bel_pin_name; + + std::string inputs; + std::string outputs; + e_interconnect ic_type; + if (is_mux) { + auto ic_name = in_bel_name; + auto res = ics.emplace(ic_name, std::make_tuple(std::string(), ostr, MUX_INTERC, false)); + + if (!res.second) { + std::tie(inputs, outputs, ic_type, std::ignore) = res.first->second; + outputs += " " + ostr; + res.first->second = std::make_tuple(inputs, outputs, ic_type, false); + } + } else if (bel.getCategory() == Device::BELCategory::ROUTING) { + auto ic_name = str(bel.getName()); + auto res = ics.emplace(ic_name, std::make_tuple(istr, std::string(), MUX_INTERC, false)); + + if (!res.second) { + std::tie(inputs, outputs, ic_type, std::ignore) = res.first->second; + inputs += " " + istr; + res.first->second = std::make_tuple(inputs, outputs, ic_type, false); + } + } else { + auto ic_name = wire_name + "_" + out_bel_pin_name; + if (is_pad && bel.getCategory() == Device::BELCategory::LOGIC) { + if (out_bel_name == pad_bel_name) + ostr += "_in"; + else { // Create new wire to connect PAD output to the BELs input + ic_name = wire_name + "_" + pad_bel_pin_name + "_out"; + istr = pad_bel_name + "." + pad_bel_pin_name + "_out"; + } + } + + auto res = ics.emplace(ic_name, std::make_tuple(istr, ostr, DIRECT_INTERC, is_pad)); + + if (!res.second) { + std::tie(inputs, outputs, ic_type, std::ignore) = res.first->second; + if (inputs.empty()) + inputs = istr; + else + inputs += " " + istr; + + if (outputs.empty()) + outputs = ostr; + else + outputs += " " + ostr; + + res.first->second = std::make_tuple(inputs, outputs, ic_type, is_pad); + } + } + } + } + + return ics; + } + // Model processing void process_models() { // Populate the common library, namely .inputs, .outputs, .names, .latches @@ -152,16 +394,25 @@ struct ArchReader { int model_index = NUM_MODELS_IN_LIBRARY; arch_->models = nullptr; - auto strList = ar_.getStrList(); auto primLib = ar_.getPrimLibs(); for (auto primitive : primLib.getCellDecls()) { - if (std::string(strList[primitive.getLib()]) == std::string("primitives")) { + if (str(primitive.getLib()) == std::string("primitives")) { + std::string prim_name = str(primitive.getName()); + + bool is_lut = false; + for (auto lut_cell : arch_->lut_cells) + is_lut = lut_cell.name == prim_name || is_lut; + + if (is_lut) + continue; + try { temp = new t_model; temp->index = model_index++; temp->never_prune = true; - temp->name = vtr::strdup(std::string(strList[primitive.getName()]).c_str()); + temp->name = vtr::strdup(str(primitive.getName()).c_str()); + ret_map_name = model_name_map.insert(std::pair(temp->name, 0)); if (!ret_map_name.second) { archfpga_throw(arch_file_, __LINE__, @@ -182,11 +433,9 @@ struct ArchReader { arch_->models = temp; } } - return; } void process_model_ports(t_model* model, Netlist::CellDeclaration::Reader primitive) { - auto strList = ar_.getStrList(); auto primLib = ar_.getPrimLibs(); auto portList = primLib.getPortList(); @@ -210,7 +459,7 @@ struct ArchReader { } t_model_ports* model_port = new t_model_ports; model_port->dir = dir; - model_port->name = vtr::strdup(std::string(strList[port.getName()]).c_str()); + model_port->name = vtr::strdup(str(port.getName()).c_str()); // TODO: add parsing of clock port types when the interchange schema allows for it: // https://github.com/chipsalliance/fpga-interchange-schema/issues/66 @@ -254,6 +503,571 @@ struct ArchReader { } } + // Complex Blocks + void process_blocks() { + auto siteTypeList = ar_.getSiteTypeList(); + + int index = 0; + auto EMPTY = get_empty_logical_type(std::string("NULL")); + EMPTY.index = index; + ltypes_.push_back(EMPTY); + + for (auto site : siteTypeList) { + t_logical_block_type ltype; + + std::string name = str(site.getName()); + + // Check for duplicates + auto is_duplicate = [name](t_logical_block_type l) { return std::string(l.name) == name; }; + VTR_ASSERT(std::find_if(ltypes_.begin(), ltypes_.end(), is_duplicate) == ltypes_.end()); + + ltype.name = vtr::strdup(name.c_str()); + ltype.index = ++index; + + auto pb_type = new t_pb_type; + ltype.pb_type = pb_type; + + pb_type->name = vtr::strdup(name.c_str()); + pb_type->num_pb = 1; + process_block_ports(pb_type, name, site); + + // Process modes (for simplicity, only the default mode is allowed for the time being) + pb_type->num_modes = 1; + pb_type->modes = new t_mode[pb_type->num_modes]; + + auto bels = site.getBels(); + auto mode = &pb_type->modes[0]; + mode->parent_pb_type = pb_type; + mode->index = 0; + mode->name = vtr::strdup("default"); + mode->disable_packing = false; + + int bel_count = get_bel_type_count(site, Device::BELCategory::LOGIC); + mode->num_pb_type_children = bel_count; + mode->pb_type_children = new t_pb_type[bel_count]; + + int count = 0; + for (auto bel : bels) { + if (bel.getCategory() != Device::BELCategory::LOGIC) + continue; + + auto bel_name = str(bel.getName()); + std::pair key(name, bel_name); + + auto cell_name = bel_name; + + if (bel_cell_mapping_.find(key) != bel_cell_mapping_.end()) { + VTR_ASSERT(bel_cell_mapping_[key].size() == 1); + cell_name = bel_cell_mapping_[key][0]; + } + + auto leaf_pb_type = new t_pb_type; + leaf_pb_type->name = vtr::strdup(bel_name.c_str()); + leaf_pb_type->num_pb = 1; + leaf_pb_type->parent_mode = mode; + + // TODO: fix this to make it dynamic. This will need the usage of CellBelMapping + + auto find_lut = [cell_name](t_lut_cell l) { return l.name == cell_name; }; + bool is_lut = std::find_if(arch_->lut_cells.begin(), arch_->lut_cells.end(), find_lut) != arch_->lut_cells.end(); + + auto find_pad = [bel_name](t_package_pin p) { return p.bel_name == bel_name; }; + bool is_pad = std::find_if(arch_->pad_bels.begin(), arch_->pad_bels.end(), find_pad) != arch_->pad_bels.end(); + + if (!is_pad) + process_block_ports(leaf_pb_type, cell_name, site, false, is_lut); + + if (is_lut) { + leaf_pb_type->blif_model = nullptr; + process_lut_block(leaf_pb_type); + } else if (is_pad) { + leaf_pb_type->blif_model = nullptr; + process_pad_block(leaf_pb_type, bel, site); + } else { + leaf_pb_type->blif_model = vtr::strdup((std::string(".subckt ") + cell_name).c_str()); + leaf_pb_type->model = get_model(arch_, cell_name); + } + + mode->pb_type_children[count++] = *leaf_pb_type; + } + + process_interconnects(mode, site); + ltypes_.push_back(ltype); + } + } + + void process_lut_block(t_pb_type* lut) { + lut->num_modes = 1; + lut->modes = new t_mode[1]; + + // Check for duplicates + std::string lut_name = lut->name; + auto find_lut = [lut_name](t_lut_bel l) { return l.name == lut_name; }; + auto res = std::find_if(arch_->lut_bels.begin(), arch_->lut_bels.end(), find_lut); + VTR_ASSERT(res != arch_->lut_bels.end()); + auto lut_bel = *res; + + auto mode = &lut->modes[0]; + mode->name = vtr::strdup("lut"); + mode->parent_pb_type = lut; + mode->index = 0; + mode->num_pb_type_children = 1; + mode->pb_type_children = new t_pb_type[1]; + + auto new_leaf = new t_pb_type; + new_leaf->name = vtr::strdup("lut_child"); + new_leaf->num_pb = 1; + new_leaf->parent_mode = mode; + + int num_ports = 2; + new_leaf->num_ports = num_ports; + new_leaf->ports = (t_port*)vtr::calloc(num_ports, sizeof(t_port)); + new_leaf->blif_model = vtr::strdup(MODEL_NAMES); + new_leaf->model = get_model(arch_, std::string(MODEL_NAMES)); + + auto in_size = lut_bel.input_pins.size(); + new_leaf->ports[0] = get_generic_port(arch_, new_leaf, IN_PORT, "in", MODEL_NAMES, in_size); + new_leaf->ports[1] = get_generic_port(arch_, new_leaf, OUT_PORT, "out", MODEL_NAMES); + + mode->pb_type_children[0] = *new_leaf; + + // Num inputs + 1 (output pin) + int num_pins = in_size + 1; + + mode->num_interconnect = num_pins; + mode->interconnect = new t_interconnect[num_pins]; + + for (int i = 0; i < num_pins; i++) { + auto ic = new t_interconnect; + + std::stringstream istr; + std::stringstream ostr; + std::string input_string; + std::string output_string; + + if (i < num_pins - 1) { + istr << lut_bel.input_pins[i]; + ostr << "in[" << i << "]"; + input_string = std::string(lut->name) + std::string(".") + istr.str(); + output_string = std::string(new_leaf->name) + std::string(".") + ostr.str(); + } else { + istr << "out"; + ostr << lut_bel.output_pin; + input_string = std::string(new_leaf->name) + std::string(".") + istr.str(); + output_string = std::string(lut->name) + std::string(".") + ostr.str(); + } + std::string name = istr.str() + std::string("_") + ostr.str(); + ic->name = vtr::strdup(name.c_str()); + ic->type = DIRECT_INTERC; + ic->parent_mode_index = 0; + ic->parent_mode = mode; + ic->input_string = vtr::strdup(input_string.c_str()); + ic->output_string = vtr::strdup(output_string.c_str()); + + mode->interconnect[i] = *ic; + } + } + + void process_pad_block(t_pb_type* pad, Device::BEL::Reader& bel, Device::SiteType::Reader& site) { + // For now, hard-code two modes for pads, so that PADs can either be IPADs or OPADs + pad->num_modes = 2; + pad->modes = new t_mode[2]; + + // Add PAD pb_type ports + VTR_ASSERT(bel.getPins().size() == 1); + std::string pin = str(site.getBelPins()[bel.getPins()[0]].getName()); + std::string ipin = pin + "_in"; + std::string opin = pin + "_out"; + + auto num_ports = 2; + auto ports = new t_port[num_ports]; + pad->ports = ports; + pad->num_ports = pad->num_pins = num_ports; + pad->num_input_pins = 1; + pad->num_output_pins = 1; + + int pin_abs = 0; + int pin_count = 0; + for (auto dir : {IN_PORT, OUT_PORT}) { + int pins_dir_count = 0; + t_port* port = &ports[pin_count]; + + port->parent_pb_type = pad; + port->index = pin_count++; + port->port_index_by_type = pins_dir_count++; + port->absolute_first_pin_index = pin_abs++; + + port->equivalent = PortEquivalence::NONE; + port->num_pins = 1; + port->type = dir; + port->is_clock = false; + + bool is_input = dir == IN_PORT; + port->name = is_input ? vtr::strdup(ipin.c_str()) : vtr::strdup(opin.c_str()); + port->model_port = nullptr; + port->port_class = vtr::strdup(nullptr); + port->port_power = (t_port_power*)vtr::calloc(1, sizeof(t_port_power)); + } + + // OPAD mode + auto omode = &pad->modes[0]; + omode->name = vtr::strdup("opad"); + omode->parent_pb_type = pad; + omode->index = 0; + omode->num_pb_type_children = 1; + omode->pb_type_children = new t_pb_type[1]; + + auto opad = new t_pb_type; + opad->name = vtr::strdup("opad"); + opad->num_pb = 1; + opad->parent_mode = omode; + + num_ports = 1; + opad->num_ports = num_ports; + opad->ports = (t_port*)vtr::calloc(num_ports, sizeof(t_port)); + opad->blif_model = vtr::strdup(MODEL_OUTPUT); + opad->model = get_model(arch_, std::string(MODEL_OUTPUT)); + + opad->ports[0] = get_generic_port(arch_, opad, IN_PORT, "outpad", MODEL_OUTPUT); + omode->pb_type_children[0] = *opad; + + // IPAD mode + auto imode = &pad->modes[1]; + imode->name = vtr::strdup("ipad"); + imode->parent_pb_type = pad; + imode->index = 1; + imode->num_pb_type_children = 1; + imode->pb_type_children = new t_pb_type[1]; + + auto ipad = new t_pb_type; + ipad->name = vtr::strdup("ipad"); + ipad->num_pb = 1; + ipad->parent_mode = imode; + + num_ports = 1; + ipad->num_ports = num_ports; + ipad->ports = (t_port*)vtr::calloc(num_ports, sizeof(t_port)); + ipad->blif_model = vtr::strdup(MODEL_INPUT); + ipad->model = get_model(arch_, std::string(MODEL_INPUT)); + + ipad->ports[0] = get_generic_port(arch_, ipad, OUT_PORT, "inpad", MODEL_INPUT); + imode->pb_type_children[0] = *ipad; + + // Handle interconnects + int num_pins = 1; + + omode->num_interconnect = num_pins; + omode->interconnect = new t_interconnect[num_pins]; + + imode->num_interconnect = num_pins; + imode->interconnect = new t_interconnect[num_pins]; + + std::string opad_istr = std::string(pad->name) + std::string(".") + ipin; + std::string opad_ostr = std::string(opad->name) + std::string(".outpad"); + std::string o_ic_name = std::string(pad->name) + std::string("_") + std::string(opad->name); + + std::string ipad_istr = std::string(ipad->name) + std::string(".inpad"); + std::string ipad_ostr = std::string(pad->name) + std::string(".") + opin; + std::string i_ic_name = std::string(ipad->name) + std::string("_") + std::string(pad->name); + + auto o_ic = new t_interconnect[num_pins]; + auto i_ic = new t_interconnect[num_pins]; + + o_ic->name = vtr::strdup(o_ic_name.c_str()); + o_ic->type = DIRECT_INTERC; + o_ic->parent_mode_index = 0; + o_ic->parent_mode = omode; + o_ic->input_string = vtr::strdup(opad_istr.c_str()); + o_ic->output_string = vtr::strdup(opad_ostr.c_str()); + + i_ic->name = vtr::strdup(i_ic_name.c_str()); + i_ic->type = DIRECT_INTERC; + i_ic->parent_mode_index = 0; + i_ic->parent_mode = imode; + i_ic->input_string = vtr::strdup(ipad_istr.c_str()); + i_ic->output_string = vtr::strdup(ipad_ostr.c_str()); + + omode->interconnect[0] = *o_ic; + imode->interconnect[0] = *i_ic; + } + + void process_block_ports(t_pb_type* pb_type, std::string cell_name, Device::SiteType::Reader& site, bool is_root = true, bool is_model_library = false) { + std::unordered_set names; + + // Prepare data based on pb_type level + std::unordered_map pins; + if (is_root) { + for (auto pin : site.getPins()) { + auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; + pins.emplace(str(pin.getName()), dir); + } + } else { + for (auto bel : site.getBels()) { + if (bel.getCategory() != Device::BELCategory::LOGIC) + continue; + + if (std::string(pb_type->name) != str(bel.getName())) + continue; + + for (auto bel_pin : bel.getPins()) { + auto pin = site.getBelPins()[bel_pin]; + auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; + pins.emplace(str(pin.getName()), dir); + } + } + } + + auto num_ports = pins.size(); + auto ports = new t_port[num_ports]; + pb_type->ports = ports; + pb_type->num_ports = pb_type->num_pins = num_ports; + pb_type->num_input_pins = 0; + pb_type->num_output_pins = 0; + + int pin_abs = 0; + int pin_count = 0; + for (auto dir : {IN_PORT, OUT_PORT}) { + int pins_dir_count = 0; + for (auto pin_pair : pins) { + auto pin_name = pin_pair.first; + auto pin_dir = pin_pair.second; + + if (pin_dir != dir) + continue; + + VTR_ASSERT(names.insert(pin_name).second); + + bool is_input = dir == IN_PORT; + pb_type->num_input_pins += is_input ? 1 : 0; + pb_type->num_output_pins += is_input ? 0 : 1; + + auto port = get_generic_port(arch_, pb_type, dir, pin_name); + ports[pin_count] = port; + port.index = pin_count++; + port.port_index_by_type = pins_dir_count++; + port.absolute_first_pin_index = pin_abs++; + + if (!is_root && !is_model_library) + port.model_port = get_model_port(arch_, cell_name, pin_name); + } + } + } + + void process_interconnects(t_mode* mode, Device::SiteType::Reader& site) { + auto ics = get_ics(site); + auto num_ic = ics.size(); + + mode->num_interconnect = num_ic; + mode->interconnect = new t_interconnect[num_ic]; + + int curr_ic = 0; + std::unordered_set names; + + // Handle site wires, namely direct interconnects + for (auto ic_pair : ics) { + std::string ic_name = ic_pair.first; + + std::string inputs; + std::string outputs; + e_interconnect ic_type; + bool add_pack_pattern; + + std::tie(inputs, outputs, ic_type, add_pack_pattern) = ic_pair.second; + + t_interconnect* ic = &mode->interconnect[curr_ic++]; + + if (add_pack_pattern) { + ic->num_annotations = 1; + // pack pattern + auto pp = new t_pin_to_pin_annotation; + + pp->prop = (int*)vtr::calloc(1, sizeof(int)); + pp->value = (char**)vtr::calloc(1, sizeof(char*)); + + pp->type = E_ANNOT_PIN_TO_PIN_PACK_PATTERN; + pp->format = E_ANNOT_PIN_TO_PIN_CONSTANT; + pp->prop[0] = (int)E_ANNOT_PIN_TO_PIN_PACK_PATTERN_NAME; + pp->value[0] = vtr::strdup(ic_name.c_str()); + pp->input_pins = vtr::strdup(inputs.c_str()); + pp->output_pins = vtr::strdup(outputs.c_str()); + pp->num_value_prop_pairs = 1; + pp->clock = nullptr; + ic->annotations = pp; + } + + // No line num for interconnects, as line num is XML specific + // TODO: probably line_num should be deprecated as it is dependent + // on the input architecture format. + ic->line_num = 0; + ic->type = ic_type; + ic->parent_mode_index = mode->index; + ic->parent_mode = mode; + + VTR_ASSERT(names.insert(ic_name).second); + ic->name = vtr::strdup(ic_name.c_str()); + ic->input_string = vtr::strdup(inputs.c_str()); + ic->output_string = vtr::strdup(outputs.c_str()); + } + } + + // Physical Tiles + void process_tiles() { + auto EMPTY = get_empty_physical_type(std::string("NULL")); + int index = 0; + EMPTY.index = index; + ptypes_.push_back(EMPTY); + + auto tileTypeList = ar_.getTileTypeList(); + + for (auto tile : tileTypeList) { + t_physical_tile_type ptype; + auto name = str(tile.getName()); + + if (name == std::string("NULL")) + continue; + + ptype.name = vtr::strdup(name.c_str()); + ptype.index = ++index; + ptype.width = ptype.height = ptype.area = 1; + ptype.capacity = 1; + + process_sub_tiles(ptype, tile); + + setup_pin_classes(&ptype); + + bool is_pad = false; + for (auto site : tile.getSiteTypes()) { + auto site_type = ar_.getSiteTypeList()[site.getPrimaryType()]; + + for (auto bel : site_type.getBels()) { + auto bel_name = str(bel.getName()); + auto is_pad_func = [bel_name](t_package_pin p) { return p.bel_name == bel_name; }; + auto res = std::find_if(arch_->pad_bels.begin(), arch_->pad_bels.end(), is_pad_func); + + is_pad = res != arch_->pad_bels.end() || is_pad; + } + } + + ptype.is_input_type = ptype.is_output_type = is_pad; + + ptypes_.push_back(ptype); + } + } + + void process_sub_tiles(t_physical_tile_type& type, Device::TileType::Reader& tile) { + // TODO: only one subtile at the moment + auto siteTypeList = ar_.getSiteTypeList(); + for (auto site_in_tile : tile.getSiteTypes()) { + t_sub_tile sub_tile; + + auto site = siteTypeList[site_in_tile.getPrimaryType()]; + + sub_tile.index = 0; + sub_tile.name = vtr::strdup(str(site.getName()).c_str()); + sub_tile.capacity.set(0, 0); + + int port_idx = 0; + int abs_first_pin_idx = 0; + int icount = 0; + int ocount = 0; + for (auto dir : {LogicalNetlist::Netlist::Direction::INPUT, LogicalNetlist::Netlist::Direction::OUTPUT}) { + int port_idx_by_type = 0; + for (auto pin : site.getPins()) { + if (pin.getDir() != dir) + continue; + + t_physical_tile_port port; + + port.name = vtr::strdup(str(pin.getName()).c_str()); + port.equivalent = PortEquivalence::NONE; + port.num_pins = 1; + + sub_tile.sub_tile_to_tile_pin_indices.push_back(port_idx); + port.index = port_idx++; + + port.absolute_first_pin_index = abs_first_pin_idx++; + port.port_index_by_type = port_idx_by_type++; + + if (dir == LogicalNetlist::Netlist::Direction::INPUT) { + port.type = IN_PORT; + icount++; + } else { + port.type = OUT_PORT; + ocount++; + } + + sub_tile.ports.push_back(port); + } + } + + auto pins_size = site.getPins().size(); + sub_tile.num_phy_pins += pins_size * type.capacity; + type.num_pins += pins_size * type.capacity; + type.num_inst_pins += pins_size; + + type.num_input_pins += icount; + type.num_output_pins += ocount; + type.num_receivers += icount * type.capacity; + type.num_drivers += ocount * type.capacity; + + type.pin_width_offset.resize(type.num_pins, 0); + type.pin_height_offset.resize(type.num_pins, 0); + + type.pinloc.resize({1, 1, 4}, std::vector(type.num_pins, false)); + for (e_side side : {TOP, RIGHT, BOTTOM, LEFT}) { + for (int pin = 0; pin < type.num_pins; pin++) { + type.pinloc[0][0][side][pin] = true; + type.pin_width_offset[pin] = 0; + type.pin_height_offset[pin] = 0; + } + } + + auto ltype = get_type_by_name(sub_tile.name, ltypes_); + vtr::bimap directs_map; + + for (int npin = 0; npin < type.num_pins; npin++) { + t_physical_pin physical_pin(npin); + t_logical_pin logical_pin(npin); + + directs_map.insert(logical_pin, physical_pin); + } + + sub_tile.equivalent_sites.push_back(ltype); + + type.tile_block_pin_directs_map[ltype->index][sub_tile.index] = directs_map; + + // Assign FC specs + int iblk_pin = 0; + for (const auto& port : sub_tile.ports) { + t_fc_specification fc_spec; + + fc_spec.seg_index = 0; + + //Apply type and defaults + if (port.type == IN_PORT) { + fc_spec.fc_type = e_fc_type::IN; + fc_spec.fc_value_type = default_fc_.in_value_type; + fc_spec.fc_value = default_fc_.in_value; + } else { + VTR_ASSERT(port.type == OUT_PORT); + fc_spec.fc_type = e_fc_type::OUT; + fc_spec.fc_value_type = default_fc_.out_value_type; + fc_spec.fc_value = default_fc_.out_value; + } + + //Add all the pins from this port + for (int iport_pin = 0; iport_pin < port.num_pins; ++iport_pin) { + int true_physical_blk_pin = sub_tile.sub_tile_to_tile_pin_indices[iblk_pin++]; + fc_spec.pins.push_back(true_physical_blk_pin); + } + + type.fc_specs.push_back(fc_spec); + } + + type.sub_tiles.push_back(sub_tile); + } + } + void process_luts() { // Add LUT Cell definitions // This is helpful to understand which cells are LUTs @@ -271,56 +1085,104 @@ struct ArchReader { arch_->lut_cells.push_back(cell); } + + for (auto lut_elem : lut_def.getLutElements()) { + for (auto lut : lut_elem.getLuts()) { + for (auto bel : lut.getBels()) { + t_lut_bel lut_bel; + + std::string name = bel.getName().cStr(); + lut_bel.name = name; + + // Check for duplicates + auto is_duplicate = [name](t_lut_bel l) { return l.name == name; }; + auto res = std::find_if(arch_->lut_bels.begin(), arch_->lut_bels.end(), is_duplicate); + if (res != arch_->lut_bels.end()) + continue; + + std::vector ipins; + for (auto pin : bel.getInputPins()) + ipins.push_back(pin.cStr()); + + lut_bel.input_pins = ipins; + lut_bel.output_pin = bel.getOutputPin().cStr(); + + arch_->lut_bels.push_back(lut_bel); + } + } + } + } + + void process_package_pins() { + for (auto package : ar_.getPackages()) { + for (auto pin : package.getPackagePins()) { + t_package_pin pckg_pin; + pckg_pin.name = str(pin.getPackagePin()); + + if (pin.getBel().isBel()) + pckg_pin.bel_name = str(pin.getBel().getBel()); + + if (pin.getSite().isSite()) + pckg_pin.site_name = str(pin.getSite().getSite()); + + arch_->pad_bels.push_back(pckg_pin); + } + } } // Layout Processing void process_layout() { - auto strList = ar_.getStrList(); auto tileList = ar_.getTileList(); auto tileTypeList = ar_.getTileTypeList(); - t_grid_def grid_def; - grid_def.width = grid_def.height = 0; - for (auto tile : tileList) { - grid_def.width = std::max(grid_def.width, tile.getCol() + 1); - grid_def.height = std::max(grid_def.height, tile.getRow() + 1); - } + std::vector packages; + for (auto package : ar_.getPackages()) + packages.push_back(str(package.getName())); - grid_def.grid_type = GridDefType::FIXED; - std::string name = std::string(ar_.getName()); + for (auto name : packages) { + t_grid_def grid_def; + grid_def.width = grid_def.height = 0; + for (auto tile : tileList) { + grid_def.width = std::max(grid_def.width, tile.getCol() + 1); + grid_def.height = std::max(grid_def.height, tile.getRow() + 1); + } - if (name == "auto") { - // At the moment, the interchange specifies fixed-layout only architectures, - // and allowing for auto-sizing could potentially be implemented later on - // to allow for experimentation on new architectures. - // For the time being the layout is restricted to be only fixed. - archfpga_throw(arch_file_, __LINE__, - "The name auto is reserved for auto-size layouts; please choose another name"); - } + grid_def.grid_type = GridDefType::FIXED; - grid_def.name = name; - for (auto tile : tileList) { - t_metadata_dict data; - std::string tile_prefix(strList[tile.getName()].cStr()); - auto tileType = tileTypeList[tile.getType()]; - std::string tile_type(strList[tileType.getName()].cStr()); - - size_t pos = tile_prefix.find(tile_type); - if (pos != std::string::npos && pos == 0) - tile_prefix.erase(pos, tile_type.length() + 1); - data.add(arch_->strings.intern_string(vtr::string_view("fasm_prefix")), - arch_->strings.intern_string(vtr::string_view(tile_prefix.c_str()))); - t_grid_loc_def single(tile_type, 1); - single.x.start_expr = tile.getCol(); - single.y.start_expr = tile.getRow(); - single.x.end_expr = single.x.start_expr + " + w - 1"; - single.y.end_expr = single.y.start_expr + " + h - 1"; - single.owned_meta = std::make_unique(data); - single.meta = single.owned_meta.get(); - grid_def.loc_defs.emplace_back(std::move(single)); - } + if (name == "auto") { + // At the moment, the interchange specifies fixed-layout only architectures, + // and allowing for auto-sizing could potentially be implemented later on + // to allow for experimentation on new architectures. + // For the time being the layout is restricted to be only fixed. + archfpga_throw(arch_file_, __LINE__, + "The name auto is reserved for auto-size layouts; please choose another name"); + } + grid_def.name = name; + for (auto tile : tileList) { + t_metadata_dict data; + std::string tile_prefix = str(tile.getName()); + auto tileType = tileTypeList[tile.getType()]; + std::string tile_type = str(tileType.getName()); + + size_t pos = tile_prefix.find(tile_type); + if (pos != std::string::npos && pos == 0) + tile_prefix.erase(pos, tile_type.length() + 1); + data.add(arch_->strings.intern_string(vtr::string_view("fasm_prefix")), + arch_->strings.intern_string(vtr::string_view(tile_prefix.c_str()))); + t_grid_loc_def single(tile_type, 1); + single.x.start_expr = std::to_string(tile.getCol()); + single.y.start_expr = std::to_string(tile.getRow()); + + single.x.end_expr = single.x.start_expr + " + w - 1"; + single.y.end_expr = single.y.start_expr + " + h - 1"; + + single.owned_meta = std::make_unique(data); + single.meta = single.owned_meta.get(); + grid_def.loc_defs.emplace_back(std::move(single)); + } - arch_->grid_layouts.emplace_back(std::move(grid_def)); + arch_->grid_layouts.emplace_back(std::move(grid_def)); + } } void process_device() { @@ -381,15 +1243,14 @@ struct ArchReader { std::string switch_name; arch_->num_switches = num_switches; - auto* switches = arch_->Switches; if (num_switches > 0) { - switches = new t_arch_switch_inf[num_switches]; + arch_->Switches = new t_arch_switch_inf[num_switches]; } float R, Cin, Cint, Cout, Tdel; for (int i = 0; i < (int)num_switches; ++i) { - t_arch_switch_inf& as = switches[i]; + t_arch_switch_inf* as = &arch_->Switches[i]; R = Cin = Cint = Cout = Tdel = 0.0; SwitchType type; @@ -437,32 +1298,30 @@ struct ArchReader { "Switch name '%s' is a reserved name for VPR internal usage!", switch_name.c_str()); } - as.name = vtr::strdup(switch_name.c_str()); - as.set_type(type); - as.mux_trans_size = as.type() == SwitchType::MUX ? 1 : 0; - - as.R = R; - as.Cin = Cin; - as.Cout = Cout; - as.Cinternal = Cint; - as.set_Tdel(t_arch_switch_inf::UNDEFINED_FANIN, Tdel); - - if (as.type() == SwitchType::SHORT || as.type() == SwitchType::PASS_GATE) { - as.buf_size_type = BufferSize::ABSOLUTE; - as.buf_size = 0; - as.power_buffer_type = POWER_BUFFER_TYPE_ABSOLUTE_SIZE; - as.power_buffer_size = 0.; + as->name = vtr::strdup(switch_name.c_str()); + as->set_type(type); + as->mux_trans_size = as->type() == SwitchType::MUX ? 1 : 0; + + as->R = R; + as->Cin = Cin; + as->Cout = Cout; + as->Cinternal = Cint; + as->set_Tdel(t_arch_switch_inf::UNDEFINED_FANIN, Tdel); + + if (as->type() == SwitchType::SHORT || as->type() == SwitchType::PASS_GATE) { + as->buf_size_type = BufferSize::ABSOLUTE; + as->buf_size = 0; + as->power_buffer_type = POWER_BUFFER_TYPE_ABSOLUTE_SIZE; + as->power_buffer_size = 0.; } else { - as.buf_size_type = BufferSize::AUTO; - as.buf_size = 0.; - as.power_buffer_type = POWER_BUFFER_TYPE_AUTO; + as->buf_size_type = BufferSize::AUTO; + as->buf_size = 0.; + as->power_buffer_type = POWER_BUFFER_TYPE_AUTO; } } } void process_segments() { - auto strList = ar_.getStrList(); - // Segment names will be taken from wires connected to pips // They are good representation for nodes std::set wire_names; @@ -479,7 +1338,7 @@ struct ArchReader { for (auto i : wire_names) { // Use default values as we will populate rr_graph with correct values // This segments are just declaration of future use - arch_->Segments[index].name = std::string(strList[i]); + arch_->Segments[index].name = str(i); arch_->Segments[index].length = 1; arch_->Segments[index].frequency = 1; arch_->Segments[index].Rmetal = 0; From 3e969ea74cb50853101145ae1c3c88e8a0030e3d Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 29 Nov 2021 14:04:16 +0100 Subject: [PATCH 03/22] vpr: tests: improve interchange tests Signed-off-by: Alessandro Comodi --- vpr/test/test_interchange_device.cpp | 122 ++++++++++++++++++++++++++- vpr/test/testarch.device | Bin 49806 -> 51495 bytes 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/vpr/test/test_interchange_device.cpp b/vpr/test/test_interchange_device.cpp index 49302728d19..53d9a5edacd 100644 --- a/vpr/test/test_interchange_device.cpp +++ b/vpr/test/test_interchange_device.cpp @@ -18,7 +18,7 @@ TEST_CASE("read_interchange_models", "[vpr]") { FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); - std::unordered_set models = {"IB", "OB", "LUT", "DFF", "GND", "VCC"}; + std::unordered_set models = {"IB", "OB", "DFF", "GND", "VCC"}; // Check that there are exactly the expected models for (auto* model = arch.models; model != nullptr; model = model->next) { @@ -67,4 +67,124 @@ TEST_CASE("read_interchange_layout", "[vpr]") { } } +TEST_CASE("read_interchange_luts", "[vpr]") { + t_arch arch; + std::vector physical_tile_types; + std::vector logical_block_types; + + FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); + + std::unordered_set lut_cell_pins = {"A0", "A1", "A2", "A3"}; + std::unordered_set lut_bel_pins = {"I0", "I1", "I2", "I3"}; + + REQUIRE(arch.lut_cells.size() == 1); + REQUIRE(arch.lut_bels.size() == 1); + + auto lut_cell = arch.lut_cells[0]; + REQUIRE(lut_cell.name == std::string("LUT")); + REQUIRE(lut_cell.init_param == std::string("INIT")); + for (auto lut_pin : lut_cell_pins) + CHECK(std::find(lut_cell.inputs.begin(), lut_cell.inputs.end(), lut_pin) != lut_cell.inputs.end()); + + auto lut_bel = arch.lut_bels[0]; + REQUIRE(lut_bel.name == std::string("LUT")); + REQUIRE(lut_bel.output_pin == std::string("O")); + for (auto lut_pin : lut_bel_pins) + CHECK(std::find(lut_bel.input_pins.begin(), lut_bel.input_pins.end(), lut_pin) != lut_bel.input_pins.end()); +} + +TEST_CASE("read_interchange_pin_packages", "[vpr]") { + t_arch arch; + std::vector physical_tile_types; + std::vector logical_block_types; + + FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); + + // The device architecture file contains 35 perimetral PADs + REQUIRE(arch.pad_bels.size() == 35); + + int ipad = 0; + for (auto pad_bel : arch.pad_bels) { + REQUIRE(pad_bel.name == std::string("A") + std::to_string(ipad++)); + REQUIRE(pad_bel.bel_name == std::string("PAD")); + } +} + +TEST_CASE("read_interchange_tiles", "[vpr]") { + t_arch arch; + std::vector physical_tile_types; + std::vector logical_block_types; + + FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); + + std::unordered_set ptypes = {"NULL", "IOB", "PWR", "CLB"}; + + // Check that there are exactly the expected models + for (auto ptype : physical_tile_types) { + std::string name = ptype.name; + REQUIRE(ptypes.find(name) != ptypes.end()); + ptypes.erase(name); + + if (name == std::string("IOB")) { + CHECK(ptype.is_input_type); + CHECK(ptype.is_output_type); + } + } + + REQUIRE(ptypes.size() == 0); +} + +TEST_CASE("read_interchange_pb_types", "[vpr]") { + t_arch arch; + std::vector physical_tile_types; + std::vector logical_block_types; + + FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); + + std::unordered_set ltypes = {"NULL", "IOPAD", "SLICE", "POWER"}; + + std::unordered_map slice_ports = { + {"L0", PORTS::IN_PORT}, + {"L1", PORTS::IN_PORT}, + {"L2", PORTS::IN_PORT}, + {"L3", PORTS::IN_PORT}, + {"R", PORTS::IN_PORT}, + {"C", PORTS::IN_PORT}, + {"D", PORTS::IN_PORT}, + {"O", PORTS::OUT_PORT}, + {"Q", PORTS::OUT_PORT}}; + + // Check that there are exactly the expected models + for (auto ltype : logical_block_types) { + std::string name = ltype.name; + REQUIRE(ltypes.find(name) != ltypes.end()); + ltypes.erase(name); + + if (ltype.pb_type == nullptr) { + REQUIRE(name == std::string("NULL")); + continue; + } + + bool check_pb_type = name == std::string("SLICE"); + size_t num_visited = 0; + for (auto iport = 0; iport < ltype.pb_type->num_ports; iport++) { + auto port = ltype.pb_type->ports[iport]; + + REQUIRE(port.name != nullptr); + + if (!check_pb_type) + continue; + + auto res = slice_ports.find(std::string(port.name)); + REQUIRE(res != slice_ports.end()); + REQUIRE(res->second == port.type); + num_visited++; + } + + REQUIRE((num_visited == slice_ports.size() || !check_pb_type)); + } + + REQUIRE(ltypes.size() == 0); +} + } // namespace diff --git a/vpr/test/testarch.device b/vpr/test/testarch.device index e9b12b1ac89a3fd52be432b3b106e8ec10f5a3dc..da02924ee50225e4549f816dd3bdf5b071e66348 100644 GIT binary patch literal 51495 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 pWJgTsy9BTg85`;KG{;ew0I$fFqNRa From 9037dca2811f7b568f142633d1648e6ff2ffa85a Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Wed, 17 Nov 2021 17:31:15 +0100 Subject: [PATCH 04/22] libs: arch: interchange: improve bel to cell mapping Signed-off-by: Alessandro Comodi --- libs/libarchfpga/src/physical_types.h | 12 +- .../src/read_fpga_interchange_arch.cpp | 447 ++++++++++++------ vpr/test/lut.netlist | Bin 1493 -> 1683 bytes vpr/test/test_interchange_device.cpp | 75 ++- vpr/test/testarch.device | Bin 51495 -> 56763 bytes 5 files changed, 337 insertions(+), 197 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 41bf53eb1d4..87095a447dc 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -1745,16 +1745,10 @@ struct t_lut_bel { std::string output_pin; }; -struct t_package_pin { - std::string name; - - std::string site_name; - std::string bel_name; -}; - /* Detailed routing architecture */ struct t_arch { mutable vtr::string_internment strings; + std::vector interned_strings; char* architecture_id; //Secure hash digest of the architecture file to uniquely identify this architecture @@ -1803,10 +1797,6 @@ struct t_arch { std::vector lut_cells; std::vector lut_bels; - // Package pins - // TODO: add possibility to have multiple packages - std::vector pad_bels; - //The name of the switch used for the input connection block (i.e. to //connect routing tracks to block pins). //This should correspond to a switch in Switches diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index c0cd0dba881..b53e7d0b5d1 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,18 @@ using namespace DeviceResources; using namespace LogicalNetlist; using namespace capnp; +struct t_package_pin { + std::string name; + + std::string site_name; + std::string bel_name; +}; + +struct t_bel_cell_mapping { + int cell; + std::vector> pins; +}; + /****************** Utility functions ******************/ /** @@ -113,7 +126,7 @@ static float get_corner_value(Device::CornerModel::Reader model, const char* spe return 0.; } -static t_model_ports* get_model_port(t_arch* arch, std::string model, std::string port) { +static t_model_ports* get_model_port(t_arch* arch, std::string model, std::string port, bool fail = true) { for (t_model* m : {arch->models, arch->model_library}) { for (; m != nullptr; m = m->next) { if (std::string(m->name) != model) @@ -126,8 +139,11 @@ static t_model_ports* get_model_port(t_arch* arch, std::string model, std::strin } } - archfpga_throw(__FILE__, __LINE__, - "Could not find model port: %s (%s)\n", port.c_str(), model.c_str()); + if (fail) + archfpga_throw(__FILE__, __LINE__, + "Could not find model port: %s (%s)\n", port.c_str(), model.c_str()); + + return nullptr; } static t_model* get_model(t_arch* arch, std::string model) { @@ -168,6 +184,7 @@ static t_port get_generic_port(t_arch* arch, port.equivalent = PortEquivalence::NONE; port.type = dir; port.is_clock = false; + port.is_non_clock_global = false; port.model_port = nullptr; port.port_class = vtr::strdup(nullptr); port.port_power = (t_port_power*)vtr::calloc(1, sizeof(t_port_power)); @@ -194,16 +211,9 @@ struct ArchReader { , ltypes_(logical_types) { set_arch_file_name(arch_file); - for (auto cell_bel : ar_.getCellBelMap()) { - auto name = str(cell_bel.getCell()); - for (auto site_bels : cell_bel.getCommonPins()[0].getSiteTypes()) { - auto site_type = str(site_bels.getSiteType()); - for (auto bel : site_bels.getBels()) { - auto bel_name = str(bel); - std::pair key(site_type, bel_name); - bel_cell_mapping_[key].push_back(name); - } - } + for (std::string str : ar_.getStrList()) { + auto interned_string = arch_->strings.intern_string(vtr::string_view(str.c_str())); + arch_->interned_strings.push_back(interned_string); } } @@ -211,6 +221,8 @@ struct ArchReader { // Preprocess arch information process_luts(); process_package_pins(); + process_cell_bel_mappings(); + process_constants(); process_models(); process_device(); @@ -236,12 +248,17 @@ struct ArchReader { t_default_fc_spec default_fc_; - // siteTypeName, belName , list of cell names - std::map, std::vector> bel_cell_mapping_; + // Package pins + + // TODO: add possibility to have multiple packages + std::vector pad_bels_; + + // Bel Cell mappings + std::unordered_map> bel_cell_mappings_; // Utils std::string str(int idx) { - return std::string(ar_.getStrList()[idx].cStr()); + return arch_->interned_strings[idx].get(&arch_->strings); } int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category) { @@ -265,6 +282,26 @@ struct ArchReader { return bel.getCategory() == Device::BELCategory::SITE_PORT ? str(site.getName()) : str(bel.getName()); } + bool is_lut(std::string name) { + for (auto cell : arch_->lut_cells) + if (cell.name == name) + return true; + + for (auto bel : arch_->lut_bels) + if (bel.name == name) + return true; + + return false; + } + + bool is_pad(std::string name) { + for (auto pad : pad_bels_) + if (pad.bel_name == name) + return true; + + return false; + } + std::unordered_map> get_ics(Device::SiteType::Reader& site) { // dictionary: // - key: interconnect name @@ -288,6 +325,7 @@ struct ArchReader { is_mux = bel.getCategory() == Device::BELCategory::ROUTING; } } + VTR_ASSERT(!std::get<1>(out_pin).empty()); // Stores all output BELs connected to the same out_pin @@ -304,7 +342,7 @@ struct ArchReader { auto bel = get_bel_reader(site, str(bel_pin.getBel())); auto out_bel_name = get_ic_prefix(site, bel); - for (auto pad_bel : arch_->pad_bels) { + for (auto pad_bel : pad_bels_) { is_pad = pad_bel.bel_name == out_bel_name || is_pad; pad_bel_name = pad_bel.bel_name == out_bel_name ? out_bel_name : pad_bel_name; pad_bel_pin_name = pad_bel.bel_name == out_bel_name ? out_bel_pin_name : pad_bel_pin_name; @@ -362,18 +400,17 @@ struct ArchReader { auto res = ics.emplace(ic_name, std::make_tuple(istr, ostr, DIRECT_INTERC, is_pad)); if (!res.second) { - std::tie(inputs, outputs, ic_type, std::ignore) = res.first->second; - if (inputs.empty()) - inputs = istr; - else - inputs += " " + istr; + std::string ins, outs; + std::tie(ins, outs, ic_type, std::ignore) = res.first->second; + + VTR_ASSERT(ins == istr); - if (outputs.empty()) - outputs = ostr; + if (outs.empty()) + outs = ostr; else - outputs += " " + ostr; + outs += " " + ostr; - res.first->second = std::make_tuple(inputs, outputs, ic_type, is_pad); + res.first->second = std::make_tuple(ins, outs, ic_type, is_pad); } } } @@ -382,6 +419,119 @@ struct ArchReader { return ics; } + /** + * Preprocessors: + * - process_luts: processes information on which cells and bels are LUTs + * - process_package_pins: processes information on the device's pinout and which sites and bels + * contain IO pads + * - process_cell_bel_mapping: processes mappings between a cell and the possible BELs location for that cell + */ + + void process_luts() { + // Add LUT Cell definitions + // This is helpful to understand which cells are LUTs + auto lut_def = ar_.getLutDefinitions(); + + for (auto lut_cell : lut_def.getLutCells()) { + t_lut_cell cell; + cell.name = lut_cell.getCell().cStr(); + for (auto input : lut_cell.getInputPins()) + cell.inputs.push_back(input.cStr()); + + auto equation = lut_cell.getEquation(); + if (equation.isInitParam()) + cell.init_param = equation.getInitParam().cStr(); + + arch_->lut_cells.push_back(cell); + } + + for (auto lut_elem : lut_def.getLutElements()) { + for (auto lut : lut_elem.getLuts()) { + for (auto bel : lut.getBels()) { + t_lut_bel lut_bel; + + std::string name = bel.getName().cStr(); + lut_bel.name = name; + + // Check for duplicates + auto is_duplicate = [name](t_lut_bel l) { return l.name == name; }; + auto res = std::find_if(arch_->lut_bels.begin(), arch_->lut_bels.end(), is_duplicate); + if (res != arch_->lut_bels.end()) + continue; + + std::vector ipins; + for (auto pin : bel.getInputPins()) + ipins.push_back(pin.cStr()); + + lut_bel.input_pins = ipins; + lut_bel.output_pin = bel.getOutputPin().cStr(); + + arch_->lut_bels.push_back(lut_bel); + } + } + } + } + + void process_package_pins() { + for (auto package : ar_.getPackages()) { + for (auto pin : package.getPackagePins()) { + t_package_pin pckg_pin; + pckg_pin.name = str(pin.getPackagePin()); + + if (pin.getBel().isBel()) + pckg_pin.bel_name = str(pin.getBel().getBel()); + + if (pin.getSite().isSite()) + pckg_pin.site_name = str(pin.getSite().getSite()); + + pad_bels_.push_back(pckg_pin); + } + } + } + + void process_cell_bel_mappings() { + for (auto cell_mapping : ar_.getCellBelMap()) { + int cell_name = cell_mapping.getCell(); + + for (auto common_pins : cell_mapping.getCommonPins()) { + std::vector> pins; + + for (auto pin_map : common_pins.getPins()) + pins.emplace_back(pin_map.getCellPin(), pin_map.getBelPin()); + + for (auto site_type_entry : common_pins.getSiteTypes()) { + for (auto bel : site_type_entry.getBels()) { + t_bel_cell_mapping mapping; + + mapping.cell = cell_name; + mapping.pins = pins; + + std::vector maps{mapping}; + + auto res = bel_cell_mappings_.emplace(bel, maps); + if (!res.second) + res.first->second.push_back(mapping); + } + } + } + } + } + + void process_constants() { + auto consts = ar_.getConstants(); + + arch_->gnd_cell = str(consts.getGndCellType()); + arch_->vcc_cell = str(consts.getVccCellType()); + + if (consts.getGndNetName().isName()) + arch_->gnd_net = str(consts.getGndNetName().getName()); + + if (consts.getVccNetName().isName()) + arch_->vcc_net = str(consts.getVccNetName().getName()); + } + + /* end preprocessors */ + // Model processing void process_models() { // Populate the common library, namely .inputs, .outputs, .names, .latches @@ -399,11 +549,7 @@ struct ArchReader { if (str(primitive.getLib()) == std::string("primitives")) { std::string prim_name = str(primitive.getName()); - bool is_lut = false; - for (auto lut_cell : arch_->lut_cells) - is_lut = lut_cell.name == prim_name || is_lut; - - if (is_lut) + if (is_lut(prim_name)) continue; try { @@ -529,7 +675,7 @@ struct ArchReader { pb_type->name = vtr::strdup(name.c_str()); pb_type->num_pb = 1; - process_block_ports(pb_type, name, site); + process_block_ports(pb_type, site); // Process modes (for simplicity, only the default mode is allowed for the time being) pb_type->num_modes = 1; @@ -554,41 +700,23 @@ struct ArchReader { auto bel_name = str(bel.getName()); std::pair key(name, bel_name); - auto cell_name = bel_name; - - if (bel_cell_mapping_.find(key) != bel_cell_mapping_.end()) { - VTR_ASSERT(bel_cell_mapping_[key].size() == 1); - cell_name = bel_cell_mapping_[key][0]; - } - - auto leaf_pb_type = new t_pb_type; - leaf_pb_type->name = vtr::strdup(bel_name.c_str()); - leaf_pb_type->num_pb = 1; - leaf_pb_type->parent_mode = mode; - - // TODO: fix this to make it dynamic. This will need the usage of CellBelMapping + auto mid_pb_type = new t_pb_type; + mid_pb_type->name = vtr::strdup(bel_name.c_str()); + mid_pb_type->num_pb = 1; + mid_pb_type->parent_mode = mode; + mid_pb_type->blif_model = nullptr; - auto find_lut = [cell_name](t_lut_cell l) { return l.name == cell_name; }; - bool is_lut = std::find_if(arch_->lut_cells.begin(), arch_->lut_cells.end(), find_lut) != arch_->lut_cells.end(); + if (!is_pad(bel_name)) + process_block_ports(mid_pb_type, site, false); - auto find_pad = [bel_name](t_package_pin p) { return p.bel_name == bel_name; }; - bool is_pad = std::find_if(arch_->pad_bels.begin(), arch_->pad_bels.end(), find_pad) != arch_->pad_bels.end(); + if (is_lut(bel_name)) + process_lut_block(mid_pb_type); + else if (is_pad(bel_name)) + process_pad_block(mid_pb_type, bel, site); + else + process_generic_block(mid_pb_type, bel); - if (!is_pad) - process_block_ports(leaf_pb_type, cell_name, site, false, is_lut); - - if (is_lut) { - leaf_pb_type->blif_model = nullptr; - process_lut_block(leaf_pb_type); - } else if (is_pad) { - leaf_pb_type->blif_model = nullptr; - process_pad_block(leaf_pb_type, bel, site); - } else { - leaf_pb_type->blif_model = vtr::strdup((std::string(".subckt ") + cell_name).c_str()); - leaf_pb_type->model = get_model(arch_, cell_name); - } - - mode->pb_type_children[count++] = *leaf_pb_type; + mode->pb_type_children[count++] = *mid_pb_type; } process_interconnects(mode, site); @@ -791,15 +919,103 @@ struct ArchReader { imode->interconnect[0] = *i_ic; } - void process_block_ports(t_pb_type* pb_type, std::string cell_name, Device::SiteType::Reader& site, bool is_root = true, bool is_model_library = false) { - std::unordered_set names; + void process_generic_block(t_pb_type* pb_type, Device::BEL::Reader& bel) { + std::string pb_name = std::string(pb_type->name); + + auto maps = bel_cell_mappings_[bel.getName()]; + int num_modes = maps.size(); + + pb_type->num_modes = num_modes; + pb_type->modes = new t_mode[num_modes]; + + int count = 0; + for (auto map : maps) { + int idx = count++; + auto mode = &pb_type->modes[idx]; + auto name = str(map.cell); + mode->name = vtr::strdup(name.c_str()); + mode->parent_pb_type = pb_type; + mode->index = idx; + mode->num_pb_type_children = 1; + mode->pb_type_children = new t_pb_type[1]; + + auto leaf = &mode->pb_type_children[0]; + std::string leaf_name = name == std::string(pb_type->name) ? name + std::string("_leaf") : name; + leaf->name = vtr::strdup(leaf_name.c_str()); + leaf->num_pb = 1; + leaf->parent_mode = mode; + + int num_ports = map.pins.size(); + leaf->num_ports = num_ports; + leaf->ports = (t_port*)vtr::calloc(num_ports, sizeof(t_port)); + leaf->blif_model = vtr::strdup((std::string(".subckt ") + name).c_str()); + leaf->model = get_model(arch_, name); + + mode->num_interconnect = num_ports; + mode->interconnect = new t_interconnect[num_ports]; + std::unordered_map> pins; + int ic_count = 0; + for (auto pin_map : map.pins) { + auto cell_pin = str(pin_map.first); + auto bel_pin = str(pin_map.second); + + if (cell_pin == arch_->vcc_cell || cell_pin == arch_->gnd_cell) + continue; + + std::smatch regex_matches; + std::string pin_suffix; + const std::regex port_regex("([0-9A-Za-z-]+)\\[([0-9]+)\\]"); + if (std::regex_match(cell_pin, regex_matches, port_regex)) { + cell_pin = regex_matches[1].str(); + pin_suffix = std::string("[") + regex_matches[2].str() + std::string("]"); + } + + auto model_port = get_model_port(arch_, name, cell_pin, false); + + if (model_port == nullptr) + continue; + + auto size = model_port->size; + auto dir = model_port->dir; + + pins.emplace(cell_pin, std::make_pair(dir, size)); + + std::string istr, ostr, ic_name; + switch (dir) { + case IN_PORT: + istr = pb_name + std::string(".") + bel_pin; + ostr = leaf_name + std::string(".") + cell_pin + pin_suffix; + break; + case OUT_PORT: + istr = leaf_name + std::string(".") + cell_pin + pin_suffix; + ostr = pb_name + std::string(".") + bel_pin; + break; + default: + VTR_ASSERT(0); + } + ic_name = istr + std::string("_") + ostr; + + auto ic = &mode->interconnect[ic_count++]; + ic->name = vtr::strdup(ic_name.c_str()); + ic->type = DIRECT_INTERC; + ic->parent_mode_index = idx; + ic->parent_mode = mode; + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + } + + create_ports(leaf, pins, name); + } + } + + void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, bool is_root = true) { // Prepare data based on pb_type level - std::unordered_map pins; + std::unordered_map> pins; if (is_root) { for (auto pin : site.getPins()) { auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; - pins.emplace(str(pin.getName()), dir); + pins.emplace(str(pin.getName()), std::make_pair(dir, 1)); } } else { for (auto bel : site.getBels()) { @@ -812,11 +1028,17 @@ struct ArchReader { for (auto bel_pin : bel.getPins()) { auto pin = site.getBelPins()[bel_pin]; auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; - pins.emplace(str(pin.getName()), dir); + pins.emplace(str(pin.getName()), std::make_pair(dir, 1)); } } } + create_ports(pb_type, pins); + } + + void create_ports(t_pb_type* pb_type, std::unordered_map>& pins, std::string model = "") { + std::unordered_set names; + auto num_ports = pins.size(); auto ports = new t_port[num_ports]; pb_type->ports = ports; @@ -830,7 +1052,9 @@ struct ArchReader { int pins_dir_count = 0; for (auto pin_pair : pins) { auto pin_name = pin_pair.first; - auto pin_dir = pin_pair.second; + PORTS pin_dir; + int num_pins; + std::tie(pin_dir, num_pins) = pin_pair.second; if (pin_dir != dir) continue; @@ -841,14 +1065,14 @@ struct ArchReader { pb_type->num_input_pins += is_input ? 1 : 0; pb_type->num_output_pins += is_input ? 0 : 1; - auto port = get_generic_port(arch_, pb_type, dir, pin_name); + auto port = get_generic_port(arch_, pb_type, dir, pin_name, /*string_model=*/"", num_pins); ports[pin_count] = port; port.index = pin_count++; port.port_index_by_type = pins_dir_count++; port.absolute_first_pin_index = pin_abs++; - if (!is_root && !is_model_library) - port.model_port = get_model_port(arch_, cell_name, pin_name); + if (!model.empty()) + port.model_port = get_model_port(arch_, model, pin_name); } } } @@ -935,20 +1159,15 @@ struct ArchReader { setup_pin_classes(&ptype); - bool is_pad = false; + bool is_io = false; for (auto site : tile.getSiteTypes()) { auto site_type = ar_.getSiteTypeList()[site.getPrimaryType()]; - for (auto bel : site_type.getBels()) { - auto bel_name = str(bel.getName()); - auto is_pad_func = [bel_name](t_package_pin p) { return p.bel_name == bel_name; }; - auto res = std::find_if(arch_->pad_bels.begin(), arch_->pad_bels.end(), is_pad_func); - - is_pad = res != arch_->pad_bels.end() || is_pad; - } + for (auto bel : site_type.getBels()) + is_io = is_pad(str(bel.getName())) || is_io; } - ptype.is_input_type = ptype.is_output_type = is_pad; + ptype.is_input_type = ptype.is_output_type = is_io; ptypes_.push_back(ptype); } @@ -1068,68 +1287,6 @@ struct ArchReader { } } - void process_luts() { - // Add LUT Cell definitions - // This is helpful to understand which cells are LUTs - auto lut_def = ar_.getLutDefinitions(); - - for (auto lut_cell : lut_def.getLutCells()) { - t_lut_cell cell; - cell.name = lut_cell.getCell().cStr(); - for (auto input : lut_cell.getInputPins()) - cell.inputs.push_back(input.cStr()); - - auto equation = lut_cell.getEquation(); - if (equation.isInitParam()) - cell.init_param = equation.getInitParam().cStr(); - - arch_->lut_cells.push_back(cell); - } - - for (auto lut_elem : lut_def.getLutElements()) { - for (auto lut : lut_elem.getLuts()) { - for (auto bel : lut.getBels()) { - t_lut_bel lut_bel; - - std::string name = bel.getName().cStr(); - lut_bel.name = name; - - // Check for duplicates - auto is_duplicate = [name](t_lut_bel l) { return l.name == name; }; - auto res = std::find_if(arch_->lut_bels.begin(), arch_->lut_bels.end(), is_duplicate); - if (res != arch_->lut_bels.end()) - continue; - - std::vector ipins; - for (auto pin : bel.getInputPins()) - ipins.push_back(pin.cStr()); - - lut_bel.input_pins = ipins; - lut_bel.output_pin = bel.getOutputPin().cStr(); - - arch_->lut_bels.push_back(lut_bel); - } - } - } - } - - void process_package_pins() { - for (auto package : ar_.getPackages()) { - for (auto pin : package.getPackagePins()) { - t_package_pin pckg_pin; - pckg_pin.name = str(pin.getPackagePin()); - - if (pin.getBel().isBel()) - pckg_pin.bel_name = str(pin.getBel().getBel()); - - if (pin.getSite().isSite()) - pckg_pin.site_name = str(pin.getSite().getSite()); - - arch_->pad_bels.push_back(pckg_pin); - } - } - } - // Layout Processing void process_layout() { auto tileList = ar_.getTileList(); @@ -1167,8 +1324,6 @@ struct ArchReader { size_t pos = tile_prefix.find(tile_type); if (pos != std::string::npos && pos == 0) tile_prefix.erase(pos, tile_type.length() + 1); - data.add(arch_->strings.intern_string(vtr::string_view("fasm_prefix")), - arch_->strings.intern_string(vtr::string_view(tile_prefix.c_str()))); t_grid_loc_def single(tile_type, 1); single.x.start_expr = std::to_string(tile.getCol()); single.y.start_expr = std::to_string(tile.getRow()); diff --git a/vpr/test/lut.netlist b/vpr/test/lut.netlist index d8a49d6feab4741fad8157eb20ec2eba7c7a4cc1..7f2833b66d134875089fce39603bcd60cad55af8 100644 GIT binary patch literal 1683 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 diff --git a/vpr/test/test_interchange_device.cpp b/vpr/test/test_interchange_device.cpp index 53d9a5edacd..2d6bd4ae64f 100644 --- a/vpr/test/test_interchange_device.cpp +++ b/vpr/test/test_interchange_device.cpp @@ -18,7 +18,7 @@ TEST_CASE("read_interchange_models", "[vpr]") { FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); - std::unordered_set models = {"IB", "OB", "DFF", "GND", "VCC"}; + std::unordered_set models = {"IB", "OB", "DFFR", "DFFS", "GND", "VCC"}; // Check that there are exactly the expected models for (auto* model = arch.models; model != nullptr; model = model->next) { @@ -74,39 +74,26 @@ TEST_CASE("read_interchange_luts", "[vpr]") { FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); - std::unordered_set lut_cell_pins = {"A0", "A1", "A2", "A3"}; - std::unordered_set lut_bel_pins = {"I0", "I1", "I2", "I3"}; + std::unordered_set lut_cells = {"LUT1", "LUT2", "LUT3", "LUT4"}; + std::unordered_set lut_bels = {"ALUT", "BLUT"}; + std::unordered_set lut_cell_pins = {"I0", "I1", "I2", "I3"}; + std::unordered_set lut_bel_pins = {"A1", "A2", "A3", "A4"}; - REQUIRE(arch.lut_cells.size() == 1); - REQUIRE(arch.lut_bels.size() == 1); + REQUIRE(arch.lut_cells.size() == 4); + REQUIRE(arch.lut_bels.size() == 2); - auto lut_cell = arch.lut_cells[0]; - REQUIRE(lut_cell.name == std::string("LUT")); - REQUIRE(lut_cell.init_param == std::string("INIT")); - for (auto lut_pin : lut_cell_pins) - CHECK(std::find(lut_cell.inputs.begin(), lut_cell.inputs.end(), lut_pin) != lut_cell.inputs.end()); - - auto lut_bel = arch.lut_bels[0]; - REQUIRE(lut_bel.name == std::string("LUT")); - REQUIRE(lut_bel.output_pin == std::string("O")); - for (auto lut_pin : lut_bel_pins) - CHECK(std::find(lut_bel.input_pins.begin(), lut_bel.input_pins.end(), lut_pin) != lut_bel.input_pins.end()); -} - -TEST_CASE("read_interchange_pin_packages", "[vpr]") { - t_arch arch; - std::vector physical_tile_types; - std::vector logical_block_types; - - FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); - - // The device architecture file contains 35 perimetral PADs - REQUIRE(arch.pad_bels.size() == 35); + for (auto lut_cell : arch.lut_cells) { + CHECK(std::find(lut_cells.begin(), lut_cells.end(), lut_cell.name) != lut_cells.end()); + REQUIRE(lut_cell.init_param == std::string("INIT")); + for (auto lut_pin : lut_cell.inputs) + CHECK(std::find(lut_cell_pins.begin(), lut_cell_pins.end(), lut_pin) != lut_cell_pins.end()); + } - int ipad = 0; - for (auto pad_bel : arch.pad_bels) { - REQUIRE(pad_bel.name == std::string("A") + std::to_string(ipad++)); - REQUIRE(pad_bel.bel_name == std::string("PAD")); + for (auto lut_bel : arch.lut_bels) { + CHECK(std::find(lut_bels.begin(), lut_bels.end(), lut_bel.name) != lut_bels.end()); + REQUIRE(lut_bel.output_pin == std::string("O")); + for (auto lut_pin : lut_bel.input_pins) + CHECK(std::find(lut_bel_pins.begin(), lut_bel_pins.end(), lut_pin) != lut_bel_pins.end()); } } @@ -144,15 +131,23 @@ TEST_CASE("read_interchange_pb_types", "[vpr]") { std::unordered_set ltypes = {"NULL", "IOPAD", "SLICE", "POWER"}; std::unordered_map slice_ports = { - {"L0", PORTS::IN_PORT}, - {"L1", PORTS::IN_PORT}, - {"L2", PORTS::IN_PORT}, - {"L3", PORTS::IN_PORT}, - {"R", PORTS::IN_PORT}, - {"C", PORTS::IN_PORT}, - {"D", PORTS::IN_PORT}, - {"O", PORTS::OUT_PORT}, - {"Q", PORTS::OUT_PORT}}; + {"L0_0", PORTS::IN_PORT}, + {"L1_0", PORTS::IN_PORT}, + {"L2_0", PORTS::IN_PORT}, + {"L3_0", PORTS::IN_PORT}, + {"R_0", PORTS::IN_PORT}, + {"D_0", PORTS::IN_PORT}, + {"O_0", PORTS::OUT_PORT}, + {"Q_0", PORTS::OUT_PORT}, + {"L0_1", PORTS::IN_PORT}, + {"L1_1", PORTS::IN_PORT}, + {"L2_1", PORTS::IN_PORT}, + {"L3_1", PORTS::IN_PORT}, + {"R_1", PORTS::IN_PORT}, + {"D_1", PORTS::IN_PORT}, + {"O_1", PORTS::OUT_PORT}, + {"Q_1", PORTS::OUT_PORT}, + {"CLK", PORTS::IN_PORT}}; // Check that there are exactly the expected models for (auto ltype : logical_block_types) { diff --git a/vpr/test/testarch.device b/vpr/test/testarch.device index da02924ee50225e4549f816dd3bdf5b071e66348..92c32afe1c41ec766c6ca5add8000bf6be55423e 100644 GIT binary patch literal 56763 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 Date: Fri, 3 Dec 2021 09:42:40 +0100 Subject: [PATCH 05/22] libs: arch: fix multiple sites and rr graph gen Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 52 ++++++++++++------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index b53e7d0b5d1..6078460b491 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -227,16 +227,16 @@ struct ArchReader { process_models(); process_device(); + process_layout(); + process_switches(); + process_segments(); + process_blocks(); process_tiles(); link_physical_logical_types(ptypes_, ltypes_); SyncModelsPbTypes(arch_, ltypes_); check_models(arch_); - - process_layout(); - process_switches(); - process_segments(); } private: @@ -255,6 +255,7 @@ struct ArchReader { // Bel Cell mappings std::unordered_map> bel_cell_mappings_; + std::unordered_map segment_name_to_segment_idx; // Utils std::string str(int idx) { @@ -1147,13 +1148,13 @@ struct ArchReader { t_physical_tile_type ptype; auto name = str(tile.getName()); - if (name == std::string("NULL")) + if (name == EMPTY.name) continue; ptype.name = vtr::strdup(name.c_str()); ptype.index = ++index; ptype.width = ptype.height = ptype.area = 1; - ptype.capacity = 1; + ptype.capacity = 0; process_sub_tiles(ptype, tile); @@ -1169,6 +1170,9 @@ struct ArchReader { ptype.is_input_type = ptype.is_output_type = is_io; + ptype.switchblock_locations = vtr::Matrix({{1, 1}}, e_sb_type::FULL); + ptype.switchblock_switch_overrides = vtr::Matrix({{1, 1}}, DEFAULT_SWITCH); + ptypes_.push_back(ptype); } } @@ -1180,15 +1184,20 @@ struct ArchReader { t_sub_tile sub_tile; auto site = siteTypeList[site_in_tile.getPrimaryType()]; + auto pins_to_wires = site_in_tile.getPrimaryPinsToTileWires(); - sub_tile.index = 0; + sub_tile.index = type.capacity; sub_tile.name = vtr::strdup(str(site.getName()).c_str()); - sub_tile.capacity.set(0, 0); + sub_tile.capacity.set(type.capacity, type.capacity); + type.capacity++; int port_idx = 0; int abs_first_pin_idx = 0; int icount = 0; int ocount = 0; + + std::unordered_map port_name_to_wire_name; + int idx = 0; for (auto dir : {LogicalNetlist::Netlist::Direction::INPUT, LogicalNetlist::Netlist::Direction::OUTPUT}) { int port_idx_by_type = 0; for (auto pin : site.getPins()) { @@ -1198,15 +1207,21 @@ struct ArchReader { t_physical_tile_port port; port.name = vtr::strdup(str(pin.getName()).c_str()); + + port_name_to_wire_name[std::string(port.name)] = str(pins_to_wires[idx++]); + port.equivalent = PortEquivalence::NONE; port.num_pins = 1; - sub_tile.sub_tile_to_tile_pin_indices.push_back(port_idx); + sub_tile.sub_tile_to_tile_pin_indices.push_back(type.num_pins + port_idx); port.index = port_idx++; port.absolute_first_pin_index = abs_first_pin_idx++; port.port_index_by_type = port_idx_by_type++; + port.is_clock = false; + port.is_non_clock_global = false; + if (dir == LogicalNetlist::Netlist::Direction::INPUT) { port.type = IN_PORT; icount++; @@ -1220,14 +1235,14 @@ struct ArchReader { } auto pins_size = site.getPins().size(); - sub_tile.num_phy_pins += pins_size * type.capacity; - type.num_pins += pins_size * type.capacity; + sub_tile.num_phy_pins += pins_size; + type.num_pins += pins_size; type.num_inst_pins += pins_size; type.num_input_pins += icount; type.num_output_pins += ocount; - type.num_receivers += icount * type.capacity; - type.num_drivers += ocount * type.capacity; + type.num_receivers += icount; + type.num_drivers += ocount; type.pin_width_offset.resize(type.num_pins, 0); type.pin_height_offset.resize(type.num_pins, 0); @@ -1260,7 +1275,7 @@ struct ArchReader { for (const auto& port : sub_tile.ports) { t_fc_specification fc_spec; - fc_spec.seg_index = 0; + fc_spec.seg_index = segment_name_to_segment_idx[port_name_to_wire_name[std::string(port.name)]]; //Apply type and defaults if (port.type == IN_PORT) { @@ -1325,8 +1340,8 @@ struct ArchReader { if (pos != std::string::npos && pos == 0) tile_prefix.erase(pos, tile_type.length() + 1); t_grid_loc_def single(tile_type, 1); - single.x.start_expr = std::to_string(tile.getCol()); - single.y.start_expr = std::to_string(tile.getRow()); + single.x.start_expr = std::to_string(tile.getCol() + 1); + single.y.start_expr = std::to_string(tile.getRow() + 1); single.x.end_expr = single.x.start_expr + " + w - 1"; single.y.end_expr = single.y.start_expr + " + h - 1"; @@ -1496,8 +1511,8 @@ struct ArchReader { arch_->Segments[index].name = str(i); arch_->Segments[index].length = 1; arch_->Segments[index].frequency = 1; - arch_->Segments[index].Rmetal = 0; - arch_->Segments[index].Cmetal = 0; + arch_->Segments[index].Rmetal = 1e-12; + arch_->Segments[index].Cmetal = 1e-12; arch_->Segments[index].parallel_axis = BOTH_AXIS; // TODO: Only bi-directional segments are created, but it the interchange format @@ -1511,6 +1526,7 @@ struct ArchReader { arch_->Segments[index].sb.resize(2); arch_->Segments[index].sb[0] = true; arch_->Segments[index].sb[1] = true; + segment_name_to_segment_idx[str(i)] = index; ++index; } } From b8a5a88350ef64bcc4da3a1d87da98ca87ec4220 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Fri, 3 Dec 2021 13:58:03 +0100 Subject: [PATCH 06/22] libs: arch: interchange: fix IO pads reading Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 105 +++++++++++------- 1 file changed, 64 insertions(+), 41 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 6078460b491..2a0c946c716 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -252,6 +252,8 @@ struct ArchReader { // TODO: add possibility to have multiple packages std::vector pad_bels_; + std::string pad_out_suffix_ = "_out"; + std::string pad_in_suffix_ = "_in"; // Bel Cell mappings std::unordered_map> bel_cell_mappings_; @@ -312,46 +314,50 @@ struct ArchReader { std::string wire_name = str(wire.getName()); // pin name, bel name - std::tuple out_pin; + std::tuple out_pin_tuple; bool is_mux = false; + int pin_id = OPEN; for (auto pin : wire.getPins()) { auto bel_pin = site.getBelPins()[pin]; - auto bel = get_bel_reader(site, str(bel_pin.getBel())); - auto bel_name = get_ic_prefix(site, bel); + auto dir = bel_pin.getDir(); - bool is_output = bel_pin.getDir() == LogicalNetlist::Netlist::Direction::OUTPUT; - if (is_output) { - VTR_ASSERT(std::get<1>(out_pin).empty()); - out_pin = std::make_tuple(pin, str(bel_pin.getName()), bel_name); - is_mux = bel.getCategory() == Device::BELCategory::ROUTING; + if (dir == LogicalNetlist::Netlist::Direction::OUTPUT) { + pin_id = pin; + break; } + + if (dir == LogicalNetlist::Netlist::Direction::INOUT) + pin_id = pin; } - VTR_ASSERT(!std::get<1>(out_pin).empty()); + VTR_ASSERT(pin_id != OPEN); + + auto out_pin = site.getBelPins()[pin_id]; + auto pin_bel = get_bel_reader(site, str(out_pin.getBel())); + auto pin_bel_name = get_ic_prefix(site, pin_bel); + out_pin_tuple = std::make_tuple(pin_id, str(out_pin.getName()), pin_bel_name); + is_mux = pin_bel.getCategory() == Device::BELCategory::ROUTING; // Stores all output BELs connected to the same out_pin std::string pad_bel_name; std::string pad_bel_pin_name; bool is_pad = false; for (auto pin : wire.getPins()) { - if (pin == std::get<0>(out_pin)) - continue; - auto bel_pin = site.getBelPins()[pin]; - std::string out_bel_pin_name = str(bel_pin.getName()); + std::string bel_pin_name = str(bel_pin.getName()); auto bel = get_bel_reader(site, str(bel_pin.getBel())); - auto out_bel_name = get_ic_prefix(site, bel); + auto bel_name = get_ic_prefix(site, bel); for (auto pad_bel : pad_bels_) { - is_pad = pad_bel.bel_name == out_bel_name || is_pad; - pad_bel_name = pad_bel.bel_name == out_bel_name ? out_bel_name : pad_bel_name; - pad_bel_pin_name = pad_bel.bel_name == out_bel_name ? out_bel_pin_name : pad_bel_pin_name; + is_pad = pad_bel.bel_name == bel_name || is_pad; + pad_bel_name = pad_bel.bel_name == bel_name ? bel_name : pad_bel_name; + pad_bel_pin_name = pad_bel.bel_name == bel_name ? bel_pin_name : pad_bel_pin_name; } } for (auto pin : wire.getPins()) { - if (pin == std::get<0>(out_pin)) + if (pin == std::get<0>(out_pin_tuple)) continue; auto bel_pin = site.getBelPins()[pin]; @@ -360,8 +366,8 @@ struct ArchReader { auto bel = get_bel_reader(site, str(bel_pin.getBel())); auto out_bel_name = get_ic_prefix(site, bel); - auto in_bel_name = std::get<2>(out_pin); - auto in_bel_pin_name = std::get<1>(out_pin); + auto in_bel_name = std::get<2>(out_pin_tuple); + auto in_bel_pin_name = std::get<1>(out_pin_tuple); std::string ostr = out_bel_name + "." + out_bel_pin_name; std::string istr = in_bel_name + "." + in_bel_pin_name; @@ -389,12 +395,12 @@ struct ArchReader { } } else { auto ic_name = wire_name + "_" + out_bel_pin_name; - if (is_pad && bel.getCategory() == Device::BELCategory::LOGIC) { + if (is_pad) { if (out_bel_name == pad_bel_name) - ostr += "_in"; + ostr += pad_in_suffix_; else { // Create new wire to connect PAD output to the BELs input - ic_name = wire_name + "_" + pad_bel_pin_name + "_out"; - istr = pad_bel_name + "." + pad_bel_pin_name + "_out"; + ic_name = wire_name + "_" + pad_bel_pin_name + pad_out_suffix_; + istr = pad_bel_name + "." + pad_bel_pin_name + pad_out_suffix_; } } @@ -805,8 +811,8 @@ struct ArchReader { // Add PAD pb_type ports VTR_ASSERT(bel.getPins().size() == 1); std::string pin = str(site.getBelPins()[bel.getPins()[0]].getName()); - std::string ipin = pin + "_in"; - std::string opin = pin + "_out"; + std::string ipin = pin + pad_in_suffix_; + std::string opin = pin + pad_out_suffix_; auto num_ports = 2; auto ports = new t_port[num_ports]; @@ -1102,22 +1108,16 @@ struct ArchReader { t_interconnect* ic = &mode->interconnect[curr_ic++]; if (add_pack_pattern) { - ic->num_annotations = 1; + auto outs = vtr::split(outputs, " "); + auto ins = vtr::split(inputs, " "); + ic->num_annotations = outs.size(); + ic->annotations = new t_pin_to_pin_annotation[outs.size()]; + VTR_ASSERT(ins.size() == 1); + // pack pattern - auto pp = new t_pin_to_pin_annotation; - - pp->prop = (int*)vtr::calloc(1, sizeof(int)); - pp->value = (char**)vtr::calloc(1, sizeof(char*)); - - pp->type = E_ANNOT_PIN_TO_PIN_PACK_PATTERN; - pp->format = E_ANNOT_PIN_TO_PIN_CONSTANT; - pp->prop[0] = (int)E_ANNOT_PIN_TO_PIN_PACK_PATTERN_NAME; - pp->value[0] = vtr::strdup(ic_name.c_str()); - pp->input_pins = vtr::strdup(inputs.c_str()); - pp->output_pins = vtr::strdup(outputs.c_str()); - pp->num_value_prop_pairs = 1; - pp->clock = nullptr; - ic->annotations = pp; + int index = 0; + for (auto output : outs) + ic->annotations[index++] = get_pack_pattern(ic_name, inputs, output); } // No line num for interconnects, as line num is XML specific @@ -1135,6 +1135,26 @@ struct ArchReader { } } + t_pin_to_pin_annotation get_pack_pattern(std::string ic_name, std::string input, std::string output) { + auto pp = new t_pin_to_pin_annotation; + + std::string pp_name = ic_name + "_" + output; + + pp->prop = (int*)vtr::calloc(1, sizeof(int)); + pp->value = (char**)vtr::calloc(1, sizeof(char*)); + + pp->type = E_ANNOT_PIN_TO_PIN_PACK_PATTERN; + pp->format = E_ANNOT_PIN_TO_PIN_CONSTANT; + pp->prop[0] = (int)E_ANNOT_PIN_TO_PIN_PACK_PATTERN_NAME; + pp->value[0] = vtr::strdup(pp_name.c_str()); + pp->input_pins = vtr::strdup(input.c_str()); + pp->output_pins = vtr::strdup(output.c_str()); + pp->num_value_prop_pairs = 1; + pp->clock = nullptr; + + return *pp; + } + // Physical Tiles void process_tiles() { auto EMPTY = get_empty_physical_type(std::string("NULL")); @@ -1319,6 +1339,9 @@ struct ArchReader { grid_def.height = std::max(grid_def.height, tile.getRow() + 1); } + grid_def.width += 2; + grid_def.height += 2; + grid_def.grid_type = GridDefType::FIXED; if (name == "auto") { From a06b40656c1a913418df5ecf6dd1965dd438770b Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Fri, 3 Dec 2021 14:19:01 +0100 Subject: [PATCH 07/22] libs: arch: interchange: add workarounds for RR graph generation These workarounds can be removed once the RR graph generation is in place Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 2a0c946c716..3fc1e814bf8 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -1295,7 +1295,10 @@ struct ArchReader { for (const auto& port : sub_tile.ports) { t_fc_specification fc_spec; - fc_spec.seg_index = segment_name_to_segment_idx[port_name_to_wire_name[std::string(port.name)]]; + // FIXME: Use always one segment for the time being. + // Can use the right segment for this IOPIN as soon + // as the RR graph reading from the interchange is complete. + fc_spec.seg_index = 0; //Apply type and defaults if (port.type == IN_PORT) { @@ -1525,10 +1528,18 @@ struct ArchReader { wire_names.insert(wires[pip.getWire1()]); } } - int num_seg = wire_names.size(); + + // FIXME: have only one segment type for the time being, so that + // the RR graph generation is correct. + // This can be removed once the RR graph reader from the interchange + // device is ready and functional. + int num_seg = 1; //wire_names.size(); + arch_->Segments.resize(num_seg); uint32_t index = 0; for (auto i : wire_names) { + if (index >= num_seg) break; + // Use default values as we will populate rr_graph with correct values // This segments are just declaration of future use arch_->Segments[index].name = str(i); From 98a1718520ed36a65ee0678575d62eb10df4e354 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Fri, 3 Dec 2021 15:57:14 +0100 Subject: [PATCH 08/22] vpr: test: interchange: fix grid width and height Signed-off-by: Alessandro Comodi --- vpr/test/test_interchange_device.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vpr/test/test_interchange_device.cpp b/vpr/test/test_interchange_device.cpp index 2d6bd4ae64f..ac939714f1a 100644 --- a/vpr/test/test_interchange_device.cpp +++ b/vpr/test/test_interchange_device.cpp @@ -50,8 +50,8 @@ TEST_CASE("read_interchange_layout", "[vpr]") { auto& gd = arch.grid_layouts[0]; REQUIRE(gd.grid_type == GridDefType::FIXED); - REQUIRE(gd.height == 10); - REQUIRE(gd.width == 10); + REQUIRE(gd.height == 12); + REQUIRE(gd.width == 12); std::unordered_map tile_types({{"NULL", false}, {"PWR", false}, {"IOB", false}, {"CLB", false}}); for (auto& loc : gd.loc_defs) { From 5dca7178310f380a8d8f2f2f2a7631f2bad57014 Mon Sep 17 00:00:00 2001 From: Maciej Dudek Date: Mon, 6 Dec 2021 11:57:00 +0100 Subject: [PATCH 09/22] vpr: base: remove cStr() casts from interchange netlist reader Signed-off-by: Maciej Dudek --- vpr/src/base/read_interchange_netlist.cpp | 28 +++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vpr/src/base/read_interchange_netlist.cpp b/vpr/src/base/read_interchange_netlist.cpp index 55237e42723..7ffe56ac399 100644 --- a/vpr/src/base/read_interchange_netlist.cpp +++ b/vpr/src/base/read_interchange_netlist.cpp @@ -87,7 +87,7 @@ struct NetlistReader { auto top_cell_decl = nr_.getCellDecls()[top_cell_instance_.getCell()]; for (auto top_port : top_cell_decl.getPorts()) { auto port = nr_.getPortList()[top_port]; - auto name = std::string(str_list[port.getName()].cStr()); + std::string name = str_list[port.getName()]; auto dir = port.getDir(); int bus_size, start_bit; @@ -145,7 +145,7 @@ struct NetlistReader { bool is_lut; int width; std::string init_param; - std::tie(is_lut, width, init_param) = is_lut_cell(str_list[cell.getName()].cStr()); + std::tie(is_lut, width, init_param) = is_lut_cell(str_list[cell.getName()]); if (is_lut) insts.emplace_back(cell_inst, width, init_param); @@ -157,12 +157,12 @@ struct NetlistReader { std::string init_param; std::tie(inst_idx, lut_width, init_param) = inst; - std::string inst_name = str_list[inst_list[inst_idx].getName()].cStr(); + std::string inst_name = str_list[inst_list[inst_idx].getName()]; auto props = inst_list[inst_idx].getPropMap().getEntries(); std::vector init; for (auto entry : props) { - if (std::string(str_list[entry.getKey()].cStr()) != init_param) + if (str_list[entry.getKey()] != init_param) continue; // TODO: export this to a library function to have generic parameter decoding @@ -170,7 +170,7 @@ struct NetlistReader { const std::regex vhex_regex("[0-9]+'h([0-9A-Z]+)"); const std::regex vbit_regex("[0-9]+'b([0-9A-Z]+)"); const std::regex bit_regex("[0-1]+"); - std::string init_str = std::string(str_list[entry.getTextValue()].cStr()); + std::string init_str = str_list[entry.getTextValue()]; std::smatch regex_matches; // Fill the init vector @@ -249,7 +249,7 @@ struct NetlistReader { std::unordered_map port_net_map; for (auto net : top_cell.getNets()) { - std::string net_name = str_list[net.getName()].cStr(); + std::string net_name = str_list[net.getName()]; for (auto port : net.getPortInsts()) { if (!port.isInst() || port.getInst() != inst_idx) continue; @@ -291,7 +291,7 @@ struct NetlistReader { auto cell = decl_list[inst_list[cell_inst].getCell()]; bool is_lut; - std::tie(is_lut, std::ignore, std::ignore) = is_lut_cell(str_list[cell.getName()].cStr()); + std::tie(is_lut, std::ignore, std::ignore) = is_lut_cell(str_list[cell.getName()]); if (!is_lut) insts.emplace_back(cell_inst, inst_list[cell_inst].getCell()); @@ -301,10 +301,10 @@ struct NetlistReader { auto inst_idx = inst_pair.first; auto cell_idx = inst_pair.second; - auto model_name = str_list[decl_list[cell_idx].getName()].cStr(); + auto model_name = str_list[decl_list[cell_idx].getName()]; const t_model* blk_model = find_model(model_name); - std::string inst_name = str_list[inst_list[inst_idx].getName()].cStr(); + std::string inst_name = str_list[inst_list[inst_idx].getName()]; VTR_ASSERT(inst_name.empty() == 0); //The name for every block should be unique, check that there is no name conflict @@ -320,9 +320,9 @@ struct NetlistReader { auto port_net_map = get_port_net_map(inst_idx); auto cell = decl_list[inst_list[inst_idx].getCell()]; - if (std::string(str_list[cell.getName()].cStr()) == arch_.vcc_cell) + if (str_list[cell.getName()] == arch_.vcc_cell) inst_name = arch_.vcc_cell; - else if (std::string(str_list[cell.getName()].cStr()) == arch_.gnd_cell) + else if (str_list[cell.getName()] == arch_.gnd_cell) inst_name = arch_.gnd_cell; if (main_netlist_.find_block(inst_name)) @@ -343,10 +343,10 @@ struct NetlistReader { net_name = arch_.gnd_net; auto port = port_list[port_idx]; - auto port_name = str_list[port.getName()].cStr(); + auto port_name = str_list[port.getName()]; //Check for consistency between model and ports - const t_model_ports* model_port = find_model_port(blk_model, std::string(port_name)); + const t_model_ports* model_port = find_model_port(blk_model, port_name); VTR_ASSERT(model_port); //Determine the pin type @@ -443,7 +443,7 @@ struct NetlistReader { auto str_list = nr_.getStrList(); std::unordered_map, std::string, vtr::hash_pair> map; for (auto net : top_cell.getNets()) { - std::string net_name = str_list[net.getName()].cStr(); + std::string net_name = str_list[net.getName()]; for (auto port : net.getPortInsts()) { if (!port.isInst() || port.getInst() != inst_idx) continue; From 06c8abdb7bd064fe13a5a9ecba073caee0ff45f8 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 6 Dec 2021 17:13:32 +0100 Subject: [PATCH 10/22] libs: arch: interchange: fix and enhance to start reading in xc7 arch Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 186 +++++++++++++++--- 1 file changed, 156 insertions(+), 30 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 3fc1e814bf8..309f82034fe 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -46,6 +46,14 @@ struct t_package_pin { struct t_bel_cell_mapping { int cell; std::vector> pins; + + bool operator==(const t_bel_cell_mapping& other) const { + return cell == other.cell; + } + + bool operator<(const t_bel_cell_mapping& other) const { + return cell < other.cell; + } }; /****************** Utility functions ******************/ @@ -223,6 +231,7 @@ struct ArchReader { process_package_pins(); process_cell_bel_mappings(); process_constants(); + process_bels_and_sites(); process_models(); process_device(); @@ -231,8 +240,9 @@ struct ArchReader { process_switches(); process_segments(); - process_blocks(); + process_sites(); process_tiles(); + link_physical_logical_types(ptypes_, ltypes_); SyncModelsPbTypes(arch_, ltypes_); @@ -248,15 +258,21 @@ struct ArchReader { t_default_fc_spec default_fc_; + std::string bel_dedup_suffix_ = "_bel"; + + std::unordered_set take_bels_; + std::unordered_set take_sites_; + // Package pins // TODO: add possibility to have multiple packages - std::vector pad_bels_; + std::vector package_pins_; + std::unordered_set pad_bels_; std::string pad_out_suffix_ = "_out"; std::string pad_in_suffix_ = "_in"; // Bel Cell mappings - std::unordered_map> bel_cell_mappings_; + std::unordered_map> bel_cell_mappings_; std::unordered_map segment_name_to_segment_idx; // Utils @@ -267,7 +283,7 @@ struct ArchReader { int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category) { int count = 0; for (auto bel : site.getBels()) - if (bel.getCategory() == category) + if (bel.getCategory() == category && take_bels_.count(bel.getName()) != 0) count++; return count; @@ -282,7 +298,13 @@ struct ArchReader { } std::string get_ic_prefix(Device::SiteType::Reader& site, Device::BEL::Reader& bel) { - return bel.getCategory() == Device::BELCategory::SITE_PORT ? str(site.getName()) : str(bel.getName()); + if (bel.getCategory() == Device::BELCategory::SITE_PORT) + return str(site.getName()); + + auto site_name = str(site.getName()); + auto bel_name = str(bel.getName()); + + return site_name == bel_name ? bel_name + bel_dedup_suffix_ : bel_name; } bool is_lut(std::string name) { @@ -298,11 +320,7 @@ struct ArchReader { } bool is_pad(std::string name) { - for (auto pad : pad_bels_) - if (pad.bel_name == name) - return true; - - return false; + return pad_bels_.count(name) != 0; } std::unordered_map> get_ics(Device::SiteType::Reader& site) { @@ -338,7 +356,7 @@ struct ArchReader { out_pin_tuple = std::make_tuple(pin_id, str(out_pin.getName()), pin_bel_name); is_mux = pin_bel.getCategory() == Device::BELCategory::ROUTING; - // Stores all output BELs connected to the same out_pin + // IO PADs require special handling std::string pad_bel_name; std::string pad_bel_pin_name; bool is_pad = false; @@ -349,7 +367,7 @@ struct ArchReader { auto bel = get_bel_reader(site, str(bel_pin.getBel())); auto bel_name = get_ic_prefix(site, bel); - for (auto pad_bel : pad_bels_) { + for (auto pad_bel : package_pins_) { is_pad = pad_bel.bel_name == bel_name || is_pad; pad_bel_name = pad_bel.bel_name == bel_name ? bel_name : pad_bel_name; pad_bel_pin_name = pad_bel.bel_name == bel_name ? bel_pin_name : pad_bel_pin_name; @@ -428,12 +446,50 @@ struct ArchReader { /** * Preprocessors: + * - process_bels_and_sites: information on whether sites and bels need to be expanded in pb types * - process_luts: processes information on which cells and bels are LUTs * - process_package_pins: processes information on the device's pinout and which sites and bels * contain IO pads * - process_cell_bel_mapping: processes mappings between a cell and the possible BELs location for that cell + * - process_constants: processes constants cell and net names */ + void process_bels_and_sites() { + auto tiles = ar_.getTileList(); + auto tile_types = ar_.getTileTypeList(); + auto site_types = ar_.getSiteTypeList(); + + for (auto tile : tiles) { + auto tile_type = tile_types[tile.getType()]; + + for (auto site : tile.getSites()) { + auto site_type_in_tile = tile_type.getSiteTypes()[site.getType()]; + auto site_type = site_types[site_type_in_tile.getPrimaryType()]; + auto site_pins = site_type.getBelPins(); + + bool found = false; + for (auto bel : site_type.getBels()) { + auto bel_name = bel.getName(); + bool res = bel_cell_mappings_.find(bel_name) != bel_cell_mappings_.end(); + + found |= res; + + bool has_inout = false; + for (auto pin : bel.getPins()) + has_inout |= site_pins[pin].getDir() == LogicalNetlist::Netlist::Direction::INOUT; + + if ((res && !has_inout) || is_pad(str(bel_name))) + take_bels_.insert(bel_name); + } + + if (found) + take_sites_.insert(site_type.getName()); + + // TODO: Enable also alternative site types handling + } + } + } + void process_luts() { // Add LUT Cell definitions // This is helpful to understand which cells are LUTs @@ -485,21 +541,39 @@ struct ArchReader { t_package_pin pckg_pin; pckg_pin.name = str(pin.getPackagePin()); - if (pin.getBel().isBel()) + if (pin.getBel().isBel()) { pckg_pin.bel_name = str(pin.getBel().getBel()); + pad_bels_.insert(pckg_pin.bel_name); + } if (pin.getSite().isSite()) pckg_pin.site_name = str(pin.getSite().getSite()); - pad_bels_.push_back(pckg_pin); + package_pins_.push_back(pckg_pin); } } } void process_cell_bel_mappings() { + auto primLib = ar_.getPrimLibs(); + for (auto cell_mapping : ar_.getCellBelMap()) { int cell_name = cell_mapping.getCell(); + int found_prim = false; + for (auto primitive : primLib.getCellDecls()) { + bool is_prim = str(primitive.getLib()) == std::string("primitives"); + bool is_cell = cell_name == primitive.getName(); + + if (is_prim && is_cell) { + found_prim = true; + break; + } + } + + if (!found_prim) + continue; + for (auto common_pins : cell_mapping.getCommonPins()) { std::vector> pins; @@ -513,11 +587,11 @@ struct ArchReader { mapping.cell = cell_name; mapping.pins = pins; - std::vector maps{mapping}; + std::set maps{mapping}; auto res = bel_cell_mappings_.emplace(bel, maps); if (!res.second) - res.first->second.push_back(mapping); + res.first->second.insert(mapping); } } } @@ -559,6 +633,22 @@ struct ArchReader { if (is_lut(prim_name)) continue; + bool has_bel = false; + for (auto bel_cell_map : bel_cell_mappings_) { + auto bel_name = bel_cell_map.first; + + bool take_bel = take_bels_.count(bel_name) != 0; + + if (!take_bel || is_lut(str(bel_name))) + continue; + + for (auto map : bel_cell_map.second) + has_bel |= (int)primitive.getName() == map.cell; + } + + if (!has_bel) + continue; + try { temp = new t_model; temp->index = model_index++; @@ -657,7 +747,7 @@ struct ArchReader { } // Complex Blocks - void process_blocks() { + void process_sites() { auto siteTypeList = ar_.getSiteTypeList(); int index = 0; @@ -666,10 +756,18 @@ struct ArchReader { ltypes_.push_back(EMPTY); for (auto site : siteTypeList) { + auto bels = site.getBels(); + + if (bels.size() == 0) + continue; + t_logical_block_type ltype; std::string name = str(site.getName()); + if (take_sites_.count(site.getName()) == 0) + continue; + // Check for duplicates auto is_duplicate = [name](t_logical_block_type l) { return std::string(l.name) == name; }; VTR_ASSERT(std::find_if(ltypes_.begin(), ltypes_.end(), is_duplicate) == ltypes_.end()); @@ -688,7 +786,6 @@ struct ArchReader { pb_type->num_modes = 1; pb_type->modes = new t_mode[pb_type->num_modes]; - auto bels = site.getBels(); auto mode = &pb_type->modes[0]; mode->parent_pb_type = pb_type; mode->index = 0; @@ -700,21 +797,29 @@ struct ArchReader { mode->pb_type_children = new t_pb_type[bel_count]; int count = 0; + VTR_LOG("Site: %s\n", str(site.getName()).c_str()); for (auto bel : bels) { if (bel.getCategory() != Device::BELCategory::LOGIC) continue; + if (take_bels_.count(bel.getName()) == 0) + continue; + + VTR_LOG(" - %s\n", str(bel.getName()).c_str()); + auto bel_name = str(bel.getName()); std::pair key(name, bel_name); auto mid_pb_type = new t_pb_type; - mid_pb_type->name = vtr::strdup(bel_name.c_str()); + std::string mid_pb_type_name = bel_name == name ? bel_name + bel_dedup_suffix_ : bel_name; + + mid_pb_type->name = vtr::strdup(mid_pb_type_name.c_str()); mid_pb_type->num_pb = 1; mid_pb_type->parent_mode = mode; mid_pb_type->blif_model = nullptr; if (!is_pad(bel_name)) - process_block_ports(mid_pb_type, site, false); + process_block_ports(mid_pb_type, site, bel.getName()); if (is_lut(bel_name)) process_lut_block(mid_pb_type); @@ -952,7 +1057,18 @@ struct ArchReader { leaf->num_pb = 1; leaf->parent_mode = mode; - int num_ports = map.pins.size(); + // Pre-count pins + int ic_count = 0; + for (auto pin_map : map.pins) { + auto cell_pin = str(pin_map.first); + + if (cell_pin == arch_->vcc_cell || cell_pin == arch_->gnd_cell) + continue; + + ic_count++; + } + + int num_ports = ic_count; leaf->num_ports = num_ports; leaf->ports = (t_port*)vtr::calloc(num_ports, sizeof(t_port)); leaf->blif_model = vtr::strdup((std::string(".subckt ") + name).c_str()); @@ -961,7 +1077,7 @@ struct ArchReader { mode->num_interconnect = num_ports; mode->interconnect = new t_interconnect[num_ports]; std::unordered_map> pins; - int ic_count = 0; + ic_count = 0; for (auto pin_map : map.pins) { auto cell_pin = str(pin_map.first); auto bel_pin = str(pin_map.second); @@ -977,10 +1093,7 @@ struct ArchReader { pin_suffix = std::string("[") + regex_matches[2].str() + std::string("]"); } - auto model_port = get_model_port(arch_, name, cell_pin, false); - - if (model_port == nullptr) - continue; + auto model_port = get_model_port(arch_, name, cell_pin); auto size = model_port->size; auto dir = model_port->dir; @@ -1016,10 +1129,10 @@ struct ArchReader { } } - void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, bool is_root = true) { + void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, int bel_name = OPEN) { // Prepare data based on pb_type level std::unordered_map> pins; - if (is_root) { + if (bel_name == OPEN) { for (auto pin : site.getPins()) { auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; pins.emplace(str(pin.getName()), std::make_pair(dir, 1)); @@ -1029,7 +1142,7 @@ struct ArchReader { if (bel.getCategory() != Device::BELCategory::LOGIC) continue; - if (std::string(pb_type->name) != str(bel.getName())) + if ((int)bel.getName() != bel_name) continue; for (auto bel_pin : bel.getPins()) { @@ -1163,6 +1276,7 @@ struct ArchReader { ptypes_.push_back(EMPTY); auto tileTypeList = ar_.getTileTypeList(); + auto siteTypeList = ar_.getSiteTypeList(); for (auto tile : tileTypeList) { t_physical_tile_type ptype; @@ -1171,6 +1285,14 @@ struct ArchReader { if (name == EMPTY.name) continue; + bool has_valid_sites = false; + + for (auto site_type : tile.getSiteTypes()) + has_valid_sites |= take_sites_.count(siteTypeList[site_type.getPrimaryType()].getName()) != 0; + + if (!has_valid_sites) + continue; + ptype.name = vtr::strdup(name.c_str()); ptype.index = ++index; ptype.width = ptype.height = ptype.area = 1; @@ -1185,7 +1307,7 @@ struct ArchReader { auto site_type = ar_.getSiteTypeList()[site.getPrimaryType()]; for (auto bel : site_type.getBels()) - is_io = is_pad(str(bel.getName())) || is_io; + is_io |= is_pad(str(bel.getName())); } ptype.is_input_type = ptype.is_output_type = is_io; @@ -1204,6 +1326,10 @@ struct ArchReader { t_sub_tile sub_tile; auto site = siteTypeList[site_in_tile.getPrimaryType()]; + + if (take_sites_.count(site.getName()) == 0) + continue; + auto pins_to_wires = site_in_tile.getPrimaryPinsToTileWires(); sub_tile.index = type.capacity; From 5d173667bb4777c1f7ae5d67f1e7b19ad69628a3 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Tue, 7 Dec 2021 16:20:14 +0100 Subject: [PATCH 11/22] libs: arch: interchange: allow cascading routing bels Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 228 ++++++++++++------ 1 file changed, 153 insertions(+), 75 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 309f82034fe..42f158b3354 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -203,6 +203,17 @@ static t_port get_generic_port(t_arch* arch, return port; } +static bool block_port_exists(t_pb_type* pb_type, std::string port_name) { + for (int iport = 0; iport < pb_type->num_ports; iport++) { + const t_port port = pb_type->ports[iport]; + + if (std::string(port.name) == port_name) + return true; + } + + return false; +} + /****************** End Utility functions ******************/ struct ArchReader { @@ -282,9 +293,14 @@ struct ArchReader { int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category) { int count = 0; - for (auto bel : site.getBels()) - if (bel.getCategory() == category && take_bels_.count(bel.getName()) != 0) + for (auto bel : site.getBels()) { + bool is_logic = category == Device::BELCategory::LOGIC; + + bool skip_bel = is_logic && take_bels_.count(bel.getName()) == 0; + + if (bel.getCategory() == category && !skip_bel) count++; + } return count; } @@ -332,8 +348,6 @@ struct ArchReader { std::string wire_name = str(wire.getName()); // pin name, bel name - std::tuple out_pin_tuple; - bool is_mux = false; int pin_id = OPEN; for (auto pin : wire.getPins()) { auto bel_pin = site.getBelPins()[pin]; @@ -351,10 +365,8 @@ struct ArchReader { VTR_ASSERT(pin_id != OPEN); auto out_pin = site.getBelPins()[pin_id]; - auto pin_bel = get_bel_reader(site, str(out_pin.getBel())); - auto pin_bel_name = get_ic_prefix(site, pin_bel); - out_pin_tuple = std::make_tuple(pin_id, str(out_pin.getName()), pin_bel_name); - is_mux = pin_bel.getCategory() == Device::BELCategory::ROUTING; + auto out_pin_bel = get_bel_reader(site, str(out_pin.getBel())); + auto out_pin_name = str(out_pin.getName()); // IO PADs require special handling std::string pad_bel_name; @@ -375,68 +387,57 @@ struct ArchReader { } for (auto pin : wire.getPins()) { - if (pin == std::get<0>(out_pin_tuple)) + if ((int)pin == pin_id) continue; auto bel_pin = site.getBelPins()[pin]; std::string out_bel_pin_name = str(bel_pin.getName()); - auto bel = get_bel_reader(site, str(bel_pin.getBel())); - auto out_bel_name = get_ic_prefix(site, bel); + auto out_bel = get_bel_reader(site, str(bel_pin.getBel())); + auto out_bel_name = get_ic_prefix(site, out_bel); - auto in_bel_name = std::get<2>(out_pin_tuple); - auto in_bel_pin_name = std::get<1>(out_pin_tuple); + auto in_bel = out_pin_bel; + auto in_bel_name = get_ic_prefix(site, in_bel); + auto in_bel_pin_name = out_pin_name; + + bool skip_in_bel = in_bel.getCategory() == Device::BELCategory::LOGIC && take_bels_.count(in_bel.getName()) == 0; + bool skip_out_bel = out_bel.getCategory() == Device::BELCategory::LOGIC && take_bels_.count(out_bel.getName()) == 0; + if (skip_in_bel || skip_out_bel) + continue; std::string ostr = out_bel_name + "." + out_bel_pin_name; std::string istr = in_bel_name + "." + in_bel_pin_name; std::string inputs; std::string outputs; - e_interconnect ic_type; - if (is_mux) { - auto ic_name = in_bel_name; - auto res = ics.emplace(ic_name, std::make_tuple(std::string(), ostr, MUX_INTERC, false)); - - if (!res.second) { - std::tie(inputs, outputs, ic_type, std::ignore) = res.first->second; - outputs += " " + ostr; - res.first->second = std::make_tuple(inputs, outputs, ic_type, false); - } - } else if (bel.getCategory() == Device::BELCategory::ROUTING) { - auto ic_name = str(bel.getName()); - auto res = ics.emplace(ic_name, std::make_tuple(istr, std::string(), MUX_INTERC, false)); - - if (!res.second) { - std::tie(inputs, outputs, ic_type, std::ignore) = res.first->second; - inputs += " " + istr; - res.first->second = std::make_tuple(inputs, outputs, ic_type, false); - } - } else { - auto ic_name = wire_name + "_" + out_bel_pin_name; - if (is_pad) { - if (out_bel_name == pad_bel_name) - ostr += pad_in_suffix_; - else { // Create new wire to connect PAD output to the BELs input - ic_name = wire_name + "_" + pad_bel_pin_name + pad_out_suffix_; - istr = pad_bel_name + "." + pad_bel_pin_name + pad_out_suffix_; - } + + auto ic_name = wire_name + "_" + out_bel_pin_name; + if (is_pad) { + if (out_bel_name == pad_bel_name) + ostr += pad_in_suffix_; + else { // Create new wire to connect PAD output to the BELs input + ic_name = wire_name + "_" + pad_bel_pin_name + pad_out_suffix_; + istr = pad_bel_name + "." + pad_bel_pin_name + pad_out_suffix_; } + } - auto res = ics.emplace(ic_name, std::make_tuple(istr, ostr, DIRECT_INTERC, is_pad)); + bool add_pack_pattern = in_bel.getCategory() == Device::BELCategory::LOGIC && out_bel.getCategory() == Device::BELCategory::LOGIC && is_pad; - if (!res.second) { - std::string ins, outs; - std::tie(ins, outs, ic_type, std::ignore) = res.first->second; + auto res = ics.emplace(ic_name, std::make_tuple(istr, ostr, DIRECT_INTERC, add_pack_pattern)); - VTR_ASSERT(ins == istr); + e_interconnect ic_type; + if (!res.second) { + std::string ins, outs; + std::tie(ins, outs, ic_type, std::ignore) = res.first->second; - if (outs.empty()) - outs = ostr; - else - outs += " " + ostr; + VTR_ASSERT(ins == istr); - res.first->second = std::make_tuple(ins, outs, ic_type, is_pad); - } + if (outs.empty()) + outs = ostr; + else + outs += " " + ostr; + + res.first->second = std::make_tuple(ins, outs, ic_type, add_pack_pattern); } } } @@ -563,7 +564,7 @@ struct ArchReader { int found_prim = false; for (auto primitive : primLib.getCellDecls()) { bool is_prim = str(primitive.getLib()) == std::string("primitives"); - bool is_cell = cell_name == primitive.getName(); + bool is_cell = cell_name == (int)primitive.getName(); if (is_prim && is_cell) { found_prim = true; @@ -646,9 +647,6 @@ struct ArchReader { has_bel |= (int)primitive.getName() == map.cell; } - if (!has_bel) - continue; - try { temp = new t_model; temp->index = model_index++; @@ -792,20 +790,20 @@ struct ArchReader { mode->name = vtr::strdup("default"); mode->disable_packing = false; - int bel_count = get_bel_type_count(site, Device::BELCategory::LOGIC); + int bel_count = get_bel_type_count(site, Device::BELCategory::LOGIC) + get_bel_type_count(site, Device::BELCategory::ROUTING); mode->num_pb_type_children = bel_count; mode->pb_type_children = new t_pb_type[bel_count]; int count = 0; - VTR_LOG("Site: %s\n", str(site.getName()).c_str()); for (auto bel : bels) { - if (bel.getCategory() != Device::BELCategory::LOGIC) + auto category = bel.getCategory(); + if (bel.getCategory() == Device::BELCategory::SITE_PORT) continue; - if (take_bels_.count(bel.getName()) == 0) - continue; + bool is_logic = category == Device::BELCategory::LOGIC; - VTR_LOG(" - %s\n", str(bel.getName()).c_str()); + if (take_bels_.count(bel.getName()) == 0 && is_logic) + continue; auto bel_name = str(bel.getName()); std::pair key(name, bel_name); @@ -825,8 +823,12 @@ struct ArchReader { process_lut_block(mid_pb_type); else if (is_pad(bel_name)) process_pad_block(mid_pb_type, bel, site); - else + else if (is_logic) process_generic_block(mid_pb_type, bel); + else { + VTR_ASSERT(category == Device::BELCategory::ROUTING); + process_routing_block(mid_pb_type); + } mode->pb_type_children[count++] = *mid_pb_type; } @@ -1035,6 +1037,21 @@ struct ArchReader { std::string pb_name = std::string(pb_type->name); auto maps = bel_cell_mappings_[bel.getName()]; + + std::vector map_to_erase; + for (auto map : maps) { + bool is_compatible = true; + for (auto pin_map : map.pins) + is_compatible &= block_port_exists(pb_type, str(pin_map.second)); + + if (!is_compatible) + map_to_erase.push_back(map); + } + + for (auto map : map_to_erase) { + maps.erase(map); + } + int num_modes = maps.size(); pb_type->num_modes = num_modes; @@ -1129,6 +1146,55 @@ struct ArchReader { } } + void process_routing_block(t_pb_type* pb_type) { + pb_type->num_modes = 1; + pb_type->modes = new t_mode[1]; + + int idx = 0; + auto mode = &pb_type->modes[idx]; + + std::string name = std::string(pb_type->name); + mode->name = vtr::strdup(name.c_str()); + mode->parent_pb_type = pb_type; + mode->index = idx; + mode->num_pb_type_children = 0; + + std::string istr, ostr, ic_name; + + // The MUX interconnections can only have a single output + VTR_ASSERT(pb_type->num_output_pins == 1); + + for (int iport = 0; iport < pb_type->num_ports; iport++) { + const t_port port = pb_type->ports[iport]; + auto port_name = name + "." + std::string(port.name); + switch (port.type) { + case IN_PORT: + istr += istr.empty() ? port_name : " " + port_name; + break; + case OUT_PORT: + ostr = port_name; + break; + default: + VTR_ASSERT(0); + } + } + + ic_name = std::string(pb_type->name); + + mode->num_interconnect = 1; + mode->interconnect = new t_interconnect[1]; + + e_interconnect ic_type = pb_type->num_input_pins == 1 ? DIRECT_INTERC : MUX_INTERC; + + auto ic = &mode->interconnect[idx]; + ic->name = vtr::strdup(ic_name.c_str()); + ic->type = ic_type; + ic->parent_mode_index = idx; + ic->parent_mode = mode; + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + } + void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, int bel_name = OPEN) { // Prepare data based on pb_type level std::unordered_map> pins; @@ -1139,9 +1205,6 @@ struct ArchReader { } } else { for (auto bel : site.getBels()) { - if (bel.getCategory() != Device::BELCategory::LOGIC) - continue; - if ((int)bel.getName() != bel_name) continue; @@ -1227,6 +1290,8 @@ struct ArchReader { ic->annotations = new t_pin_to_pin_annotation[outs.size()]; VTR_ASSERT(ins.size() == 1); + // TODO: propagate pack pattern until the block root. + // pack pattern int index = 0; for (auto output : outs) @@ -1453,8 +1518,9 @@ struct ArchReader { // Layout Processing void process_layout() { - auto tileList = ar_.getTileList(); - auto tileTypeList = ar_.getTileTypeList(); + auto tiles = ar_.getTileList(); + auto tile_types = ar_.getTileTypeList(); + auto site_types = ar_.getSiteTypeList(); std::vector packages; for (auto package : ar_.getPackages()) @@ -1463,7 +1529,7 @@ struct ArchReader { for (auto name : packages) { t_grid_def grid_def; grid_def.width = grid_def.height = 0; - for (auto tile : tileList) { + for (auto tile : tiles) { grid_def.width = std::max(grid_def.width, tile.getCol() + 1); grid_def.height = std::max(grid_def.height, tile.getRow() + 1); } @@ -1482,16 +1548,28 @@ struct ArchReader { "The name auto is reserved for auto-size layouts; please choose another name"); } grid_def.name = name; - for (auto tile : tileList) { + for (auto tile : tiles) { + auto tile_type = tile_types[tile.getType()]; + + bool found = false; + for (auto site : tile.getSites()) { + auto site_type_in_tile = tile_type.getSiteTypes()[site.getType()]; + auto site_type = site_types[site_type_in_tile.getPrimaryType()]; + + found |= take_sites_.count(site_type.getName()) != 0; + } + + if (!found) + continue; + t_metadata_dict data; std::string tile_prefix = str(tile.getName()); - auto tileType = tileTypeList[tile.getType()]; - std::string tile_type = str(tileType.getName()); + std::string tile_type_name = str(tile_type.getName()); - size_t pos = tile_prefix.find(tile_type); + size_t pos = tile_prefix.find(tile_type_name); if (pos != std::string::npos && pos == 0) - tile_prefix.erase(pos, tile_type.length() + 1); - t_grid_loc_def single(tile_type, 1); + tile_prefix.erase(pos, tile_type_name.length() + 1); + t_grid_loc_def single(tile_type_name, 1); single.x.start_expr = std::to_string(tile.getCol() + 1); single.y.start_expr = std::to_string(tile.getRow() + 1); From 749b4028317dc9d3c33185270c5b1c30efbc0e6c Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Tue, 7 Dec 2021 16:20:46 +0100 Subject: [PATCH 12/22] vpr: pack: allow pins to have name starting with integer Signed-off-by: Alessandro Comodi --- vpr/src/pack/pb_type_graph.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/vpr/src/pack/pb_type_graph.cpp b/vpr/src/pack/pb_type_graph.cpp index 7f855cb9ab6..40552ba778c 100644 --- a/vpr/src/pack/pb_type_graph.cpp +++ b/vpr/src/pack/pb_type_graph.cpp @@ -1098,9 +1098,6 @@ static bool realloc_and_load_pb_graph_pin_ptrs_at_var(const int line_num, return false; //clb[9:0]123 } (*token_index)++; - if (!checkTokenType(tokens[*token_index], TOKEN_STRING)) { - return false; //clb[9:0].123 - } /* parse ports and port pins of pb */ port_name = tokens[*token_index].data; From 9c7fb8875c777106ca500dca90d40c57fcf458c5 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Fri, 10 Dec 2021 18:58:49 +0100 Subject: [PATCH 13/22] libs: arch: interchange : allow inout ports in bels connected to pads Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 256 ++++++++++++------ 1 file changed, 177 insertions(+), 79 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 42f158b3354..96adb99d906 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -45,14 +45,11 @@ struct t_package_pin { struct t_bel_cell_mapping { int cell; + int site; std::vector> pins; - bool operator==(const t_bel_cell_mapping& other) const { - return cell == other.cell; - } - bool operator<(const t_bel_cell_mapping& other) const { - return cell < other.cell; + return cell < other.cell || (cell == other.cell && site < other.site); } }; @@ -279,8 +276,8 @@ struct ArchReader { // TODO: add possibility to have multiple packages std::vector package_pins_; std::unordered_set pad_bels_; - std::string pad_out_suffix_ = "_out"; - std::string pad_in_suffix_ = "_in"; + std::string out_suffix_ = "_out"; + std::string in_suffix_ = "_in"; // Bel Cell mappings std::unordered_map> bel_cell_mappings_; @@ -306,9 +303,19 @@ struct ArchReader { } Device::BEL::Reader get_bel_reader(Device::SiteType::Reader& site, std::string bel_name) { - for (auto bel : site.getBels()) { + for (auto bel : site.getBels()) if (str(bel.getName()) == bel_name) return bel; + VTR_ASSERT(0); + } + + Device::BELPin::Reader get_bel_pin_reader(Device::SiteType::Reader& site, Device::BEL::Reader& bel, std::string pin_name) { + auto bel_pins = site.getBelPins(); + + for (auto bel_pin : bel.getPins()) { + auto pin_reader = bel_pins[bel_pin]; + if (str(pin_reader.getName()) == pin_name) + return pin_reader; } VTR_ASSERT(0); } @@ -339,7 +346,7 @@ struct ArchReader { return pad_bels_.count(name) != 0; } - std::unordered_map> get_ics(Device::SiteType::Reader& site) { + std::unordered_map> get_interconnects(Device::SiteType::Reader& site) { // dictionary: // - key: interconnect name // - value: (inputs string, outputs string, interconnect type) @@ -349,43 +356,55 @@ struct ArchReader { // pin name, bel name int pin_id = OPEN; + bool pad_exists = false; + bool all_inout_pins = true; + std::string pad_bel_name; + std::string pad_bel_pin_name; for (auto pin : wire.getPins()) { auto bel_pin = site.getBelPins()[pin]; auto dir = bel_pin.getDir(); + std::string bel_pin_name = str(bel_pin.getName()); - if (dir == LogicalNetlist::Netlist::Direction::OUTPUT) { - pin_id = pin; - break; + auto bel = get_bel_reader(site, str(bel_pin.getBel())); + auto bel_name = get_ic_prefix(site, bel); + + auto bel_is_pad = is_pad(bel_name); + + pad_exists |= bel_is_pad; + all_inout_pins &= dir == LogicalNetlist::Netlist::Direction::INOUT; + + if (bel_is_pad) { + pad_bel_name = bel_name; + pad_bel_pin_name = bel_pin_name; } - if (dir == LogicalNetlist::Netlist::Direction::INOUT) + if (dir == LogicalNetlist::Netlist::Direction::OUTPUT) pin_id = pin; } - VTR_ASSERT(pin_id != OPEN); - - auto out_pin = site.getBelPins()[pin_id]; - auto out_pin_bel = get_bel_reader(site, str(out_pin.getBel())); - auto out_pin_name = str(out_pin.getName()); + if (pin_id == OPEN) { + // If no driver pin has been found, the assumption is that + // there must be a PAD with inout pin connected to other inout pins + for (auto pin : wire.getPins()) { + auto bel_pin = site.getBelPins()[pin]; + std::string bel_pin_name = str(bel_pin.getName()); - // IO PADs require special handling - std::string pad_bel_name; - std::string pad_bel_pin_name; - bool is_pad = false; - for (auto pin : wire.getPins()) { - auto bel_pin = site.getBelPins()[pin]; - std::string bel_pin_name = str(bel_pin.getName()); + auto bel = get_bel_reader(site, str(bel_pin.getBel())); + auto bel_name = get_ic_prefix(site, bel); - auto bel = get_bel_reader(site, str(bel_pin.getBel())); - auto bel_name = get_ic_prefix(site, bel); + if (!is_pad(bel_name)) + continue; - for (auto pad_bel : package_pins_) { - is_pad = pad_bel.bel_name == bel_name || is_pad; - pad_bel_name = pad_bel.bel_name == bel_name ? bel_name : pad_bel_name; - pad_bel_pin_name = pad_bel.bel_name == bel_name ? bel_pin_name : pad_bel_pin_name; + pin_id = pin; } } + VTR_ASSERT(pin_id != OPEN); + + auto out_pin = site.getBelPins()[pin_id]; + auto out_pin_bel = get_bel_reader(site, str(out_pin.getBel())); + auto out_pin_name = str(out_pin.getName()); + for (auto pin : wire.getPins()) { if ((int)pin == pin_id) continue; @@ -408,36 +427,55 @@ struct ArchReader { std::string ostr = out_bel_name + "." + out_bel_pin_name; std::string istr = in_bel_name + "." + in_bel_pin_name; - std::string inputs; - std::string outputs; + // TODO: If the bel pin is INOUT (e.g. PULLDOWN/PULLUP in Series7) + // for now treat as input only and assign the in suffix + if (bel_pin.getDir() == LogicalNetlist::Netlist::Direction::INOUT && !all_inout_pins && !is_pad(out_bel_name)) + ostr += in_suffix_; auto ic_name = wire_name + "_" + out_bel_pin_name; - if (is_pad) { + + std::vector> strs; + if (all_inout_pins) { + std::string extra_istr = out_bel_name + "." + out_bel_pin_name + out_suffix_; + std::string extra_ostr = in_bel_name + "." + in_bel_pin_name + in_suffix_; + std::string extra_ic_name = ic_name + "_extra"; + + strs.push_back(std::make_tuple(extra_ic_name, extra_istr, extra_ostr)); + + istr += out_suffix_; + ostr += in_suffix_; + } else if (pad_exists) { if (out_bel_name == pad_bel_name) - ostr += pad_in_suffix_; + ostr += in_suffix_; else { // Create new wire to connect PAD output to the BELs input - ic_name = wire_name + "_" + pad_bel_pin_name + pad_out_suffix_; - istr = pad_bel_name + "." + pad_bel_pin_name + pad_out_suffix_; + ic_name = wire_name + "_" + pad_bel_pin_name + out_suffix_; + istr = pad_bel_name + "." + pad_bel_pin_name + out_suffix_; } } - bool add_pack_pattern = in_bel.getCategory() == Device::BELCategory::LOGIC && out_bel.getCategory() == Device::BELCategory::LOGIC && is_pad; + strs.push_back(std::make_tuple(ic_name, istr, ostr)); - auto res = ics.emplace(ic_name, std::make_tuple(istr, ostr, DIRECT_INTERC, add_pack_pattern)); + bool add_pack_pattern = in_bel.getCategory() == Device::BELCategory::LOGIC && out_bel.getCategory() == Device::BELCategory::LOGIC && pad_exists; - e_interconnect ic_type; - if (!res.second) { - std::string ins, outs; - std::tie(ins, outs, ic_type, std::ignore) = res.first->second; + for (auto s : strs) { + auto name = std::get<0>(s); // interconnect name + auto i = std::get<1>(s); // interconnect input + auto o = std::get<2>(s); // interconnect output + auto res = ics.emplace(name, std::make_tuple(i, o, DIRECT_INTERC, add_pack_pattern)); - VTR_ASSERT(ins == istr); + e_interconnect ic_type; + if (!res.second) { + std::string ins, outs; + std::tie(ins, outs, ic_type, std::ignore) = res.first->second; - if (outs.empty()) - outs = ostr; - else - outs += " " + ostr; + VTR_ASSERT(ins == i); - res.first->second = std::make_tuple(ins, outs, ic_type, add_pack_pattern); + if (outs.empty()) + outs = o; + else + outs += " " + o; + res.first->second = std::make_tuple(ins, outs, ic_type, add_pack_pattern); + } } } } @@ -466,7 +504,6 @@ struct ArchReader { for (auto site : tile.getSites()) { auto site_type_in_tile = tile_type.getSiteTypes()[site.getType()]; auto site_type = site_types[site_type_in_tile.getPrimaryType()]; - auto site_pins = site_type.getBelPins(); bool found = false; for (auto bel : site_type.getBels()) { @@ -475,11 +512,7 @@ struct ArchReader { found |= res; - bool has_inout = false; - for (auto pin : bel.getPins()) - has_inout |= site_pins[pin].getDir() == LogicalNetlist::Netlist::Direction::INOUT; - - if ((res && !has_inout) || is_pad(str(bel_name))) + if (res || is_pad(str(bel_name))) take_bels_.insert(bel_name); } @@ -557,22 +590,33 @@ struct ArchReader { void process_cell_bel_mappings() { auto primLib = ar_.getPrimLibs(); + auto portList = primLib.getPortList(); for (auto cell_mapping : ar_.getCellBelMap()) { int cell_name = cell_mapping.getCell(); - int found_prim = false; + int found_valid_prim = false; for (auto primitive : primLib.getCellDecls()) { bool is_prim = str(primitive.getLib()) == std::string("primitives"); bool is_cell = cell_name == (int)primitive.getName(); - if (is_prim && is_cell) { - found_prim = true; + bool has_inout = false; + for (auto port_idx : primitive.getPorts()) { + auto port = portList[port_idx]; + + if (port.getDir() == LogicalNetlist::Netlist::Direction::INOUT) { + has_inout = true; + break; + } + } + + if (is_prim && is_cell && !has_inout) { + found_valid_prim = true; break; } } - if (!found_prim) + if (!found_valid_prim) continue; for (auto common_pins : cell_mapping.getCommonPins()) { @@ -582,17 +626,20 @@ struct ArchReader { pins.emplace_back(pin_map.getCellPin(), pin_map.getBelPin()); for (auto site_type_entry : common_pins.getSiteTypes()) { + int site_type = site_type_entry.getSiteType(); + for (auto bel : site_type_entry.getBels()) { t_bel_cell_mapping mapping; mapping.cell = cell_name; + mapping.site = site_type; mapping.pins = pins; std::set maps{mapping}; - auto res = bel_cell_mappings_.emplace(bel, maps); - if (!res.second) + if (!res.second) { res.first->second.insert(mapping); + } } } } @@ -647,12 +694,15 @@ struct ArchReader { has_bel |= (int)primitive.getName() == map.cell; } + if (!has_bel) + continue; + try { temp = new t_model; temp->index = model_index++; temp->never_prune = true; - temp->name = vtr::strdup(str(primitive.getName()).c_str()); + temp->name = vtr::strdup(prim_name.c_str()); ret_map_name = model_name_map.insert(std::pair(temp->name, 0)); if (!ret_map_name.second) { @@ -660,7 +710,10 @@ struct ArchReader { "Duplicate model name: '%s'.\n", temp->name); } - process_model_ports(temp, primitive); + if (!process_model_ports(temp, primitive)) { + free_arch_model(temp); + continue; + } check_model_clocks(temp, arch_file_, __LINE__); check_model_combinational_sinks(temp, arch_file_, __LINE__); @@ -676,7 +729,7 @@ struct ArchReader { } } - void process_model_ports(t_model* model, Netlist::CellDeclaration::Reader primitive) { + bool process_model_ports(t_model* model, Netlist::CellDeclaration::Reader primitive) { auto primLib = ar_.getPrimLibs(); auto portList = primLib.getPortList(); @@ -693,7 +746,7 @@ struct ArchReader { dir = OUT_PORT; break; case LogicalNetlist::Netlist::Direction::INOUT: - dir = INOUT_PORT; + return false; break; default: break; @@ -742,6 +795,8 @@ struct ArchReader { model->outputs = model_port; } } + + return true; } // Complex Blocks @@ -824,7 +879,7 @@ struct ArchReader { else if (is_pad(bel_name)) process_pad_block(mid_pb_type, bel, site); else if (is_logic) - process_generic_block(mid_pb_type, bel); + process_generic_block(mid_pb_type, bel, site); else { VTR_ASSERT(category == Device::BELCategory::ROUTING); process_routing_block(mid_pb_type); @@ -918,8 +973,8 @@ struct ArchReader { // Add PAD pb_type ports VTR_ASSERT(bel.getPins().size() == 1); std::string pin = str(site.getBelPins()[bel.getPins()[0]].getName()); - std::string ipin = pin + pad_in_suffix_; - std::string opin = pin + pad_out_suffix_; + std::string ipin = pin + in_suffix_; + std::string opin = pin + out_suffix_; auto num_ports = 2; auto ports = new t_port[num_ports]; @@ -1033,24 +1088,44 @@ struct ArchReader { imode->interconnect[0] = *i_ic; } - void process_generic_block(t_pb_type* pb_type, Device::BEL::Reader& bel) { + void process_generic_block(t_pb_type* pb_type, Device::BEL::Reader& bel, Device::SiteType::Reader& site) { std::string pb_name = std::string(pb_type->name); - auto maps = bel_cell_mappings_[bel.getName()]; + std::set maps(bel_cell_mappings_[bel.getName()]); std::vector map_to_erase; for (auto map : maps) { - bool is_compatible = true; - for (auto pin_map : map.pins) - is_compatible &= block_port_exists(pb_type, str(pin_map.second)); + auto name = str(map.cell); + bool is_compatible = map.site == (int)site.getName(); + for (auto pin_map : map.pins) { + if (is_compatible == false) + break; + + auto cell_pin = str(pin_map.first); + auto bel_pin = str(pin_map.second); + + if (cell_pin == arch_->vcc_cell || cell_pin == arch_->gnd_cell) + continue; + + // Assign suffix to bel pin as it is a inout pin which was split in out and in ports + auto pin_reader = get_bel_pin_reader(site, bel, bel_pin); + bool is_inout = pin_reader.getDir() == LogicalNetlist::Netlist::Direction::INOUT; + + auto model_port = get_model_port(arch_, name, cell_pin, false); + + if (is_inout && model_port != nullptr) { + bel_pin = model_port->dir == IN_PORT ? bel_pin + in_suffix_ : bel_pin + out_suffix_; + } + + is_compatible &= block_port_exists(pb_type, bel_pin); + } if (!is_compatible) map_to_erase.push_back(map); } - for (auto map : map_to_erase) { - maps.erase(map); - } + for (auto map : map_to_erase) + VTR_ASSERT(maps.erase(map) == 1); int num_modes = maps.size(); @@ -1059,6 +1134,9 @@ struct ArchReader { int count = 0; for (auto map : maps) { + if (map.site != (int)site.getName()) + continue; + int idx = count++; auto mode = &pb_type->modes[idx]; auto name = str(map.cell); @@ -1115,15 +1193,21 @@ struct ArchReader { auto size = model_port->size; auto dir = model_port->dir; + // Assign suffix to bel pin as it is a inout pin which was split in out and in ports + auto pin_reader = get_bel_pin_reader(site, bel, bel_pin); + bool is_inout = pin_reader.getDir() == LogicalNetlist::Netlist::Direction::INOUT; + pins.emplace(cell_pin, std::make_pair(dir, size)); std::string istr, ostr, ic_name; switch (dir) { case IN_PORT: + bel_pin = is_inout ? bel_pin + in_suffix_ : bel_pin; istr = pb_name + std::string(".") + bel_pin; ostr = leaf_name + std::string(".") + cell_pin + pin_suffix; break; case OUT_PORT: + bel_pin = is_inout ? bel_pin + out_suffix_ : bel_pin; istr = leaf_name + std::string(".") + cell_pin + pin_suffix; ostr = pb_name + std::string(".") + bel_pin; break; @@ -1210,8 +1294,22 @@ struct ArchReader { for (auto bel_pin : bel.getPins()) { auto pin = site.getBelPins()[bel_pin]; - auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; - pins.emplace(str(pin.getName()), std::make_pair(dir, 1)); + auto dir = pin.getDir(); + + switch (dir) { + case LogicalNetlist::Netlist::Direction::INPUT: + pins.emplace(str(pin.getName()), std::make_pair(IN_PORT, 1)); + break; + case LogicalNetlist::Netlist::Direction::OUTPUT: + pins.emplace(str(pin.getName()), std::make_pair(OUT_PORT, 1)); + break; + case LogicalNetlist::Netlist::Direction::INOUT: + pins.emplace(str(pin.getName()) + in_suffix_, std::make_pair(IN_PORT, 1)); + pins.emplace(str(pin.getName()) + out_suffix_, std::make_pair(OUT_PORT, 1)); + break; + default: + VTR_ASSERT(0); + } } } } @@ -1261,7 +1359,7 @@ struct ArchReader { } void process_interconnects(t_mode* mode, Device::SiteType::Reader& site) { - auto ics = get_ics(site); + auto ics = get_interconnects(site); auto num_ic = ics.size(); mode->num_interconnect = num_ic; From b7c962d93628f375ff80bdefc086e7df3b720401 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 13 Dec 2021 17:02:17 +0100 Subject: [PATCH 14/22] vpr: base: interchange: netlist: add c-style init and fixed bug Signed-off-by: Alessandro Comodi --- vpr/src/base/read_interchange_netlist.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/vpr/src/base/read_interchange_netlist.cpp b/vpr/src/base/read_interchange_netlist.cpp index 7ffe56ac399..38f1731197b 100644 --- a/vpr/src/base/read_interchange_netlist.cpp +++ b/vpr/src/base/read_interchange_netlist.cpp @@ -168,7 +168,9 @@ struct NetlistReader { // TODO: export this to a library function to have generic parameter decoding if (entry.which() == LogicalNetlist::Netlist::PropertyMap::Entry::TEXT_VALUE) { const std::regex vhex_regex("[0-9]+'h([0-9A-Z]+)"); - const std::regex vbit_regex("[0-9]+'b([0-9A-Z]+)"); + const std::regex vbit_regex("[0-9]+'b([0-9]+)"); + const std::regex chex_regex("0x([0-9A-Za-z]+)"); + const std::regex cbit_regex("0b([0-9]+)"); const std::regex bit_regex("[0-1]+"); std::string init_str = str_list[entry.getTextValue()]; std::smatch regex_matches; @@ -180,9 +182,18 @@ struct NetlistReader { for (int bit = 3; bit >= 0; bit--) init.push_back((value >> bit) & 1); } + else if (std::regex_match(init_str, regex_matches, chex_regex)) + for (const char& c : regex_matches[1].str()) { + int value = std::stoi(std::string(1, c), 0, 16); + for (int bit = 3; bit >= 0; bit--) + init.push_back((value >> bit) & 1); + } else if (std::regex_match(init_str, regex_matches, vbit_regex)) for (const char& c : regex_matches[1].str()) init.push_back((bool)std::stoi(std::string(1, c), 0, 2)); + else if (std::regex_match(init_str, regex_matches, cbit_regex)) + for (const char& c : regex_matches[1].str()) + init.push_back((bool)std::stoi(std::string(1, c), 0, 2)); else if (std::regex_match(init_str, regex_matches, bit_regex)) for (const char& c : init_str) init.push_back((bool)std::stoi(std::string(1, c), 0, 2)); @@ -260,6 +271,9 @@ struct NetlistReader { int inum = 0; for (auto port : cell_lib.getPorts()) { + if (port_net_map.find(port) == port_net_map.end()) + continue; + auto net_name = port_net_map.at(port); AtomNetId net_id = main_netlist_.create_net(net_name); From a97a3e9e273f81afef8d39b3d1e449e91a8460e6 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Tue, 14 Dec 2021 13:33:39 +0100 Subject: [PATCH 15/22] libs: arch: interchange: add pack pattern propagation Moreover, this commit reduces code verbosity to get directions of pins Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 313 +++++++++++++----- 1 file changed, 236 insertions(+), 77 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 96adb99d906..6a4cc40238e 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -36,6 +36,17 @@ using namespace DeviceResources; using namespace LogicalNetlist; using namespace capnp; +// Necessary to reduce code verbosity when getting the pin directions +static const auto INPUT = LogicalNetlist::Netlist::Direction::INPUT; +static const auto OUTPUT = LogicalNetlist::Netlist::Direction::OUTPUT; +static const auto INOUT = LogicalNetlist::Netlist::Direction::INOUT; + +// Enum for pack pattern expansion direction +enum e_pp_dir { + FORWARD = 0, + BACKWARD = 1 +}; + struct t_package_pin { std::string name; @@ -44,15 +55,23 @@ struct t_package_pin { }; struct t_bel_cell_mapping { - int cell; - int site; - std::vector> pins; + size_t cell; + size_t site; + std::vector> pins; bool operator<(const t_bel_cell_mapping& other) const { return cell < other.cell || (cell == other.cell && site < other.site); } }; +// Intermediate data type to store information on interconnects to be created +struct t_ic_data { + std::string input; + std::vector outputs; + + bool requires_pack_pattern; +}; + /****************** Utility functions ******************/ /** @@ -284,7 +303,7 @@ struct ArchReader { std::unordered_map segment_name_to_segment_idx; // Utils - std::string str(int idx) { + std::string str(size_t idx) { return arch_->interned_strings[idx].get(&arch_->strings); } @@ -346,11 +365,21 @@ struct ArchReader { return pad_bels_.count(name) != 0; } - std::unordered_map> get_interconnects(Device::SiteType::Reader& site) { + std::string remove_bel_suffix(std::string bel) { + std::smatch regex_matches; + std::string regex = std::string("(.*)") + bel_dedup_suffix_; + const std::regex bel_regex(regex.c_str()); + if (std::regex_match(bel, regex_matches, bel_regex)) + return regex_matches[1].str(); + + return bel; + } + + std::unordered_map get_interconnects(Device::SiteType::Reader& site) { // dictionary: // - key: interconnect name // - value: (inputs string, outputs string, interconnect type) - std::unordered_map> ics; + std::unordered_map ics; for (auto wire : site.getSiteWires()) { std::string wire_name = str(wire.getName()); @@ -371,14 +400,14 @@ struct ArchReader { auto bel_is_pad = is_pad(bel_name); pad_exists |= bel_is_pad; - all_inout_pins &= dir == LogicalNetlist::Netlist::Direction::INOUT; + all_inout_pins &= dir == INOUT; if (bel_is_pad) { pad_bel_name = bel_name; pad_bel_pin_name = bel_pin_name; } - if (dir == LogicalNetlist::Netlist::Direction::OUTPUT) + if (dir == OUTPUT) pin_id = pin; } @@ -429,18 +458,27 @@ struct ArchReader { // TODO: If the bel pin is INOUT (e.g. PULLDOWN/PULLUP in Series7) // for now treat as input only and assign the in suffix - if (bel_pin.getDir() == LogicalNetlist::Netlist::Direction::INOUT && !all_inout_pins && !is_pad(out_bel_name)) + if (bel_pin.getDir() == INOUT && !all_inout_pins && !is_pad(out_bel_name)) ostr += in_suffix_; auto ic_name = wire_name + "_" + out_bel_pin_name; - std::vector> strs; + bool requires_pack_pattern = pad_exists; + + std::vector> ics_data; if (all_inout_pins) { std::string extra_istr = out_bel_name + "." + out_bel_pin_name + out_suffix_; std::string extra_ostr = in_bel_name + "." + in_bel_pin_name + in_suffix_; std::string extra_ic_name = ic_name + "_extra"; - strs.push_back(std::make_tuple(extra_ic_name, extra_istr, extra_ostr)); + std::vector extra_ostrs{extra_ostr}; + t_ic_data extra_ic_data = { + extra_istr, // ic input + extra_ostrs, // ic outputs + requires_pack_pattern // pack pattern required + }; + + ics_data.push_back(std::make_pair(extra_ic_name, extra_ic_data)); istr += out_suffix_; ostr += in_suffix_; @@ -453,28 +491,28 @@ struct ArchReader { } } - strs.push_back(std::make_tuple(ic_name, istr, ostr)); + std::vector ostrs{ostr}; + t_ic_data ic_data = { + istr, + ostrs, + requires_pack_pattern}; + + ics_data.push_back(std::make_pair(ic_name, ic_data)); - bool add_pack_pattern = in_bel.getCategory() == Device::BELCategory::LOGIC && out_bel.getCategory() == Device::BELCategory::LOGIC && pad_exists; + for (auto entry : ics_data) { + auto name = entry.first; + auto data = entry.second; - for (auto s : strs) { - auto name = std::get<0>(s); // interconnect name - auto i = std::get<1>(s); // interconnect input - auto o = std::get<2>(s); // interconnect output - auto res = ics.emplace(name, std::make_tuple(i, o, DIRECT_INTERC, add_pack_pattern)); + auto res = ics.emplace(name, data); - e_interconnect ic_type; if (!res.second) { - std::string ins, outs; - std::tie(ins, outs, ic_type, std::ignore) = res.first->second; + auto old_data = res.first->second; - VTR_ASSERT(ins == i); + VTR_ASSERT(old_data.input == data.input); + VTR_ASSERT(data.outputs.size() == 1); - if (outs.empty()) - outs = o; - else - outs += " " + o; - res.first->second = std::make_tuple(ins, outs, ic_type, add_pack_pattern); + res.first->second.outputs.push_back(data.outputs[0]); + res.first->second.requires_pack_pattern |= data.requires_pack_pattern; } } } @@ -593,18 +631,18 @@ struct ArchReader { auto portList = primLib.getPortList(); for (auto cell_mapping : ar_.getCellBelMap()) { - int cell_name = cell_mapping.getCell(); + size_t cell_name = cell_mapping.getCell(); int found_valid_prim = false; for (auto primitive : primLib.getCellDecls()) { bool is_prim = str(primitive.getLib()) == std::string("primitives"); - bool is_cell = cell_name == (int)primitive.getName(); + bool is_cell = cell_name == primitive.getName(); bool has_inout = false; for (auto port_idx : primitive.getPorts()) { auto port = portList[port_idx]; - if (port.getDir() == LogicalNetlist::Netlist::Direction::INOUT) { + if (port.getDir() == INOUT) { has_inout = true; break; } @@ -620,13 +658,13 @@ struct ArchReader { continue; for (auto common_pins : cell_mapping.getCommonPins()) { - std::vector> pins; + std::vector> pins; for (auto pin_map : common_pins.getPins()) pins.emplace_back(pin_map.getCellPin(), pin_map.getBelPin()); for (auto site_type_entry : common_pins.getSiteTypes()) { - int site_type = site_type_entry.getSiteType(); + size_t site_type = site_type_entry.getSiteType(); for (auto bel : site_type_entry.getBels()) { t_bel_cell_mapping mapping; @@ -691,7 +729,7 @@ struct ArchReader { continue; for (auto map : bel_cell_map.second) - has_bel |= (int)primitive.getName() == map.cell; + has_bel |= primitive.getName() == map.cell; } if (!has_bel) @@ -739,13 +777,13 @@ struct ArchReader { auto port = portList[port_idx]; enum PORTS dir = ERR_PORT; switch (port.getDir()) { - case LogicalNetlist::Netlist::Direction::INPUT: + case INPUT: dir = IN_PORT; break; - case LogicalNetlist::Netlist::Direction::OUTPUT: + case OUTPUT: dir = OUT_PORT; break; - case LogicalNetlist::Netlist::Direction::INOUT: + case INOUT: return false; break; default: @@ -1096,7 +1134,7 @@ struct ArchReader { std::vector map_to_erase; for (auto map : maps) { auto name = str(map.cell); - bool is_compatible = map.site == (int)site.getName(); + bool is_compatible = map.site == site.getName(); for (auto pin_map : map.pins) { if (is_compatible == false) break; @@ -1109,7 +1147,7 @@ struct ArchReader { // Assign suffix to bel pin as it is a inout pin which was split in out and in ports auto pin_reader = get_bel_pin_reader(site, bel, bel_pin); - bool is_inout = pin_reader.getDir() == LogicalNetlist::Netlist::Direction::INOUT; + bool is_inout = pin_reader.getDir() == INOUT; auto model_port = get_model_port(arch_, name, cell_pin, false); @@ -1134,7 +1172,7 @@ struct ArchReader { int count = 0; for (auto map : maps) { - if (map.site != (int)site.getName()) + if (map.site != site.getName()) continue; int idx = count++; @@ -1195,7 +1233,7 @@ struct ArchReader { // Assign suffix to bel pin as it is a inout pin which was split in out and in ports auto pin_reader = get_bel_pin_reader(site, bel, bel_pin); - bool is_inout = pin_reader.getDir() == LogicalNetlist::Netlist::Direction::INOUT; + bool is_inout = pin_reader.getDir() == INOUT; pins.emplace(cell_pin, std::make_pair(dir, size)); @@ -1279,17 +1317,17 @@ struct ArchReader { ic->output_string = vtr::strdup(ostr.c_str()); } - void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, int bel_name = OPEN) { + void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, size_t bel_name = OPEN) { // Prepare data based on pb_type level std::unordered_map> pins; - if (bel_name == OPEN) { + if (bel_name == (size_t)OPEN) { for (auto pin : site.getPins()) { - auto dir = pin.getDir() == LogicalNetlist::Netlist::Direction::INPUT ? IN_PORT : OUT_PORT; + auto dir = pin.getDir() == INPUT ? IN_PORT : OUT_PORT; pins.emplace(str(pin.getName()), std::make_pair(dir, 1)); } } else { for (auto bel : site.getBels()) { - if ((int)bel.getName() != bel_name) + if (bel.getName() != bel_name) continue; for (auto bel_pin : bel.getPins()) { @@ -1297,13 +1335,13 @@ struct ArchReader { auto dir = pin.getDir(); switch (dir) { - case LogicalNetlist::Netlist::Direction::INPUT: + case INPUT: pins.emplace(str(pin.getName()), std::make_pair(IN_PORT, 1)); break; - case LogicalNetlist::Netlist::Direction::OUTPUT: + case OUTPUT: pins.emplace(str(pin.getName()), std::make_pair(OUT_PORT, 1)); break; - case LogicalNetlist::Netlist::Direction::INOUT: + case INOUT: pins.emplace(str(pin.getName()) + in_suffix_, std::make_pair(IN_PORT, 1)); pins.emplace(str(pin.getName()) + out_suffix_, std::make_pair(OUT_PORT, 1)); break; @@ -1370,51 +1408,172 @@ struct ArchReader { // Handle site wires, namely direct interconnects for (auto ic_pair : ics) { - std::string ic_name = ic_pair.first; - - std::string inputs; - std::string outputs; - e_interconnect ic_type; - bool add_pack_pattern; - - std::tie(inputs, outputs, ic_type, add_pack_pattern) = ic_pair.second; + auto ic_name = ic_pair.first; + auto ic_data = ic_pair.second; - t_interconnect* ic = &mode->interconnect[curr_ic++]; + auto input = ic_data.input; + auto outputs = ic_data.outputs; - if (add_pack_pattern) { - auto outs = vtr::split(outputs, " "); - auto ins = vtr::split(inputs, " "); - ic->num_annotations = outs.size(); - ic->annotations = new t_pin_to_pin_annotation[outs.size()]; - VTR_ASSERT(ins.size() == 1); + auto merge_string = [](std::string& ss, std::string& s) { + return ss.empty() ? s : ss + " " + s; + }; - // TODO: propagate pack pattern until the block root. + std::string outputs_str = std::accumulate(outputs.begin(), outputs.end(), std::string(), merge_string); - // pack pattern - int index = 0; - for (auto output : outs) - ic->annotations[index++] = get_pack_pattern(ic_name, inputs, output); - } + t_interconnect* ic = &mode->interconnect[curr_ic++]; // No line num for interconnects, as line num is XML specific // TODO: probably line_num should be deprecated as it is dependent // on the input architecture format. ic->line_num = 0; - ic->type = ic_type; + ic->type = DIRECT_INTERC; ic->parent_mode_index = mode->index; ic->parent_mode = mode; VTR_ASSERT(names.insert(ic_name).second); ic->name = vtr::strdup(ic_name.c_str()); - ic->input_string = vtr::strdup(inputs.c_str()); - ic->output_string = vtr::strdup(outputs.c_str()); + ic->input_string = vtr::strdup(input.c_str()); + ic->output_string = vtr::strdup(outputs_str.c_str()); + } + + for (size_t iic = 0; iic < num_ic; iic++) { + t_interconnect* ic = &mode->interconnect[iic]; + + auto ic_data = ics.at(std::string(ic->name)); + + if (ic_data.requires_pack_pattern) { + auto backward_pps_map = propagate_pack_patterns(ic, site, BACKWARD); + auto forward_pps_map = propagate_pack_patterns(ic, site, FORWARD); + + std::unordered_map> pps_map; + + for (auto pp : backward_pps_map) + pps_map.emplace(pp.first, std::set{}); + + for (auto pp : forward_pps_map) + pps_map.emplace(pp.first, std::set{}); + + for (auto for_pp_pair : forward_pps_map) + for (auto back_pp_pair : backward_pps_map) + for (auto for_pp : for_pp_pair.second) + for (auto back_pp : back_pp_pair.second) { + std::string pp_name = for_pp + "_" + back_pp; + pps_map.at(for_pp_pair.first).insert(pp_name); + pps_map.at(back_pp_pair.first).insert(pp_name); + } + + for (auto pair : pps_map) { + t_interconnect* pp_ic = pair.first; + + auto num_pp = pair.second.size(); + pp_ic->num_annotations = num_pp; + pp_ic->annotations = new t_pin_to_pin_annotation[num_pp]; + + int idx = 0; + for (auto pp_name : pair.second) + pp_ic->annotations[idx++] = get_pack_pattern(pp_name, pp_ic->input_string, pp_ic->output_string); + } + } + } + } + + // Propagates and generates all pack_patterns required for the given ic. + // This is necessary to find all root blocks that generate the pack pattern. + std::unordered_map> propagate_pack_patterns(t_interconnect* ic, Device::SiteType::Reader& site, e_pp_dir direction) { + auto site_pins = site.getBelPins(); + + std::string endpoint = direction == BACKWARD ? ic->input_string : ic->output_string; + auto ic_endpoints = vtr::split(endpoint, " "); + + std::unordered_map> pps_map; + + bool is_backward = direction == BACKWARD; + + for (auto ep : ic_endpoints) { + auto parts = vtr::split(ep, "."); + auto bel = parts[0]; + auto pin = parts[1]; + + if (bel == str(site.getName())) + return pps_map; + + // Assign mode and pb_type + t_mode* parent_mode = ic->parent_mode; + t_pb_type* pb_type = nullptr; + for (int ipb = 0; ipb < parent_mode->num_pb_type_children; ipb++) + if (std::string(parent_mode->pb_type_children[ipb].name) == bel) + pb_type = &parent_mode->pb_type_children[ipb]; + + VTR_ASSERT(pb_type != nullptr); + + auto bel_reader = get_bel_reader(site, remove_bel_suffix(bel)); + + // Passing through routing mux. Check at the muxes input pins interconnects + if (bel_reader.getCategory() == Device::BELCategory::ROUTING) { + for (auto bel_pin : bel_reader.getPins()) { + auto pin_reader = site_pins[bel_pin]; + auto pin_name = str(pin_reader.getName()); + + if (pin_reader.getDir() != (is_backward ? INPUT : OUTPUT)) + continue; + + for (int iic = 0; iic < parent_mode->num_interconnect; iic++) { + t_interconnect* other_ic = &parent_mode->interconnect[iic]; + + if (std::string(ic->name) == std::string(other_ic->name)) + continue; + + std::string ic_to_find = bel + "." + pin_name; + + bool found = false; + for (auto out : vtr::split(is_backward ? other_ic->output_string : other_ic->input_string, " ")) { + found |= out == ic_to_find; + } + + if (found) { + // An output interconnect to propagate was found, continue searching + auto res = propagate_pack_patterns(other_ic, site, direction); + + for (auto pp_map : res) + pps_map.emplace(pp_map.first, pp_map.second); + } + } + } + } else { + VTR_ASSERT(bel_reader.getCategory() == Device::BELCategory::LOGIC); + + for (int imode = 0; imode < pb_type->num_modes; imode++) { + t_mode* mode = &pb_type->modes[imode]; + + for (int iic = 0; iic < mode->num_interconnect; iic++) { + t_interconnect* other_ic = &mode->interconnect[iic]; + + bool found = false; + for (auto other_ep : vtr::split(is_backward ? other_ic->output_string : other_ic->input_string, " ")) { + found |= other_ep == ep; + } + + if (found) { + std::string pp_name = std::string(pb_type->name) + "." + std::string(mode->name); + + std::set pp{pp_name}; + auto res = pps_map.emplace(other_ic, pp); + + if (!res.second) + res.first->second.insert(pp_name); + } + } + } + } } + + return pps_map; } t_pin_to_pin_annotation get_pack_pattern(std::string ic_name, std::string input, std::string output) { auto pp = new t_pin_to_pin_annotation; - std::string pp_name = ic_name + "_" + output; + std::string pp_name = ic_name; pp->prop = (int*)vtr::calloc(1, sizeof(int)); pp->value = (char**)vtr::calloc(1, sizeof(char*)); @@ -1507,7 +1666,7 @@ struct ArchReader { std::unordered_map port_name_to_wire_name; int idx = 0; - for (auto dir : {LogicalNetlist::Netlist::Direction::INPUT, LogicalNetlist::Netlist::Direction::OUTPUT}) { + for (auto dir : {INPUT, OUTPUT}) { int port_idx_by_type = 0; for (auto pin : site.getPins()) { if (pin.getDir() != dir) @@ -1531,7 +1690,7 @@ struct ArchReader { port.is_clock = false; port.is_non_clock_global = false; - if (dir == LogicalNetlist::Netlist::Direction::INPUT) { + if (dir == INPUT) { port.type = IN_PORT; icount++; } else { @@ -1737,7 +1896,7 @@ struct ArchReader { pip_timing_models_list.push_back(entry); } - auto num_switches = pip_timing_models.size() + 2; + size_t num_switches = pip_timing_models.size() + 2; std::string switch_name; arch_->num_switches = num_switches; @@ -1747,7 +1906,7 @@ struct ArchReader { } float R, Cin, Cint, Cout, Tdel; - for (int i = 0; i < (int)num_switches; ++i) { + for (size_t i = 0; i < num_switches; ++i) { t_arch_switch_inf* as = &arch_->Switches[i]; R = Cin = Cint = Cout = Tdel = 0.0; @@ -1838,7 +1997,7 @@ struct ArchReader { int num_seg = 1; //wire_names.size(); arch_->Segments.resize(num_seg); - uint32_t index = 0; + int index = 0; for (auto i : wire_names) { if (index >= num_seg) break; From c513f9cfdff36a2bd4d76a2c5b0f7e236acf14c7 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Wed, 15 Dec 2021 17:09:32 +0100 Subject: [PATCH 16/22] libs: arch: interchange: clean and add in-code comments Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 173 +++++++++++------- 1 file changed, 104 insertions(+), 69 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 6a4cc40238e..359c1775af9 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -150,6 +150,7 @@ static float get_corner_value(Device::CornerModel::Reader model, const char* spe return 0.; } +/** @brief Returns the port corresponding to the given model in the architecture */ static t_model_ports* get_model_port(t_arch* arch, std::string model, std::string port, bool fail = true) { for (t_model* m : {arch->models, arch->model_library}) { for (; m != nullptr; m = m->next) { @@ -170,6 +171,7 @@ static t_model_ports* get_model_port(t_arch* arch, std::string model, std::strin return nullptr; } +/** @brief Returns the specified architecture model */ static t_model* get_model(t_arch* arch, std::string model) { for (t_model* m : {arch->models, arch->model_library}) for (; m != nullptr; m = m->next) @@ -180,6 +182,7 @@ static t_model* get_model(t_arch* arch, std::string model) { "Could not find model: %s\n", model.c_str()); } +/** @brief Returns the physical or logical type by its name */ template static T* get_type_by_name(const char* type_name, std::vector& types) { for (auto& type : types) { @@ -192,6 +195,7 @@ static T* get_type_by_name(const char* type_name, std::vector& types) { "Could not find type: %s\n", type_name); } +/** @brief Returns a generic port instantiation for a complex block */ static t_port get_generic_port(t_arch* arch, t_pb_type* pb_type, PORTS dir, @@ -219,6 +223,7 @@ static t_port get_generic_port(t_arch* arch, return port; } +/** @brief Returns true if a given port name exists in the given complex block */ static bool block_port_exists(t_pb_type* pb_type, std::string port_name) { for (int iport = 0; iport < pb_type->num_ports; iport++) { const t_port port = pb_type->ports[iport]; @@ -230,6 +235,25 @@ static bool block_port_exists(t_pb_type* pb_type, std::string port_name) { return false; } +/** @brief Returns a pack pattern given it's name, input and output strings */ +static t_pin_to_pin_annotation get_pack_pattern(std::string pp_name, std::string input, std::string output) { + t_pin_to_pin_annotation pp; + + pp.prop = (int*)vtr::calloc(1, sizeof(int)); + pp.value = (char**)vtr::calloc(1, sizeof(char*)); + + pp.type = E_ANNOT_PIN_TO_PIN_PACK_PATTERN; + pp.format = E_ANNOT_PIN_TO_PIN_CONSTANT; + pp.prop[0] = (int)E_ANNOT_PIN_TO_PIN_PACK_PATTERN_NAME; + pp.value[0] = vtr::strdup(pp_name.c_str()); + pp.input_pins = vtr::strdup(input.c_str()); + pp.output_pins = vtr::strdup(output.c_str()); + pp.num_value_prop_pairs = 1; + pp.clock = nullptr; + + return pp; +} + /****************** End Utility functions ******************/ struct ArchReader { @@ -303,10 +327,13 @@ struct ArchReader { std::unordered_map segment_name_to_segment_idx; // Utils + + /** @brief Returns the string corresponding to the given index */ std::string str(size_t idx) { return arch_->interned_strings[idx].get(&arch_->strings); } + /** @brief Get the BEL count of a site depending on its category (e.g. logic or routing BELs) */ int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category) { int count = 0; for (auto bel : site.getBels()) { @@ -321,6 +348,7 @@ struct ArchReader { return count; } + /** @brief Get the BEL reader given its name and site */ Device::BEL::Reader get_bel_reader(Device::SiteType::Reader& site, std::string bel_name) { for (auto bel : site.getBels()) if (str(bel.getName()) == bel_name) @@ -328,6 +356,7 @@ struct ArchReader { VTR_ASSERT(0); } + /** @brief Get the BEL pin reader given its name, site and corresponding BEL */ Device::BELPin::Reader get_bel_pin_reader(Device::SiteType::Reader& site, Device::BEL::Reader& bel, std::string pin_name) { auto bel_pins = site.getBelPins(); @@ -339,7 +368,8 @@ struct ArchReader { VTR_ASSERT(0); } - std::string get_ic_prefix(Device::SiteType::Reader& site, Device::BEL::Reader& bel) { + /** @brief Get the BEL name, with an optional deduplication suffix in case its name collides with the site name */ + std::string get_bel_name(Device::SiteType::Reader& site, Device::BEL::Reader& bel) { if (bel.getCategory() == Device::BELCategory::SITE_PORT) return str(site.getName()); @@ -349,6 +379,18 @@ struct ArchReader { return site_name == bel_name ? bel_name + bel_dedup_suffix_ : bel_name; } + /** @brief Returns the name of the input argument BEL with optionally the de-duplication suffix removed */ + std::string remove_bel_suffix(std::string bel) { + std::smatch regex_matches; + std::string regex = std::string("(.*)") + bel_dedup_suffix_; + const std::regex bel_regex(regex.c_str()); + if (std::regex_match(bel, regex_matches, bel_regex)) + return regex_matches[1].str(); + + return bel; + } + + /** @brief Returns true in case the input argument corresponds to the name of a LUT */ bool is_lut(std::string name) { for (auto cell : arch_->lut_cells) if (cell.name == name) @@ -361,20 +403,12 @@ struct ArchReader { return false; } + /** @brief Returns true in case the input argument corresponds to a PAD BEL */ bool is_pad(std::string name) { return pad_bels_.count(name) != 0; } - std::string remove_bel_suffix(std::string bel) { - std::smatch regex_matches; - std::string regex = std::string("(.*)") + bel_dedup_suffix_; - const std::regex bel_regex(regex.c_str()); - if (std::regex_match(bel, regex_matches, bel_regex)) - return regex_matches[1].str(); - - return bel; - } - + /** @brief Returns an intermediate map representing all the interconnects to be added in a site */ std::unordered_map get_interconnects(Device::SiteType::Reader& site) { // dictionary: // - key: interconnect name @@ -395,7 +429,7 @@ struct ArchReader { std::string bel_pin_name = str(bel_pin.getName()); auto bel = get_bel_reader(site, str(bel_pin.getBel())); - auto bel_name = get_ic_prefix(site, bel); + auto bel_name = get_bel_name(site, bel); auto bel_is_pad = is_pad(bel_name); @@ -419,7 +453,7 @@ struct ArchReader { std::string bel_pin_name = str(bel_pin.getName()); auto bel = get_bel_reader(site, str(bel_pin.getBel())); - auto bel_name = get_ic_prefix(site, bel); + auto bel_name = get_bel_name(site, bel); if (!is_pad(bel_name)) continue; @@ -442,10 +476,10 @@ struct ArchReader { std::string out_bel_pin_name = str(bel_pin.getName()); auto out_bel = get_bel_reader(site, str(bel_pin.getBel())); - auto out_bel_name = get_ic_prefix(site, out_bel); + auto out_bel_name = get_bel_name(site, out_bel); auto in_bel = out_pin_bel; - auto in_bel_name = get_ic_prefix(site, in_bel); + auto in_bel_name = get_bel_name(site, in_bel); auto in_bel_pin_name = out_pin_name; bool skip_in_bel = in_bel.getCategory() == Device::BELCategory::LOGIC && take_bels_.count(in_bel.getName()) == 0; @@ -530,7 +564,6 @@ struct ArchReader { * - process_cell_bel_mapping: processes mappings between a cell and the possible BELs location for that cell * - process_constants: processes constants cell and net names */ - void process_bels_and_sites() { auto tiles = ar_.getTileList(); auto tile_types = ar_.getTileTypeList(); @@ -719,6 +752,8 @@ struct ArchReader { if (is_lut(prim_name)) continue; + // Check whether the model can be placed in at least one + // BEL that was marked as valid (e.g. added to the take_bels_ data structure) bool has_bel = false; for (auto bel_cell_map : bel_cell_mappings_) { auto bel_name = bel_cell_map.first; @@ -842,6 +877,7 @@ struct ArchReader { auto siteTypeList = ar_.getSiteTypeList(); int index = 0; + // TODO: Make this dynamic depending on data from the interchange auto EMPTY = get_empty_logical_type(std::string("NULL")); EMPTY.index = index; ltypes_.push_back(EMPTY); @@ -887,6 +923,7 @@ struct ArchReader { mode->num_pb_type_children = bel_count; mode->pb_type_children = new t_pb_type[bel_count]; + // Iterate over all the BELs to create intermedite complex block types int count = 0; for (auto bel : bels) { auto category = bel.getCategory(); @@ -931,6 +968,7 @@ struct ArchReader { } } + /** @brief Generates a LUT primitive block starting from the intermediate pb type */ void process_lut_block(t_pb_type* lut) { lut->num_modes = 1; lut->modes = new t_mode[1]; @@ -1003,6 +1041,7 @@ struct ArchReader { } } + /** @brief Generates the leaf pb types for the PAD type */ void process_pad_block(t_pb_type* pad, Device::BEL::Reader& bel, Device::SiteType::Reader& site) { // For now, hard-code two modes for pads, so that PADs can either be IPADs or OPADs pad->num_modes = 2; @@ -1126,6 +1165,9 @@ struct ArchReader { imode->interconnect[0] = *i_ic; } + /** @brief Generates the leaf pb types for a generic intermediate block, with as many modes + * as the number of models that can be used in this complex block. + */ void process_generic_block(t_pb_type* pb_type, Device::BEL::Reader& bel, Device::SiteType::Reader& site) { std::string pb_name = std::string(pb_type->name); @@ -1209,7 +1251,7 @@ struct ArchReader { mode->num_interconnect = num_ports; mode->interconnect = new t_interconnect[num_ports]; - std::unordered_map> pins; + std::set> pins; ic_count = 0; for (auto pin_map : map.pins) { auto cell_pin = str(pin_map.first); @@ -1235,7 +1277,7 @@ struct ArchReader { auto pin_reader = get_bel_pin_reader(site, bel, bel_pin); bool is_inout = pin_reader.getDir() == INOUT; - pins.emplace(cell_pin, std::make_pair(dir, size)); + pins.emplace(cell_pin, dir, size); std::string istr, ostr, ic_name; switch (dir) { @@ -1268,6 +1310,9 @@ struct ArchReader { } } + /** @brief Generates a routing block to allow for cascading routing blocks to be + * placed in the same complex block type. + */ void process_routing_block(t_pb_type* pb_type) { pb_type->num_modes = 1; pb_type->modes = new t_mode[1]; @@ -1317,37 +1362,38 @@ struct ArchReader { ic->output_string = vtr::strdup(ostr.c_str()); } + /** @brief Processes all the ports of a given complex block. + * If a bel name index is specified, the bel pins are processed, otherwise the site ports + * are processed instead. + */ void process_block_ports(t_pb_type* pb_type, Device::SiteType::Reader& site, size_t bel_name = OPEN) { // Prepare data based on pb_type level - std::unordered_map> pins; + std::set> pins; if (bel_name == (size_t)OPEN) { for (auto pin : site.getPins()) { auto dir = pin.getDir() == INPUT ? IN_PORT : OUT_PORT; - pins.emplace(str(pin.getName()), std::make_pair(dir, 1)); + pins.emplace(str(pin.getName()), dir, 1); } } else { - for (auto bel : site.getBels()) { - if (bel.getName() != bel_name) - continue; + auto bel = get_bel_reader(site, str(bel_name)); - for (auto bel_pin : bel.getPins()) { - auto pin = site.getBelPins()[bel_pin]; - auto dir = pin.getDir(); - - switch (dir) { - case INPUT: - pins.emplace(str(pin.getName()), std::make_pair(IN_PORT, 1)); - break; - case OUTPUT: - pins.emplace(str(pin.getName()), std::make_pair(OUT_PORT, 1)); - break; - case INOUT: - pins.emplace(str(pin.getName()) + in_suffix_, std::make_pair(IN_PORT, 1)); - pins.emplace(str(pin.getName()) + out_suffix_, std::make_pair(OUT_PORT, 1)); - break; - default: - VTR_ASSERT(0); - } + for (auto bel_pin : bel.getPins()) { + auto pin = site.getBelPins()[bel_pin]; + auto dir = pin.getDir(); + + switch (dir) { + case INPUT: + pins.emplace(str(pin.getName()), IN_PORT, 1); + break; + case OUTPUT: + pins.emplace(str(pin.getName()), OUT_PORT, 1); + break; + case INOUT: + pins.emplace(str(pin.getName()) + in_suffix_, IN_PORT, 1); + pins.emplace(str(pin.getName()) + out_suffix_, OUT_PORT, 1); + break; + default: + VTR_ASSERT(0); } } } @@ -1355,7 +1401,8 @@ struct ArchReader { create_ports(pb_type, pins); } - void create_ports(t_pb_type* pb_type, std::unordered_map>& pins, std::string model = "") { + /** @brief Generates all the port for a complex block, given its pointer and a map of ports (key) and their direction and width */ + void create_ports(t_pb_type* pb_type, std::set>& pins, std::string model = "") { std::unordered_set names; auto num_ports = pins.size(); @@ -1369,17 +1416,15 @@ struct ArchReader { int pin_count = 0; for (auto dir : {IN_PORT, OUT_PORT}) { int pins_dir_count = 0; - for (auto pin_pair : pins) { - auto pin_name = pin_pair.first; + for (auto pin_tuple : pins) { + std::string pin_name; PORTS pin_dir; int num_pins; - std::tie(pin_dir, num_pins) = pin_pair.second; + std::tie(pin_name, pin_dir, num_pins) = pin_tuple; if (pin_dir != dir) continue; - VTR_ASSERT(names.insert(pin_name).second); - bool is_input = dir == IN_PORT; pb_type->num_input_pins += is_input ? 1 : 0; pb_type->num_output_pins += is_input ? 0 : 1; @@ -1396,6 +1441,7 @@ struct ArchReader { } } + /** @brief Processes and creates the interconnects corresponding to a given mode */ void process_interconnects(t_mode* mode, Device::SiteType::Reader& site) { auto ics = get_interconnects(site); auto num_ic = ics.size(); @@ -1436,6 +1482,7 @@ struct ArchReader { ic->output_string = vtr::strdup(outputs_str.c_str()); } + // Checks and, in case, adds all the necessary pack patterns to the marked interconnects for (size_t iic = 0; iic < num_ic; iic++) { t_interconnect* ic = &mode->interconnect[iic]; @@ -1453,6 +1500,13 @@ struct ArchReader { for (auto pp : forward_pps_map) pps_map.emplace(pp.first, std::set{}); + // Cross-product of all pack-patterns added both when exploring backwards and forward. + // E.g.: + // Generated pack patterns + // - backward: OBUFDS, OBUF + // - forward: OPAD + // Final pack patterns: + // - OBUFDS_OPAD, OBUF_OPAD for (auto for_pp_pair : forward_pps_map) for (auto back_pp_pair : backward_pps_map) for (auto for_pp : for_pp_pair.second) @@ -1477,8 +1531,9 @@ struct ArchReader { } } - // Propagates and generates all pack_patterns required for the given ic. - // This is necessary to find all root blocks that generate the pack pattern. + /** @brief Propagates and generates all pack_patterns required for the given ic. + * This is necessary to find all root blocks that generate the pack pattern. + */ std::unordered_map> propagate_pack_patterns(t_interconnect* ic, Device::SiteType::Reader& site, e_pp_dir direction) { auto site_pins = site.getBelPins(); @@ -1570,26 +1625,6 @@ struct ArchReader { return pps_map; } - t_pin_to_pin_annotation get_pack_pattern(std::string ic_name, std::string input, std::string output) { - auto pp = new t_pin_to_pin_annotation; - - std::string pp_name = ic_name; - - pp->prop = (int*)vtr::calloc(1, sizeof(int)); - pp->value = (char**)vtr::calloc(1, sizeof(char*)); - - pp->type = E_ANNOT_PIN_TO_PIN_PACK_PATTERN; - pp->format = E_ANNOT_PIN_TO_PIN_CONSTANT; - pp->prop[0] = (int)E_ANNOT_PIN_TO_PIN_PACK_PATTERN_NAME; - pp->value[0] = vtr::strdup(pp_name.c_str()); - pp->input_pins = vtr::strdup(input.c_str()); - pp->output_pins = vtr::strdup(output.c_str()); - pp->num_value_prop_pairs = 1; - pp->clock = nullptr; - - return *pp; - } - // Physical Tiles void process_tiles() { auto EMPTY = get_empty_physical_type(std::string("NULL")); From a017e5384199dbb07d88cabb4680298cb1bf2488 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Wed, 15 Dec 2021 17:13:22 +0100 Subject: [PATCH 17/22] vpr: test: adjust grid layout test as NULL tile is not in the grid Signed-off-by: Alessandro Comodi --- vpr/test/test_interchange_device.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vpr/test/test_interchange_device.cpp b/vpr/test/test_interchange_device.cpp index ac939714f1a..4d7e0d23ce3 100644 --- a/vpr/test/test_interchange_device.cpp +++ b/vpr/test/test_interchange_device.cpp @@ -53,7 +53,7 @@ TEST_CASE("read_interchange_layout", "[vpr]") { REQUIRE(gd.height == 12); REQUIRE(gd.width == 12); - std::unordered_map tile_types({{"NULL", false}, {"PWR", false}, {"IOB", false}, {"CLB", false}}); + std::unordered_map tile_types({{"PWR", false}, {"IOB", false}, {"CLB", false}}); for (auto& loc : gd.loc_defs) { auto ret = tile_types.find(loc.block_type); REQUIRE(ret != tile_types.end()); From 21ee47cc186428cd2636e0981835e85f21d1ec26 Mon Sep 17 00:00:00 2001 From: Maciej Kurc Date: Wed, 8 Dec 2021 09:56:26 +0100 Subject: [PATCH 18/22] Added reading LUT elements from FPGA interchange Signed-off-by: Maciej Kurc --- libs/libarchfpga/src/physical_types.h | 16 +- .../src/read_fpga_interchange_arch.cpp | 347 ++++++++++++++---- libs/libvtrutil/src/vtr_util.cpp | 30 ++ libs/libvtrutil/src/vtr_util.h | 2 + vpr/test/test_interchange_device.cpp | 20 +- 5 files changed, 326 insertions(+), 89 deletions(-) diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 87095a447dc..4c6442efff7 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -1743,6 +1743,20 @@ struct t_lut_bel { std::vector input_pins; std::string output_pin; + + bool operator==(const t_lut_bel& other) const { + return name == other.name && input_pins == other.input_pins && output_pin == other.output_pin; + } +}; + +struct t_lut_element { + std::string site_type; + int width; + std::vector lut_bels; + + bool operator==(const t_lut_element& other) const { + return site_type == other.site_type && width == other.width && lut_bels == other.lut_bels; + } }; /* Detailed routing architecture */ @@ -1795,7 +1809,7 @@ struct t_arch { // Luts std::vector lut_cells; - std::vector lut_bels; + std::unordered_map> lut_elements; //The name of the switch used for the input connection block (i.e. to //connect routing tracks to block pins). diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index 359c1775af9..fa58d2f6f32 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -391,18 +391,44 @@ struct ArchReader { } /** @brief Returns true in case the input argument corresponds to the name of a LUT */ - bool is_lut(std::string name) { + bool is_lut(std::string name, const std::string site = std::string()) { for (auto cell : arch_->lut_cells) if (cell.name == name) return true; - for (auto bel : arch_->lut_bels) - if (bel.name == name) - return true; + for (const auto& it : arch_->lut_elements) { + if (!site.empty() && site != it.first) { + continue; + } + + for (const auto& lut_element : it.second) { + for (const auto& lut_bel : lut_element.lut_bels) { + if (lut_bel.name == name) { + return true; + } + } + } + } return false; } + t_lut_element* get_lut_element_for_bel(const std::string& site_type, const std::string& bel_name) { + if (!arch_->lut_elements.count(site_type)) { + return nullptr; + } + + for (auto& lut_element : arch_->lut_elements.at(site_type)) { + for (auto& lut_bel : lut_element.lut_bels) { + if (lut_bel.name == bel_name) { + return &lut_element; + } + } + } + + return nullptr; + } + /** @brief Returns true in case the input argument corresponds to a PAD BEL */ bool is_pad(std::string name) { return pad_bels_.count(name) != 0; @@ -412,8 +438,11 @@ struct ArchReader { std::unordered_map get_interconnects(Device::SiteType::Reader& site) { // dictionary: // - key: interconnect name - // - value: (inputs string, outputs string, interconnect type) + // - value: interconnect data std::unordered_map ics; + + const std::string site_type = str(site.getName()); + for (auto wire : site.getSiteWires()) { std::string wire_name = str(wire.getName()); @@ -487,6 +516,32 @@ struct ArchReader { if (skip_in_bel || skip_out_bel) continue; + // LUT bels are nested under pb_types which represent LUT + // elements. Check if a BEL belongs to a LUT element and + // adjust pb_type name in the interconnect accordingly. + auto get_lut_element_index = [&](const std::string& bel_name) { + auto lut_element = get_lut_element_for_bel(site_type, bel_name); + if (lut_element == nullptr) { + return -1; + } + + const auto& lut_elements = arch_->lut_elements.at(site_type); + auto it = std::find(lut_elements.begin(), lut_elements.end(), *lut_element); + VTR_ASSERT(it != lut_elements.end()); + return (int)std::distance(lut_elements.begin(), it); + }; + + int index = -1; + + index = get_lut_element_index(out_bel_name); + if (index >= 0) { + out_bel_name = "LUT" + std::to_string(index); + } + index = get_lut_element_index(in_bel_name); + if (index >= 0) { + in_bel_name = "LUT" + std::to_string(index); + } + std::string ostr = out_bel_name + "." + out_bel_pin_name; std::string istr = in_bel_name + "." + in_bel_pin_name; @@ -615,27 +670,25 @@ struct ArchReader { for (auto lut_elem : lut_def.getLutElements()) { for (auto lut : lut_elem.getLuts()) { + t_lut_element element; + element.site_type = lut_elem.getSite().cStr(); + element.width = lut.getWidth(); + for (auto bel : lut.getBels()) { t_lut_bel lut_bel; - - std::string name = bel.getName().cStr(); - lut_bel.name = name; - - // Check for duplicates - auto is_duplicate = [name](t_lut_bel l) { return l.name == name; }; - auto res = std::find_if(arch_->lut_bels.begin(), arch_->lut_bels.end(), is_duplicate); - if (res != arch_->lut_bels.end()) - continue; - + lut_bel.name = bel.getName().cStr(); std::vector ipins; + for (auto pin : bel.getInputPins()) ipins.push_back(pin.cStr()); lut_bel.input_pins = ipins; lut_bel.output_pin = bel.getOutputPin().cStr(); - arch_->lut_bels.push_back(lut_bel); + element.lut_bels.push_back(lut_bel); } + + arch_->lut_elements[element.site_type].push_back(element); } } } @@ -919,12 +972,31 @@ struct ArchReader { mode->name = vtr::strdup("default"); mode->disable_packing = false; - int bel_count = get_bel_type_count(site, Device::BELCategory::LOGIC) + get_bel_type_count(site, Device::BELCategory::ROUTING); - mode->num_pb_type_children = bel_count; - mode->pb_type_children = new t_pb_type[bel_count]; + // Get LUT elements for this site + std::vector lut_elements; + if (arch_->lut_elements.count(name)) { + lut_elements = arch_->lut_elements.at(name); + } - // Iterate over all the BELs to create intermedite complex block types + // Count non-LUT BELs plus LUT elements + int block_count = 0; int count = 0; + + for (auto bel : site.getBels()) { + if (bel.getCategory() != Device::BELCategory::LOGIC) { + continue; + } + if (is_lut(str(bel.getName()), name)) { + continue; + } + block_count++; + } + block_count += lut_elements.size(); + + mode->num_pb_type_children = block_count; + mode->pb_type_children = new t_pb_type[mode->num_pb_type_children]; + + // Add regular BELs for (auto bel : bels) { auto category = bel.getCategory(); if (bel.getCategory() == Device::BELCategory::SITE_PORT) @@ -935,6 +1007,9 @@ struct ArchReader { if (take_bels_.count(bel.getName()) == 0 && is_logic) continue; + if (is_lut(str(bel.getName()), name)) + continue; + auto bel_name = str(bel.getName()); std::pair key(name, bel_name); @@ -949,9 +1024,7 @@ struct ArchReader { if (!is_pad(bel_name)) process_block_ports(mid_pb_type, site, bel.getName()); - if (is_lut(bel_name)) - process_lut_block(mid_pb_type); - else if (is_pad(bel_name)) + if (is_pad(bel_name)) process_pad_block(mid_pb_type, bel, site); else if (is_logic) process_generic_block(mid_pb_type, bel, site); @@ -959,8 +1032,19 @@ struct ArchReader { VTR_ASSERT(category == Device::BELCategory::ROUTING); process_routing_block(mid_pb_type); } + } + + // Add LUT elements + for (size_t i = 0; i < lut_elements.size(); ++i) { + const auto& lut_element = lut_elements[i]; + + auto mid_pb_type = &mode->pb_type_children[count++]; + mid_pb_type->name = vtr::stringf("LUT%d", i); + mid_pb_type->num_pb = 1; + mid_pb_type->parent_mode = mode; + mid_pb_type->blif_model = nullptr; - mode->pb_type_children[count++] = *mid_pb_type; + process_lut_element(mid_pb_type, lut_element); } process_interconnects(mode, site); @@ -968,79 +1052,180 @@ struct ArchReader { } } - /** @brief Generates a LUT primitive block starting from the intermediate pb type */ - void process_lut_block(t_pb_type* lut) { - lut->num_modes = 1; - lut->modes = new t_mode[1]; + /** @brief Processes a LUT element starting from the intermediate pb type */ + void process_lut_element(t_pb_type* parent, const t_lut_element& lut_element) { + // Collect ports for the parent pb_type representing the whole LUT + // element + std::set> parent_ports; + for (const auto& lut_bel : lut_element.lut_bels) { + for (const auto& name : lut_bel.input_pins) { + parent_ports.emplace(name, IN_PORT, 1); + } - // Check for duplicates - std::string lut_name = lut->name; - auto find_lut = [lut_name](t_lut_bel l) { return l.name == lut_name; }; - auto res = std::find_if(arch_->lut_bels.begin(), arch_->lut_bels.end(), find_lut); - VTR_ASSERT(res != arch_->lut_bels.end()); - auto lut_bel = *res; + parent_ports.emplace(lut_bel.output_pin, OUT_PORT, 1); + } - auto mode = &lut->modes[0]; - mode->name = vtr::strdup("lut"); - mode->parent_pb_type = lut; - mode->index = 0; - mode->num_pb_type_children = 1; - mode->pb_type_children = new t_pb_type[1]; + // Create the ports + create_ports(parent, parent_ports); - auto new_leaf = new t_pb_type; - new_leaf->name = vtr::strdup("lut_child"); - new_leaf->num_pb = 1; - new_leaf->parent_mode = mode; + // Make a single mode for each member LUT of the LUT element + parent->num_modes = (int)lut_element.lut_bels.size(); + parent->modes = new t_mode[parent->num_modes]; - int num_ports = 2; - new_leaf->num_ports = num_ports; - new_leaf->ports = (t_port*)vtr::calloc(num_ports, sizeof(t_port)); - new_leaf->blif_model = vtr::strdup(MODEL_NAMES); - new_leaf->model = get_model(arch_, std::string(MODEL_NAMES)); + for (size_t i = 0; i < lut_element.lut_bels.size(); ++i) { + const t_lut_bel& lut_bel = lut_element.lut_bels[i]; + auto mode = &parent->modes[i]; - auto in_size = lut_bel.input_pins.size(); - new_leaf->ports[0] = get_generic_port(arch_, new_leaf, IN_PORT, "in", MODEL_NAMES, in_size); - new_leaf->ports[1] = get_generic_port(arch_, new_leaf, OUT_PORT, "out", MODEL_NAMES); + mode->name = vtr::strdup(lut_bel.name.c_str()); + mode->parent_pb_type = parent; + mode->index = i; - mode->pb_type_children[0] = *new_leaf; + // Leaf pb_type block for the LUT + mode->num_pb_type_children = 1; + mode->pb_type_children = new t_pb_type[mode->num_pb_type_children]; - // Num inputs + 1 (output pin) - int num_pins = in_size + 1; + auto pb_type = &mode->pb_type_children[0]; + pb_type->name = vtr::strdup(lut_bel.name.c_str()); + pb_type->num_pb = 1; + pb_type->parent_mode = mode; + pb_type->blif_model = nullptr; - mode->num_interconnect = num_pins; - mode->interconnect = new t_interconnect[num_pins]; + process_lut_block(pb_type, lut_bel); - for (int i = 0; i < num_pins; i++) { - auto ic = new t_interconnect; + // Mode interconnect + mode->num_interconnect = lut_bel.input_pins.size() + 1; + mode->interconnect = new t_interconnect[mode->num_interconnect]; - std::stringstream istr; - std::stringstream ostr; - std::string input_string; - std::string output_string; + // Inputs + for (size_t j = 0; j < lut_bel.input_pins.size(); ++j) { + auto* ic = &mode->interconnect[j]; - if (i < num_pins - 1) { - istr << lut_bel.input_pins[i]; - ostr << "in[" << i << "]"; - input_string = std::string(lut->name) + std::string(".") + istr.str(); - output_string = std::string(new_leaf->name) + std::string(".") + ostr.str(); - } else { - istr << "out"; - ostr << lut_bel.output_pin; - input_string = std::string(new_leaf->name) + std::string(".") + istr.str(); - output_string = std::string(lut->name) + std::string(".") + ostr.str(); + ic->type = DIRECT_INTERC; + ic->parent_mode = mode; + ic->parent_mode_index = mode->index; + + ic->input_string = vtr::stringf("%s.%s", parent->name, lut_bel.input_pins[j].c_str()); + ic->output_string = vtr::stringf("%s.in[%d]", pb_type->name, j); + ic->name = vtr::stringf("%s_to_%s", + ic->input_string, + ic->output_string); } - std::string name = istr.str() + std::string("_") + ostr.str(); - ic->name = vtr::strdup(name.c_str()); + + // Output + auto* ic = &mode->interconnect[mode->num_interconnect - 1]; ic->type = DIRECT_INTERC; - ic->parent_mode_index = 0; ic->parent_mode = mode; - ic->input_string = vtr::strdup(input_string.c_str()); - ic->output_string = vtr::strdup(output_string.c_str()); + ic->parent_mode_index = mode->index; - mode->interconnect[i] = *ic; + ic->input_string = vtr::stringf("%s.out", pb_type->name); + ic->output_string = vtr::stringf("%s.%s", parent->name, lut_bel.output_pin.c_str()); + ic->name = vtr::stringf("%s_to_%s", + ic->input_string, + ic->output_string); } } + /** @brief Processes a LUT primitive starting from the intermediate pb type */ + void process_lut_block(t_pb_type* pb_type, const t_lut_bel& lut_bel) { + // Create port list + size_t width = lut_bel.input_pins.size(); + + std::set> ports; + ports.emplace("in", IN_PORT, width); + ports.emplace("out", OUT_PORT, 1); + + create_ports(pb_type, ports); + + // Make two modes. One for LUT-thru and another for the actual LUT bel + pb_type->num_modes = 2; + pb_type->modes = new t_mode[pb_type->num_modes]; + + // ................................................ + // LUT-thru + t_mode* mode = &pb_type->modes[0]; + + // Mode + mode->name = vtr::strdup("wire"); + mode->parent_pb_type = pb_type; + mode->index = 0; + mode->num_pb_type_children = 0; + + // Mode interconnect + mode->num_interconnect = 1; + mode->interconnect = new t_interconnect[mode->num_interconnect]; + t_interconnect* ic = &mode->interconnect[0]; + + ic->input_string = vtr::stringf("%s.in", pb_type->name); + ic->output_string = vtr::stringf("%s.out", pb_type->name); + ic->name = vtr::strdup("passthrough"); + + ic->type = COMPLETE_INTERC; + ic->parent_mode = mode; + ic->parent_mode_index = mode->index; + + // ................................................ + // LUT BEL + mode = &pb_type->modes[1]; + + // Mode + mode->name = vtr::strdup("lut"); + mode->parent_pb_type = pb_type; + mode->index = 1; + + // Leaf pb_type + mode->num_pb_type_children = 1; + mode->pb_type_children = new t_pb_type[mode->num_pb_type_children]; + + auto lut = &mode->pb_type_children[0]; + lut->name = vtr::strdup("lut"); + lut->num_pb = 1; + lut->parent_mode = mode; + + lut->blif_model = vtr::strdup(MODEL_NAMES); + lut->model = get_model(arch_, std::string(MODEL_NAMES)); + + lut->num_ports = 2; + lut->ports = (t_port*)vtr::calloc(lut->num_ports, sizeof(t_port)); + lut->ports[0] = get_generic_port(arch_, lut, IN_PORT, "in", MODEL_NAMES, width); + lut->ports[1] = get_generic_port(arch_, lut, OUT_PORT, "out", MODEL_NAMES); + + lut->ports[0].equivalent = PortEquivalence::FULL; + + // Set classes + pb_type->class_type = LUT_CLASS; + lut->class_type = LUT_CLASS; + lut->ports[0].port_class = vtr::strdup("lut_in"); + lut->ports[1].port_class = vtr::strdup("lut_out"); + + // Mode interconnect + mode->num_interconnect = 2; + mode->interconnect = new t_interconnect[mode->num_interconnect]; + + // Input + ic = &mode->interconnect[0]; + ic->type = DIRECT_INTERC; + ic->parent_mode = mode; + ic->parent_mode_index = mode->index; + + ic->input_string = vtr::stringf("%s.in", pb_type->name); + ic->output_string = vtr::stringf("%s.in", lut->name); + ic->name = vtr::stringf("%s_to_%s", + ic->input_string, + ic->output_string); + + // Output + ic = &mode->interconnect[1]; + ic->type = DIRECT_INTERC; + ic->parent_mode = mode; + ic->parent_mode_index = mode->index; + + ic->input_string = vtr::stringf("%s.out", lut->name); + ic->output_string = vtr::stringf("%s.out", pb_type->name); + ic->name = vtr::stringf("%s_to_%s", + ic->input_string, + ic->output_string); + } + /** @brief Generates the leaf pb types for the PAD type */ void process_pad_block(t_pb_type* pad, Device::BEL::Reader& bel, Device::SiteType::Reader& site) { // For now, hard-code two modes for pads, so that PADs can either be IPADs or OPADs @@ -2029,10 +2214,10 @@ struct ArchReader { // the RR graph generation is correct. // This can be removed once the RR graph reader from the interchange // device is ready and functional. - int num_seg = 1; //wire_names.size(); + size_t num_seg = 1; //wire_names.size(); arch_->Segments.resize(num_seg); - int index = 0; + size_t index = 0; for (auto i : wire_names) { if (index >= num_seg) break; diff --git a/libs/libvtrutil/src/vtr_util.cpp b/libs/libvtrutil/src/vtr_util.cpp index 45ee3035883..c897d296f0f 100644 --- a/libs/libvtrutil/src/vtr_util.cpp +++ b/libs/libvtrutil/src/vtr_util.cpp @@ -501,4 +501,34 @@ int get_pid() { #endif } +char* stringf(const char* format, ...) { + // Initial buffer + const int initial_size = 80 + 1; + char* str = (char*)vtr::malloc(initial_size); + + // Init and copy args list + va_list va1, va2; + va_start(va1, format); + va_copy(va2, va1); + + // First attempt + int len = vsnprintf(str, initial_size, format, va1); + VTR_ASSERT(len >= 0); + + // The buffer was too small + if (len >= initial_size) { + str = (char*)vtr::realloc((void*)str, len + 1); + VTR_ASSERT(str != nullptr); + len = vsnprintf(str, len + 1, format, va2); + VTR_ASSERT(len >= 0); + VTR_ASSERT(len <= len); + } + + // Cleanup + va_end(va1); + va_end(va2); + + return str; +} + } // namespace vtr diff --git a/libs/libvtrutil/src/vtr_util.h b/libs/libvtrutil/src/vtr_util.h index 08562d3d092..3d482eb96bb 100644 --- a/libs/libvtrutil/src/vtr_util.h +++ b/libs/libvtrutil/src/vtr_util.h @@ -118,6 +118,8 @@ void uniquify(Container container) { int get_pid(); +char* stringf(const char* format, ...); + } // namespace vtr #endif diff --git a/vpr/test/test_interchange_device.cpp b/vpr/test/test_interchange_device.cpp index 4d7e0d23ce3..22a04eaca1e 100644 --- a/vpr/test/test_interchange_device.cpp +++ b/vpr/test/test_interchange_device.cpp @@ -80,8 +80,6 @@ TEST_CASE("read_interchange_luts", "[vpr]") { std::unordered_set lut_bel_pins = {"A1", "A2", "A3", "A4"}; REQUIRE(arch.lut_cells.size() == 4); - REQUIRE(arch.lut_bels.size() == 2); - for (auto lut_cell : arch.lut_cells) { CHECK(std::find(lut_cells.begin(), lut_cells.end(), lut_cell.name) != lut_cells.end()); REQUIRE(lut_cell.init_param == std::string("INIT")); @@ -89,11 +87,19 @@ TEST_CASE("read_interchange_luts", "[vpr]") { CHECK(std::find(lut_cell_pins.begin(), lut_cell_pins.end(), lut_pin) != lut_cell_pins.end()); } - for (auto lut_bel : arch.lut_bels) { - CHECK(std::find(lut_bels.begin(), lut_bels.end(), lut_bel.name) != lut_bels.end()); - REQUIRE(lut_bel.output_pin == std::string("O")); - for (auto lut_pin : lut_bel.input_pins) - CHECK(std::find(lut_bel_pins.begin(), lut_bel_pins.end(), lut_pin) != lut_bel_pins.end()); + for (const auto& it : arch.lut_elements) { + const auto& lut_elements = it.second; + + for (const auto& lut_element : lut_elements) { + REQUIRE(lut_element.lut_bels.size() == 2); + + for (auto lut_bel : lut_element.lut_bels) { + CHECK(std::find(lut_bels.begin(), lut_bels.end(), lut_bel.name) != lut_bels.end()); + REQUIRE(lut_bel.output_pin == std::string("O")); + for (auto lut_pin : lut_bel.input_pins) + CHECK(std::find(lut_bel_pins.begin(), lut_bel_pins.end(), lut_pin) != lut_bel_pins.end()); + } + } } } From 857e7ce7eb37641cfc7bbf3fbfc3de8b9a0bbf6e Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Thu, 16 Dec 2021 16:58:57 +0100 Subject: [PATCH 19/22] libs: arch: interchange: fix luts that have additional pins Signed-off-by: Alessandro Comodi --- .../src/read_fpga_interchange_arch.cpp | 108 +++++++++++------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index fa58d2f6f32..b32455c6af6 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -41,6 +41,10 @@ static const auto INPUT = LogicalNetlist::Netlist::Direction::INPUT; static const auto OUTPUT = LogicalNetlist::Netlist::Direction::OUTPUT; static const auto INOUT = LogicalNetlist::Netlist::Direction::INOUT; +static const auto LOGIC = Device::BELCategory::LOGIC; +static const auto ROUTING = Device::BELCategory::ROUTING; +static const auto SITE_PORT = Device::BELCategory::SITE_PORT; + // Enum for pack pattern expansion direction enum e_pp_dir { FORWARD = 0, @@ -67,7 +71,7 @@ struct t_bel_cell_mapping { // Intermediate data type to store information on interconnects to be created struct t_ic_data { std::string input; - std::vector outputs; + std::set outputs; bool requires_pack_pattern; }; @@ -334,10 +338,14 @@ struct ArchReader { } /** @brief Get the BEL count of a site depending on its category (e.g. logic or routing BELs) */ - int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category) { + int get_bel_type_count(Device::SiteType::Reader& site, Device::BELCategory category, bool skip_lut = false) { int count = 0; for (auto bel : site.getBels()) { - bool is_logic = category == Device::BELCategory::LOGIC; + auto bel_name = str(bel.getName()); + bool is_logic = category == LOGIC; + + if (skip_lut && is_lut(bel_name, str(site.getName()))) + continue; bool skip_bel = is_logic && take_bels_.count(bel.getName()) == 0; @@ -370,7 +378,7 @@ struct ArchReader { /** @brief Get the BEL name, with an optional deduplication suffix in case its name collides with the site name */ std::string get_bel_name(Device::SiteType::Reader& site, Device::BEL::Reader& bel) { - if (bel.getCategory() == Device::BELCategory::SITE_PORT) + if (bel.getCategory() == SITE_PORT) return str(site.getName()); auto site_name = str(site.getName()); @@ -511,8 +519,8 @@ struct ArchReader { auto in_bel_name = get_bel_name(site, in_bel); auto in_bel_pin_name = out_pin_name; - bool skip_in_bel = in_bel.getCategory() == Device::BELCategory::LOGIC && take_bels_.count(in_bel.getName()) == 0; - bool skip_out_bel = out_bel.getCategory() == Device::BELCategory::LOGIC && take_bels_.count(out_bel.getName()) == 0; + bool skip_in_bel = in_bel.getCategory() == LOGIC && take_bels_.count(in_bel.getName()) == 0; + bool skip_out_bel = out_bel.getCategory() == LOGIC && take_bels_.count(out_bel.getName()) == 0; if (skip_in_bel || skip_out_bel) continue; @@ -521,25 +529,54 @@ struct ArchReader { // adjust pb_type name in the interconnect accordingly. auto get_lut_element_index = [&](const std::string& bel_name) { auto lut_element = get_lut_element_for_bel(site_type, bel_name); - if (lut_element == nullptr) { + if (lut_element == nullptr) return -1; - } const auto& lut_elements = arch_->lut_elements.at(site_type); auto it = std::find(lut_elements.begin(), lut_elements.end(), *lut_element); VTR_ASSERT(it != lut_elements.end()); + return (int)std::distance(lut_elements.begin(), it); }; - int index = -1; + // TODO: This avoids having LUTs that can be used in other ways than LUTs, e.g. as DRAMs. + // Once support is added for macro expansion, all the connections currently marked as + // invalid will be re-enabled. + auto is_lut_connection_valid = [&](const std::string& bel_name, const std::string& pin_name) { + auto lut_element = get_lut_element_for_bel(site_type, bel_name); + if (lut_element == nullptr) + return false; + + bool pin_found = false; + for (auto lut_bel : lut_element->lut_bels) { + for (auto lut_bel_pin : lut_bel.input_pins) + pin_found |= lut_bel_pin == pin_name; + + pin_found |= lut_bel.output_pin == pin_name; + } + + if (!pin_found) + return false; + + return true; + }; - index = get_lut_element_index(out_bel_name); + int index = get_lut_element_index(out_bel_name); + bool valid_lut = is_lut_connection_valid(out_bel_name, out_bel_pin_name); if (index >= 0) { out_bel_name = "LUT" + std::to_string(index); + + if (!valid_lut) + continue; } + index = get_lut_element_index(in_bel_name); + valid_lut = is_lut_connection_valid(in_bel_name, in_bel_pin_name); if (index >= 0) { in_bel_name = "LUT" + std::to_string(index); + + if (!valid_lut) + continue; } std::string ostr = out_bel_name + "." + out_bel_pin_name; @@ -560,7 +597,7 @@ struct ArchReader { std::string extra_ostr = in_bel_name + "." + in_bel_pin_name + in_suffix_; std::string extra_ic_name = ic_name + "_extra"; - std::vector extra_ostrs{extra_ostr}; + std::set extra_ostrs{extra_ostr}; t_ic_data extra_ic_data = { extra_istr, // ic input extra_ostrs, // ic outputs @@ -580,7 +617,7 @@ struct ArchReader { } } - std::vector ostrs{ostr}; + std::set ostrs{ostr}; t_ic_data ic_data = { istr, ostrs, @@ -600,7 +637,8 @@ struct ArchReader { VTR_ASSERT(old_data.input == data.input); VTR_ASSERT(data.outputs.size() == 1); - res.first->second.outputs.push_back(data.outputs[0]); + for (auto out : data.outputs) + res.first->second.outputs.insert(out); res.first->second.requires_pack_pattern |= data.requires_pack_pattern; } } @@ -974,35 +1012,23 @@ struct ArchReader { // Get LUT elements for this site std::vector lut_elements; - if (arch_->lut_elements.count(name)) { + if (arch_->lut_elements.count(name)) lut_elements = arch_->lut_elements.at(name); - } // Count non-LUT BELs plus LUT elements - int block_count = 0; - int count = 0; - - for (auto bel : site.getBels()) { - if (bel.getCategory() != Device::BELCategory::LOGIC) { - continue; - } - if (is_lut(str(bel.getName()), name)) { - continue; - } - block_count++; - } - block_count += lut_elements.size(); + int block_count = get_bel_type_count(site, LOGIC, true) + get_bel_type_count(site, ROUTING, true) + lut_elements.size(); mode->num_pb_type_children = block_count; mode->pb_type_children = new t_pb_type[mode->num_pb_type_children]; // Add regular BELs + int count = 0; for (auto bel : bels) { auto category = bel.getCategory(); - if (bel.getCategory() == Device::BELCategory::SITE_PORT) + if (bel.getCategory() == SITE_PORT) continue; - bool is_logic = category == Device::BELCategory::LOGIC; + bool is_logic = category == LOGIC; if (take_bels_.count(bel.getName()) == 0 && is_logic) continue; @@ -1013,7 +1039,7 @@ struct ArchReader { auto bel_name = str(bel.getName()); std::pair key(name, bel_name); - auto mid_pb_type = new t_pb_type; + auto mid_pb_type = &mode->pb_type_children[count++]; std::string mid_pb_type_name = bel_name == name ? bel_name + bel_dedup_suffix_ : bel_name; mid_pb_type->name = vtr::strdup(mid_pb_type_name.c_str()); @@ -1029,7 +1055,7 @@ struct ArchReader { else if (is_logic) process_generic_block(mid_pb_type, bel, site); else { - VTR_ASSERT(category == Device::BELCategory::ROUTING); + VTR_ASSERT(category == ROUTING); process_routing_block(mid_pb_type); } } @@ -1362,6 +1388,7 @@ struct ArchReader { for (auto map : maps) { auto name = str(map.cell); bool is_compatible = map.site == site.getName(); + for (auto pin_map : map.pins) { if (is_compatible == false) break; @@ -1378,9 +1405,8 @@ struct ArchReader { auto model_port = get_model_port(arch_, name, cell_pin, false); - if (is_inout && model_port != nullptr) { + if (is_inout && model_port != nullptr) bel_pin = model_port->dir == IN_PORT ? bel_pin + in_suffix_ : bel_pin + out_suffix_; - } is_compatible &= block_port_exists(pb_type, bel_pin); } @@ -1394,6 +1420,8 @@ struct ArchReader { int num_modes = maps.size(); + VTR_ASSERT(num_modes > 0); + pb_type->num_modes = num_modes; pb_type->modes = new t_mode[num_modes]; @@ -1403,7 +1431,7 @@ struct ArchReader { continue; int idx = count++; - auto mode = &pb_type->modes[idx]; + t_mode* mode = &pb_type->modes[idx]; auto name = str(map.cell); mode->name = vtr::strdup(name.c_str()); mode->parent_pb_type = pb_type; @@ -1645,7 +1673,7 @@ struct ArchReader { auto input = ic_data.input; auto outputs = ic_data.outputs; - auto merge_string = [](std::string& ss, std::string& s) { + auto merge_string = [](std::string ss, std::string s) { return ss.empty() ? s : ss + " " + s; }; @@ -1740,6 +1768,7 @@ struct ArchReader { // Assign mode and pb_type t_mode* parent_mode = ic->parent_mode; t_pb_type* pb_type = nullptr; + for (int ipb = 0; ipb < parent_mode->num_pb_type_children; ipb++) if (std::string(parent_mode->pb_type_children[ipb].name) == bel) pb_type = &parent_mode->pb_type_children[ipb]; @@ -1749,7 +1778,7 @@ struct ArchReader { auto bel_reader = get_bel_reader(site, remove_bel_suffix(bel)); // Passing through routing mux. Check at the muxes input pins interconnects - if (bel_reader.getCategory() == Device::BELCategory::ROUTING) { + if (bel_reader.getCategory() == ROUTING) { for (auto bel_pin : bel_reader.getPins()) { auto pin_reader = site_pins[bel_pin]; auto pin_name = str(pin_reader.getName()); @@ -1766,9 +1795,8 @@ struct ArchReader { std::string ic_to_find = bel + "." + pin_name; bool found = false; - for (auto out : vtr::split(is_backward ? other_ic->output_string : other_ic->input_string, " ")) { + for (auto out : vtr::split(is_backward ? other_ic->output_string : other_ic->input_string, " ")) found |= out == ic_to_find; - } if (found) { // An output interconnect to propagate was found, continue searching @@ -1780,7 +1808,7 @@ struct ArchReader { } } } else { - VTR_ASSERT(bel_reader.getCategory() == Device::BELCategory::LOGIC); + VTR_ASSERT(bel_reader.getCategory() == LOGIC); for (int imode = 0; imode < pb_type->num_modes; imode++) { t_mode* mode = &pb_type->modes[imode]; From 821bff939729d4e6f536598dc7c2d94c44b68d71 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Fri, 21 Jan 2022 11:25:48 +0100 Subject: [PATCH 20/22] interchange: address review comments Signed-off-by: Alessandro Comodi --- libs/libarchfpga/src/arch_util.cpp | 2 + libs/libarchfpga/src/arch_util.h | 22 ++++++- libs/libarchfpga/src/physical_types.h | 6 ++ .../src/read_fpga_interchange_arch.cpp | 65 ++++++++++++------- libs/libvtrutil/src/vtr_util.cpp | 30 --------- libs/libvtrutil/src/vtr_util.h | 2 - vpr/src/base/SetupVPR.cpp | 4 +- vpr/src/pack/pb_type_graph.cpp | 6 ++ 8 files changed, 77 insertions(+), 60 deletions(-) diff --git a/libs/libarchfpga/src/arch_util.cpp b/libs/libarchfpga/src/arch_util.cpp index 4932d9fb2dc..3924f19b1e7 100644 --- a/libs/libarchfpga/src/arch_util.cpp +++ b/libs/libarchfpga/src/arch_util.cpp @@ -569,6 +569,7 @@ t_physical_tile_type get_empty_physical_type(std::string name) { type.switchblock_switch_overrides = vtr::Matrix({{size_t(type.width), size_t(type.height)}}, DEFAULT_SWITCH); type.is_input_type = false; type.is_output_type = false; + type.is_empty = true; return type; } @@ -577,6 +578,7 @@ t_logical_block_type get_empty_logical_type(std::string name) { t_logical_block_type type; type.name = vtr::strdup(name.c_str()); type.pb_type = nullptr; + type.is_empty = true; return type; } diff --git a/libs/libarchfpga/src/arch_util.h b/libs/libarchfpga/src/arch_util.h index d5519f5684c..fc2e8850d2d 100644 --- a/libs/libarchfpga/src/arch_util.h +++ b/libs/libarchfpga/src/arch_util.h @@ -15,6 +15,8 @@ void set_arch_file_name(const char* arch); */ const char* get_arch_file_name(); +const std::string EMPTY_BLOCK_NAME("EMPTY"); + class InstPort { public: static constexpr int UNSPECIFIED = -1; @@ -60,8 +62,15 @@ void free_type_descriptors(std::vector& type_descriptors); t_port* findPortByName(const char* name, t_pb_type* pb_type, int* high_index, int* low_index); -t_physical_tile_type get_empty_physical_type(std::string name); -t_logical_block_type get_empty_logical_type(std::string name); +/** @brief Returns and empty physical tile type, assigned with the given name argument. + * The default empty string is assigned if no name is provided + */ +t_physical_tile_type get_empty_physical_type(std::string name = EMPTY_BLOCK_NAME); + +/** @brief Returns and empty logical block type, assigned with the given name argument. + * The default empty string is assigned if no name is provided + */ +t_logical_block_type get_empty_logical_type(std::string name = EMPTY_BLOCK_NAME); std::unordered_set get_equivalent_sites_set(t_physical_tile_type_ptr type); @@ -100,6 +109,15 @@ bool pb_type_contains_blif_model(const t_pb_type* pb_type, const std::string& bl const t_pin_to_pin_annotation* find_sequential_annotation(const t_pb_type* pb_type, const t_model_ports* port, enum e_pin_to_pin_delay_annotations annot_type); const t_pin_to_pin_annotation* find_combinational_annotation(const t_pb_type* pb_type, std::string in_port, std::string out_port); +/** + * @brief Updates the physical and logical types based on the equivalence between one and the other. + * + * This function is required to check and synchronize all the information to be able to use the logical block + * equivalence, and link all the logical block pins to the physical tile ones, given that multiple logical blocks (i.e. pb_types) + * can be placed at the same physical location if this is allowed in the architecture description. + * + * See https://docs.verilogtorouting.org/en/latest/tutorials/arch/equivalent_sites/ for reference + */ void link_physical_logical_types(std::vector& PhysicalTileTypes, std::vector& LogicalBlockTypes); diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 4c6442efff7..4d923e34e9c 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -653,6 +653,9 @@ struct t_physical_tile_type { // Does this t_physical_tile_type contain an outpad? bool is_output_type = false; + + // Is this t_physical_tile_type an empty type? + bool is_empty = false; }; /* Holds the capacity range of a certain sub_tile block within the parent physical tile type. @@ -828,6 +831,9 @@ struct t_logical_block_type { std::vector equivalent_tiles; ///>List of physical tiles at which one could ///>place this type of netlist block. + + // Is this t_logical_block_type empty? + bool is_empty = false; }; /************************************************************************************************* diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index b32455c6af6..d0149cd1598 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -1065,7 +1065,8 @@ struct ArchReader { const auto& lut_element = lut_elements[i]; auto mid_pb_type = &mode->pb_type_children[count++]; - mid_pb_type->name = vtr::stringf("LUT%d", i); + std::string lut_name = "LUT" + std::to_string(i); + mid_pb_type->name = vtr::strdup(lut_name.c_str()); mid_pb_type->num_pb = 1; mid_pb_type->parent_mode = mode; mid_pb_type->blif_model = nullptr; @@ -1122,6 +1123,8 @@ struct ArchReader { mode->num_interconnect = lut_bel.input_pins.size() + 1; mode->interconnect = new t_interconnect[mode->num_interconnect]; + std::string istr, ostr, name; + // Inputs for (size_t j = 0; j < lut_bel.input_pins.size(); ++j) { auto* ic = &mode->interconnect[j]; @@ -1130,11 +1133,13 @@ struct ArchReader { ic->parent_mode = mode; ic->parent_mode_index = mode->index; - ic->input_string = vtr::stringf("%s.%s", parent->name, lut_bel.input_pins[j].c_str()); - ic->output_string = vtr::stringf("%s.in[%d]", pb_type->name, j); - ic->name = vtr::stringf("%s_to_%s", - ic->input_string, - ic->output_string); + istr = std::string(parent->name) + "." + lut_bel.input_pins[j]; + ostr = std::string(pb_type->name) + ".in[" + std::to_string(j) + "]"; + name = istr + "_to_" + ostr; + + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + ic->name = vtr::strdup(name.c_str()); } // Output @@ -1143,11 +1148,13 @@ struct ArchReader { ic->parent_mode = mode; ic->parent_mode_index = mode->index; - ic->input_string = vtr::stringf("%s.out", pb_type->name); - ic->output_string = vtr::stringf("%s.%s", parent->name, lut_bel.output_pin.c_str()); - ic->name = vtr::stringf("%s_to_%s", - ic->input_string, - ic->output_string); + istr = std::string(pb_type->name) + ".out"; + ostr = std::string(parent->name) + "." + lut_bel.output_pin; + name = istr + "_to_" + ostr; + + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + ic->name = vtr::strdup(name.c_str()); } } @@ -1181,9 +1188,15 @@ struct ArchReader { mode->interconnect = new t_interconnect[mode->num_interconnect]; t_interconnect* ic = &mode->interconnect[0]; - ic->input_string = vtr::stringf("%s.in", pb_type->name); - ic->output_string = vtr::stringf("%s.out", pb_type->name); - ic->name = vtr::strdup("passthrough"); + std::string istr, ostr, name; + + istr = std::string(pb_type->name) + ".in"; + ostr = std::string(pb_type->name) + ".out"; + name = "passthrough"; + + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + ic->name = vtr::strdup(name.c_str()); ic->type = COMPLETE_INTERC; ic->parent_mode = mode; @@ -1233,11 +1246,13 @@ struct ArchReader { ic->parent_mode = mode; ic->parent_mode_index = mode->index; - ic->input_string = vtr::stringf("%s.in", pb_type->name); - ic->output_string = vtr::stringf("%s.in", lut->name); - ic->name = vtr::stringf("%s_to_%s", - ic->input_string, - ic->output_string); + istr = std::string(pb_type->name) + ".in"; + ostr = std::string(lut->name) + ".in"; + name = istr + "_to_" + ostr; + + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + ic->name = vtr::strdup(name.c_str()); // Output ic = &mode->interconnect[1]; @@ -1245,11 +1260,13 @@ struct ArchReader { ic->parent_mode = mode; ic->parent_mode_index = mode->index; - ic->input_string = vtr::stringf("%s.out", lut->name); - ic->output_string = vtr::stringf("%s.out", pb_type->name); - ic->name = vtr::stringf("%s_to_%s", - ic->input_string, - ic->output_string); + istr = std::string(lut->name) + ".out"; + ostr = std::string(pb_type->name) + ".out"; + name = istr + "_to_" + ostr; + + ic->input_string = vtr::strdup(istr.c_str()); + ic->output_string = vtr::strdup(ostr.c_str()); + ic->name = vtr::strdup(name.c_str()); } /** @brief Generates the leaf pb types for the PAD type */ diff --git a/libs/libvtrutil/src/vtr_util.cpp b/libs/libvtrutil/src/vtr_util.cpp index c897d296f0f..45ee3035883 100644 --- a/libs/libvtrutil/src/vtr_util.cpp +++ b/libs/libvtrutil/src/vtr_util.cpp @@ -501,34 +501,4 @@ int get_pid() { #endif } -char* stringf(const char* format, ...) { - // Initial buffer - const int initial_size = 80 + 1; - char* str = (char*)vtr::malloc(initial_size); - - // Init and copy args list - va_list va1, va2; - va_start(va1, format); - va_copy(va2, va1); - - // First attempt - int len = vsnprintf(str, initial_size, format, va1); - VTR_ASSERT(len >= 0); - - // The buffer was too small - if (len >= initial_size) { - str = (char*)vtr::realloc((void*)str, len + 1); - VTR_ASSERT(str != nullptr); - len = vsnprintf(str, len + 1, format, va2); - VTR_ASSERT(len >= 0); - VTR_ASSERT(len <= len); - } - - // Cleanup - va_end(va1); - va_end(va2); - - return str; -} - } // namespace vtr diff --git a/libs/libvtrutil/src/vtr_util.h b/libs/libvtrutil/src/vtr_util.h index 3d482eb96bb..08562d3d092 100644 --- a/libs/libvtrutil/src/vtr_util.h +++ b/libs/libvtrutil/src/vtr_util.h @@ -118,8 +118,6 @@ void uniquify(Container container) { int get_pid(); -char* stringf(const char* format, ...); - } // namespace vtr #endif diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index 8d3d415340d..5c9d0f3807f 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -139,7 +139,7 @@ void SetupVPR(const t_options* Options, int num_inputs = 0; int num_outputs = 0; for (auto& type : device_ctx.physical_tile_types) { - if (type.index == 0) { + if (type.is_empty) { VTR_ASSERT(device_ctx.EMPTY_PHYSICAL_TILE_TYPE == nullptr); VTR_ASSERT(type.num_pins == 0); device_ctx.EMPTY_PHYSICAL_TILE_TYPE = &type; @@ -157,7 +157,7 @@ void SetupVPR(const t_options* Options, device_ctx.EMPTY_LOGICAL_BLOCK_TYPE = nullptr; int max_equivalent_tiles = 0; for (const auto& type : device_ctx.logical_block_types) { - if (type.index == 0) { + if (type.is_empty) { VTR_ASSERT(device_ctx.EMPTY_LOGICAL_BLOCK_TYPE == nullptr); VTR_ASSERT(type.pb_type == nullptr); device_ctx.EMPTY_LOGICAL_BLOCK_TYPE = &type; diff --git a/vpr/src/pack/pb_type_graph.cpp b/vpr/src/pack/pb_type_graph.cpp index 40552ba778c..7a536cb0e23 100644 --- a/vpr/src/pack/pb_type_graph.cpp +++ b/vpr/src/pack/pb_type_graph.cpp @@ -1099,6 +1099,12 @@ static bool realloc_and_load_pb_graph_pin_ptrs_at_var(const int line_num, } (*token_index)++; + bool is_string = !checkTokenType(tokens[*token_index], TOKEN_STRING); + bool is_int = !checkTokenType(tokens[*token_index], TOKEN_INT); + + if (!is_string && !is_int) + return false; + /* parse ports and port pins of pb */ port_name = tokens[*token_index].data; (*token_index)++; From 162ca071680bcd720af4421b29cffc5688644383 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 24 Jan 2022 11:26:54 +0100 Subject: [PATCH 21/22] libs: arch: add is_empty function to phys/log types Signed-off-by: Alessandro Comodi --- libs/libarchfpga/src/arch_util.cpp | 10 ++++------ libs/libarchfpga/src/arch_util.h | 6 +++--- libs/libarchfpga/src/physical_types.cpp | 15 +++++++++++++++ libs/libarchfpga/src/physical_types.h | 4 ++-- .../src/read_fpga_interchange_arch.cpp | 4 ++-- libs/libarchfpga/src/read_xml_arch_file.cpp | 4 ++-- vpr/src/base/SetupVPR.cpp | 4 ++-- 7 files changed, 30 insertions(+), 17 deletions(-) diff --git a/libs/libarchfpga/src/arch_util.cpp b/libs/libarchfpga/src/arch_util.cpp index 3924f19b1e7..de804a63b66 100644 --- a/libs/libarchfpga/src/arch_util.cpp +++ b/libs/libarchfpga/src/arch_util.cpp @@ -555,9 +555,9 @@ t_port* findPortByName(const char* name, t_pb_type* pb_type, int* high_index, in return port; } -t_physical_tile_type get_empty_physical_type(std::string name) { +t_physical_tile_type get_empty_physical_type(const char* name) { t_physical_tile_type type; - type.name = vtr::strdup(name.c_str()); + type.name = vtr::strdup(name); type.num_pins = 0; type.width = 1; type.height = 1; @@ -569,16 +569,14 @@ t_physical_tile_type get_empty_physical_type(std::string name) { type.switchblock_switch_overrides = vtr::Matrix({{size_t(type.width), size_t(type.height)}}, DEFAULT_SWITCH); type.is_input_type = false; type.is_output_type = false; - type.is_empty = true; return type; } -t_logical_block_type get_empty_logical_type(std::string name) { +t_logical_block_type get_empty_logical_type(const char* name) { t_logical_block_type type; - type.name = vtr::strdup(name.c_str()); + type.name = vtr::strdup(name); type.pb_type = nullptr; - type.is_empty = true; return type; } diff --git a/libs/libarchfpga/src/arch_util.h b/libs/libarchfpga/src/arch_util.h index fc2e8850d2d..7d882450a5a 100644 --- a/libs/libarchfpga/src/arch_util.h +++ b/libs/libarchfpga/src/arch_util.h @@ -15,7 +15,7 @@ void set_arch_file_name(const char* arch); */ const char* get_arch_file_name(); -const std::string EMPTY_BLOCK_NAME("EMPTY"); +constexpr const char* EMPTY_BLOCK_NAME = "EMPTY"; class InstPort { public: @@ -65,12 +65,12 @@ t_port* findPortByName(const char* name, t_pb_type* pb_type, int* high_index, in /** @brief Returns and empty physical tile type, assigned with the given name argument. * The default empty string is assigned if no name is provided */ -t_physical_tile_type get_empty_physical_type(std::string name = EMPTY_BLOCK_NAME); +t_physical_tile_type get_empty_physical_type(const char* name = EMPTY_BLOCK_NAME); /** @brief Returns and empty logical block type, assigned with the given name argument. * The default empty string is assigned if no name is provided */ -t_logical_block_type get_empty_logical_type(std::string name = EMPTY_BLOCK_NAME); +t_logical_block_type get_empty_logical_type(const char* name = EMPTY_BLOCK_NAME); std::unordered_set get_equivalent_sites_set(t_physical_tile_type_ptr type); diff --git a/libs/libarchfpga/src/physical_types.cpp b/libs/libarchfpga/src/physical_types.cpp index fb65e7a4983..dfa110f393f 100644 --- a/libs/libarchfpga/src/physical_types.cpp +++ b/libs/libarchfpga/src/physical_types.cpp @@ -1,6 +1,9 @@ #include "physical_types.h" #include "vtr_math.h" #include "vtr_util.h" +#include "vtr_log.h" + +#include "arch_util.h" static bool switch_type_is_buffered(SwitchType type); static bool switch_type_is_configurable(SwitchType type); @@ -129,6 +132,18 @@ int t_physical_tile_type::get_sub_tile_loc_from_pin(int pin_num) const { return OPEN; } +bool t_physical_tile_type::is_empty() const { + return std::string(name) == std::string(EMPTY_BLOCK_NAME); +} + +/* + * t_logical_block_type + */ + +bool t_logical_block_type::is_empty() const { + return std::string(name) == std::string(EMPTY_BLOCK_NAME); +} + /** * t_pb_graph_node */ diff --git a/libs/libarchfpga/src/physical_types.h b/libs/libarchfpga/src/physical_types.h index 4d923e34e9c..1c84821fbca 100644 --- a/libs/libarchfpga/src/physical_types.h +++ b/libs/libarchfpga/src/physical_types.h @@ -655,7 +655,7 @@ struct t_physical_tile_type { bool is_output_type = false; // Is this t_physical_tile_type an empty type? - bool is_empty = false; + bool is_empty() const; }; /* Holds the capacity range of a certain sub_tile block within the parent physical tile type. @@ -833,7 +833,7 @@ struct t_logical_block_type { ///>place this type of netlist block. // Is this t_logical_block_type empty? - bool is_empty = false; + bool is_empty() const; }; /************************************************************************************************* diff --git a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp index d0149cd1598..977ed0bc254 100644 --- a/libs/libarchfpga/src/read_fpga_interchange_arch.cpp +++ b/libs/libarchfpga/src/read_fpga_interchange_arch.cpp @@ -969,7 +969,7 @@ struct ArchReader { int index = 0; // TODO: Make this dynamic depending on data from the interchange - auto EMPTY = get_empty_logical_type(std::string("NULL")); + auto EMPTY = get_empty_logical_type(); EMPTY.index = index; ltypes_.push_back(EMPTY); @@ -1857,7 +1857,7 @@ struct ArchReader { // Physical Tiles void process_tiles() { - auto EMPTY = get_empty_physical_type(std::string("NULL")); + auto EMPTY = get_empty_physical_type(); int index = 0; EMPTY.index = index; ptypes_.push_back(EMPTY); diff --git a/libs/libarchfpga/src/read_xml_arch_file.cpp b/libs/libarchfpga/src/read_xml_arch_file.cpp index ba6b82f28a9..b59aa86b93e 100644 --- a/libs/libarchfpga/src/read_xml_arch_file.cpp +++ b/libs/libarchfpga/src/read_xml_arch_file.cpp @@ -2780,7 +2780,7 @@ static void ProcessTiles(pugi::xml_node Node, /* Alloc the type list. Need one additional t_type_desctiptors: * 1: empty psuedo-type */ - t_physical_tile_type EMPTY_PHYSICAL_TILE_TYPE = get_empty_physical_type(std::string("EMPTY")); + t_physical_tile_type EMPTY_PHYSICAL_TILE_TYPE = get_empty_physical_type(); EMPTY_PHYSICAL_TILE_TYPE.index = 0; PhysicalTileTypes.push_back(EMPTY_PHYSICAL_TILE_TYPE); @@ -3453,7 +3453,7 @@ static void ProcessComplexBlocks(vtr::string_internment* strings, pugi::xml_node /* Alloc the type list. Need one additional t_type_desctiptors: * 1: empty psuedo-type */ - t_logical_block_type EMPTY_LOGICAL_BLOCK_TYPE = get_empty_logical_type(std::string("EMPTY")); + t_logical_block_type EMPTY_LOGICAL_BLOCK_TYPE = get_empty_logical_type(); EMPTY_LOGICAL_BLOCK_TYPE.index = 0; LogicalBlockTypes.push_back(EMPTY_LOGICAL_BLOCK_TYPE); diff --git a/vpr/src/base/SetupVPR.cpp b/vpr/src/base/SetupVPR.cpp index 5c9d0f3807f..2d192e831b0 100644 --- a/vpr/src/base/SetupVPR.cpp +++ b/vpr/src/base/SetupVPR.cpp @@ -139,7 +139,7 @@ void SetupVPR(const t_options* Options, int num_inputs = 0; int num_outputs = 0; for (auto& type : device_ctx.physical_tile_types) { - if (type.is_empty) { + if (type.is_empty()) { VTR_ASSERT(device_ctx.EMPTY_PHYSICAL_TILE_TYPE == nullptr); VTR_ASSERT(type.num_pins == 0); device_ctx.EMPTY_PHYSICAL_TILE_TYPE = &type; @@ -157,7 +157,7 @@ void SetupVPR(const t_options* Options, device_ctx.EMPTY_LOGICAL_BLOCK_TYPE = nullptr; int max_equivalent_tiles = 0; for (const auto& type : device_ctx.logical_block_types) { - if (type.is_empty) { + if (type.is_empty()) { VTR_ASSERT(device_ctx.EMPTY_LOGICAL_BLOCK_TYPE == nullptr); VTR_ASSERT(type.pb_type == nullptr); device_ctx.EMPTY_LOGICAL_BLOCK_TYPE = &type; From 450540c3f24ebd66dee7a96779fb7a9d147cca64 Mon Sep 17 00:00:00 2001 From: Alessandro Comodi Date: Mon, 24 Jan 2022 13:28:38 +0100 Subject: [PATCH 22/22] vpr: test: fix interchange test Signed-off-by: Alessandro Comodi --- vpr/test/test_interchange_device.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vpr/test/test_interchange_device.cpp b/vpr/test/test_interchange_device.cpp index 22a04eaca1e..caf5295a1d9 100644 --- a/vpr/test/test_interchange_device.cpp +++ b/vpr/test/test_interchange_device.cpp @@ -110,7 +110,7 @@ TEST_CASE("read_interchange_tiles", "[vpr]") { FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); - std::unordered_set ptypes = {"NULL", "IOB", "PWR", "CLB"}; + std::unordered_set ptypes = {"EMPTY", "IOB", "PWR", "CLB"}; // Check that there are exactly the expected models for (auto ptype : physical_tile_types) { @@ -134,7 +134,7 @@ TEST_CASE("read_interchange_pb_types", "[vpr]") { FPGAInterchangeReadArch(kArchFile, /*timing_enabled=*/true, &arch, physical_tile_types, logical_block_types); - std::unordered_set ltypes = {"NULL", "IOPAD", "SLICE", "POWER"}; + std::unordered_set ltypes = {"EMPTY", "IOPAD", "SLICE", "POWER"}; std::unordered_map slice_ports = { {"L0_0", PORTS::IN_PORT}, @@ -162,7 +162,7 @@ TEST_CASE("read_interchange_pb_types", "[vpr]") { ltypes.erase(name); if (ltype.pb_type == nullptr) { - REQUIRE(name == std::string("NULL")); + REQUIRE(name == std::string("EMPTY")); continue; }