diff --git a/utils/vqm2blif/src/base/hard_block_recog.cpp b/utils/vqm2blif/src/base/hard_block_recog.cpp new file mode 100644 index 00000000000..518795fe3fc --- /dev/null +++ b/utils/vqm2blif/src/base/hard_block_recog.cpp @@ -0,0 +1,1939 @@ +/* +* The purpose of this file is to support netlists that +* contain new types of hard blocks. Specifically, user defined +* hard blocks are identified within the netlist and then instantiated +* so that they are later written to the generated .blif file. +* +* For example, consider a user design that contains a new hard block +* called "router". This hard block is embedded within the FPGA architecture +* but the Quartus synthesis tool has no reference to what a "router" block is. +* Since the tool has no information to the hard block, the generated netlist +* does not properly model the hard blocks. Instead of declaring a single hard +* block in the netlist, it was found that the generated .vqm netlist +* from Quartus inserted a LUT for every input port of the hard block +* and then a LUT & DFF for every output port of the hard block. +* An example is shown below: +* +* Suppose we had the following module in the design (representing a hard +* block): +* +* router router_1 ( +* .input_one (pin_1), +* .input_two (pin_2), +* .output_one (pin_3), +* .output_two (pin_4), +* ); +* +* In the generated netlist from Quartus, the model will be represented as: +* +* router_1 +* -------------------------------------------------------------------- +* | LUT | +* | -------------- | +* pin_1 ---> input_one | | +* | | | | +* | | | | +* | -------------- | +* | | +* | | +* | | +* | LUT | +* | -------------- | +* pin_2 ---> input_two | | +* | | | | +* | | | | +* | -------------- | +* | | +* | | +* | | +* | LUT DFF | +* | ----------- -------------- | +* | | output_one ---------> ------> pin_3 +* | | | | | | +* | | | | | | +* | ----------- -------------- | +* | | +* | | +* | | +* | LUT DFF | +* | ----------- -------------- | +* | | output_two ---------> ------> pin_4 +* | | | | | | +* | | | | | | +* | ----------- -------------- | +* -------------------------------------------------------------------- +* +* Looking at the figure above, the netlist is incorrect. Instead of +* of a single "black box" model, LUTS and DFF have been +* incorrectly added to represent all the "ports" of the hard block. +* +* The functions in this file process the .vqm netlist generated by +* Quartus synthesis to fix the issues mentioned above. +* While processing the netlist, if a LUT or DFF +* is found, then it is "checked" too see whether it represents a +* a hard block "port", if it does then the corresponding hard block +* the "port" belongs to is identified and +* added to the netlist (newly instantiated). The corresponding luts and +* flip flops are then removed from the netlist and their netlist +* connections are then appropriately moved to the newly instantiated +* hard block. +* +* For example, if we take the netlist with the "router" block +* above and we process it using the functions in this file, the +* router block in the resulting netlist will look as follows: +* +* router_1 +* -------------------------------------------------------------------- +* | | +* | | +* pin_1 ---> input_one | +* | | +* | | +* | | +* | | +* | | +* | | +* | | +* | | +* pin_2 ---> input_two | +* | | +* | | +* | | +* | | +* | | +* | | +* | | +* | | +* | output_one ------> pin_3 +* | | +* | | +* | | +* | | +* | | +* | | +* | | +* | | +* | output_two ------> pin_4 +* | | +* | | +* | | +* -------------------------------------------------------------------- +* +* Now the hard block is correctly modelled within the netlist. +*/ + + +#include "hard_block_recog.h" + +//============================================================================================ +// INTERNAL FUNCTION DECLARATIONS +//============================================================================================ + +static void initialize_hard_block_models(t_arch* main_arch, std::vector* hard_block_type_names, t_hard_block_recog* storage_of_hard_block_info); + +static void process_module_nodes_and_create_hard_blocks(t_module* main_module, std::vector* hard_block_type_name_list, t_hard_block_recog* module_hard_block_node_refs_and_info); + +static bool create_and_initialize_all_hard_block_ports(t_model* hard_block_arch_model, t_hard_block_recog* storage_of_hard_block_info); + +static void create_hard_block_port_info_structure(t_hard_block_recog* storage_of_hard_block_info, std::string hard_block_type_name); + +static int extract_and_store_hard_block_model_ports(t_hard_block_recog* storage_of_hard_block_info, t_model_ports* curr_hard_block_model_port, std::string curr_hard_block_type_name,int port_index, std::string port_type); + +static t_array_ref* convert_hard_block_model_port_to_hard_block_node_port(t_model_ports* hard_block_model_port); + +static t_node_port_association* create_unconnected_node_port_association(char *port_name, int port_index, int wire_index); + +static void store_hard_block_port_info(t_hard_block_recog* storage_of_hard_block_port_info, std::string curr_hard_block_type_name,std::string curr_port_name, t_array_ref** curr_port_array, int* port_index); + +static void copy_array_ref(t_array_ref* array_ref_orig, t_array_ref* array_ref_copy); + +static t_array_ref* create_and_initialize_t_array_ref_struct(void); + +static int find_hard_block_instance(t_hard_block_recog* module_hard_block_node_refs_and_info, t_parsed_hard_block_port_info* curr_module_node_info); + +static void assign_net_to_hard_block_instance_port(t_node* curr_module_node, t_parsed_hard_block_port_info* curr_module_node_info, t_hard_block_recog* module_hard_block_node_refs_and_info, int curr_hard_block_instance_index); + +static t_node_port_association* get_lut_dffeas_port_connected_to_hard_block_instance_net(t_node* curr_module_node, DeviceInfo target_device_info); + +static int identify_port_index_within_hard_block_type_port_array(t_hard_block_port_info* curr_hard_block_type_port_info, t_parsed_hard_block_port_info* curr_module_node_info, t_node* curr_module_node); + +static void handle_net_assignment(t_node* curr_module_node, t_hard_block* curr_hard_block_instance, int port_to_assign_index, t_node_port_association* port_connected_to_hard_block_instance_net, t_parsed_hard_block_port_info* curr_module_node_info); + +static bool is_hard_block_port_legal(t_node* curr_module_node, DeviceInfo target_device_info); + +static int create_new_hard_block_instance(t_array_ref* module_node_list, t_hard_block_recog* module_hard_block_node_refs_and_info, t_parsed_hard_block_port_info* curr_module_node_info); + +static t_array_ref* create_unconnected_hard_block_instance_ports(t_hard_block_port_info* curr_hard_block_type_port_info); + +static t_node* create_new_hard_block_instance_node(t_array_ref* curr_hard_block_instance_ports, t_parsed_hard_block_port_info* curr_hard_block_instance_info); + +static int store_new_hard_block_instance_info(t_hard_block_recog* module_hard_block_node_refs_and_info, t_hard_block_port_info* curr_hard_block_type_port_info, t_node* new_hard_block_instance_node, t_parsed_hard_block_port_info* curr_module_node_info); + +static t_array_ref* create_t_array_ref_from_array(void** array_to_store, int array_size); + +static void delete_hard_block_port_info(std::unordered_map* hard_block_type_name_to_port_info_map); + +static t_parsed_hard_block_port_info extract_hard_block_port_info_from_module_node(t_node* curr_module_node, std::vector* hard_block_type_name_list); + +static std::string identify_hard_block_type(std::vector* hard_block_type_name_list, std::string curr_node_name_component); + +static void identify_hard_block_port_name_and_index (t_parsed_hard_block_port_info* curr_hard_block_port, std::string curr_node_name_component); + +static void split_node_name(std::string original_node_name, std::vector* node_name_components, std::string delimiter); + +static std::string construct_hard_block_name(std::vector*node_name_components, std::string delimiter); + +static void remove_luts_dffeas_nodes_representing_hard_block_ports(t_module* main_module, t_hard_block_recog* module_hard_block_node_refs_and_info); + +static void verify_hard_blocks(t_hard_block_recog* module_hard_block_node_refs_and_info); + +// utility functions + +bool sort_hard_blocks_by_valid_connections(t_hard_block, t_hard_block); + +//============================================================================================ +//============================================================================================ + +/** + * @details This function is the main controller and executes all the + * processing steps involved in indentifying new types of hard + * blocks within the design and adding them to the netlist. + * + * The following steps are performed: + * - The FPGA architecture is parsed to internally model + * each hard block in the design + * - The nodes within the netlist are individually processed + * to determine whether hard blocks are included within the + * design and then added to the netlist + * - The newly added hard blocks are verified to make sure they + * are legal + * - The luts and dffeas within the netlist that represented + * hard block ports are removed + * + * @param main_module This contains all the netlist information, such as luts, + * flip flops, and other types of blocks. All the nets within + * the netlist are also included. + * + * @param main_arch This contains all the information regarding the FPGA + * architecture that the design will be mapped to. + * + * @param list_hard_block_type_names A list of the hard block names that need + * to be properly added to the netlist. + * + * @param arch_file_name Name of the architecture file that contains the FPGA + * architecture information. + * + * @param vqm_file_name Name of the quartus generated .vqm netlist file. + */ +void add_hard_blocks_to_netlist(t_module* main_module, t_arch* main_arch, std::vector* list_hard_block_type_names, std::string arch_file_name, std::string vqm_file_name, std::string device) +{ + t_hard_block_recog module_hard_block_node_refs_and_info; + + // variables used for reporting statistics to the user + int number_of_hard_blocks_added = 0; + int number_of_luts_flip_flops_removed = 0; + + // based on the device supplied, store the corresponding parameters related to that device + module_hard_block_node_refs_and_info.target_device_info = (device_parameter_database.find(device))->second; + + /* + We catch any errors that occur during the initialization procedure. + Some errors that can occur are when the provided hard block names + from the user were not found in the architecture file or the hard + block models in the architecture file do not have any ports. + All the errors in this step are related to the architecture file, so + once we catch the error, we append the architecture file location and + throw another error to force program termination. + */ + try + { + + std::cout << "\t>> Generating models for custom hard blocks.\n"; + + // internally model the new types of hard blocks using the architecture + // file + initialize_hard_block_models(main_arch, list_hard_block_type_names, &module_hard_block_node_refs_and_info); + } + catch(const vtr::VtrError& error) + { + throw vtr::VtrError((std::string)error.what() + " The FPGA architecture is described in " + arch_file_name + "."); + } + + /* + We catch any errors that occur during the procedure of reading the + netlist and inserting custom hard blocks whereever necessary. + All the errors in this step are related to the provided .vqm netlist file, so once we catch the error, we append the netlist file location + and throw another error to force program termination. + */ + try + { + + std::cout << "\t>> Processing netlist to infer and add custom hard blocks.\n"; + + + // go through the netlist nodes and add the new types of hard blocks + // if they are needed + process_module_nodes_and_create_hard_blocks(main_module, list_hard_block_type_names, &module_hard_block_node_refs_and_info); + + std::cout << "\t>> Verifying newly created custom hard blocks.\n"; + + verify_hard_blocks(&module_hard_block_node_refs_and_info); + + number_of_hard_blocks_added = module_hard_block_node_refs_and_info.hard_block_instances.size(); + number_of_luts_flip_flops_removed = module_hard_block_node_refs_and_info.luts_dffeas_nodes_to_remove.size(); + + std::cout << "\t>> Inferred and added " << number_of_hard_blocks_added << " hard blocks to the netlist.\n"; + } + catch(const vtr::VtrError& error) + { + throw vtr::VtrError((std::string)error.what() + " The original netlist is described in " + vqm_file_name + "."); + } + + std::cout << "\t>> Cleaning up netlist after adding hard blocks.\n"; + + // at this point we have a list of luts/dffeas nodes found in the module that we need to remove + // so we remove them here + remove_luts_dffeas_nodes_representing_hard_block_ports(main_module, &module_hard_block_node_refs_and_info); + + std::cout << "\t>> Removed " << number_of_luts_flip_flops_removed << " luts and flip flops from the netlist.\n"; + + // need to delete all the dynamic memory used to store + // all the hard block port information + delete_hard_block_port_info(&(module_hard_block_node_refs_and_info.hard_block_type_name_to_port_info)); + + return; +} + +/** + * @details Given a list of hard block names, an FPGA architecture is + * parsed to find and store information related to the hard blocks + * within the list. The ports and their indexing is stored. + * + * @param main_arch This contains all the information regarding the FPGA + * architeture that the design will be mapped to. + * + * @param hard_block_type_names A list of the hard block names that need + * to be properly added to the netlist. + * + * @param storage_of_hard_block_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The port + * information of the hard blocks are stored + * in here. + */ +static void initialize_hard_block_models(t_arch* main_arch, std::vector* hard_block_type_names, t_hard_block_recog* storage_of_hard_block_info) +{ + t_model* hard_block_model = NULL; + std::vector::iterator hard_block_type_name_traverser; + bool single_hard_block_init_result = false; + + // iterate through each hard block name within the list + for (hard_block_type_name_traverser = hard_block_type_names->begin(); hard_block_type_name_traverser != hard_block_type_names->end(); hard_block_type_name_traverser++) + { + // get the corresponding model for each hard block name + hard_block_model = find_arch_model_by_name(*hard_block_type_name_traverser, main_arch->models); + + // a check to see if the model was found within the FPGA architecture + if (hard_block_model == NULL) + { + throw vtr::VtrError("The provided hard block model: '" + *hard_block_type_name_traverser + "' was not found within the corresponding FPGA architecture."); + } + else + { + // store the port information for the current hard block model + single_hard_block_init_result = create_and_initialize_all_hard_block_ports(hard_block_model, storage_of_hard_block_info); + + // a check to make sure the hard block has both input and output ports + if (!single_hard_block_init_result) + { + throw vtr::VtrError("Hard block model: '" + *hard_block_type_name_traverser + "' found in the architecture has no input/output ports."); + } + } + + } + + return; + +} + +/** + * @details Given a module (which is the netlist), iterate through each + * node to determine whether the node represents a hard block port. + * If the node does represent a port, then a new hard block node + * is created and added to the module and the corresponding port + * is connected to the same net as the node that represented that port. + * If the hard block was already created, then it is not created again. + * Finally the nodes that were identified as representing hard block + * ports are stored for reference. The nodes are stored so that they + * can be removed from the netlist and deleted, since they are replaced + * by the corresponding hard blocks themselves. + * + * @param main_module This contains all the netlist information, such as luts, + * flip flops, and other types of blocks. All the nets within + * the netlist are also included. + * + * @param hard_block_type_name_list A list of the hard block names that need + * to be properly added to the netlist. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The port + * information of the hard blocks are stored + * in here. + */ +static void process_module_nodes_and_create_hard_blocks(t_module* main_module, std::vector* hard_block_type_name_list, t_hard_block_recog* module_hard_block_node_refs_and_info) +{ + // represents a block in the netlist + // refer to 'vqm_dll.h' for more info on t_node + t_node* curr_module_node = NULL; + + // create a new t_array_ref (refer to 'vqm_dll.h') structure so that we can append new hard block instances we create to the original node array + t_array_ref* node_list_with_hard_blocks = create_t_array_ref_from_array((void**)(main_module->array_of_nodes), main_module->number_of_nodes); + + std::string curr_module_node_type = ""; + std::string curr_node_name = ""; + + int number_of_module_nodes = main_module->number_of_nodes; // before any hard blocks are added + + t_parsed_hard_block_port_info curr_module_node_info; + + int curr_hard_block_instance_index = 0; + + /* iterate through every node in the module and create a new node to represent each and every hard block we identify within the netlist.*/ + for (int i = 0; i < number_of_module_nodes; i++) + { + + curr_module_node = (t_node*)node_list_with_hard_blocks->pointer[i]; + curr_module_node_type.assign(curr_module_node->type); + + // hard block ports are only represented in nodes that are either a LUT or flip flop block + if ((curr_module_node_type.compare((module_hard_block_node_refs_and_info->target_device_info).lut_type_name) == 0) || (curr_module_node_type.compare((module_hard_block_node_refs_and_info->target_device_info).dff_type_name) == 0)) + { + + curr_module_node_info = extract_hard_block_port_info_from_module_node(curr_module_node, hard_block_type_name_list); + + // check to see that the current node represents a hard block port + // a hard block instance name would not have bee found if it wasn't a hard block port + if (!curr_module_node_info.hard_block_name.empty()) + { + + // if we are here, the current node is a LUT or DFF node that represents a hard block instance port // + + /* referring to the diagram at the top, if the node is a lut that represents an output port, its connected net is an internal connection, which is not a legal netlist connection, so we cannot process the node any further. Therefore we only process nodes that are LUTs representing input ports or DFFs. */ + if (is_hard_block_port_legal(curr_module_node, module_hard_block_node_refs_and_info->target_device_info)) + { + + // get the index to the current hard block instance we need to work with + curr_hard_block_instance_index = find_hard_block_instance(module_hard_block_node_refs_and_info, &curr_module_node_info); + + // check to see whether the hard block instance the current node belongs to exists (remember each node here represents a hard block port) + if (curr_hard_block_instance_index == HARD_BLOCK_INSTANCE_DOES_NOT_EXIST) + { + // we need to create a new hard block instance, since it does not exists for the port the current node represents + curr_hard_block_instance_index = create_new_hard_block_instance(node_list_with_hard_blocks, module_hard_block_node_refs_and_info, &curr_module_node_info); + } + + /* the remaining steps below assign the net currently connected to the node being processed (a flip-flop or a LUT) to the hard block instance port the current node represents. The correspponding hard block instance and the specific port were found previously.*/ + assign_net_to_hard_block_instance_port(curr_module_node, &curr_module_node_info, module_hard_block_node_refs_and_info, curr_hard_block_instance_index); + } + + /* in this if clause, the current node represents a hard block port, so we cannot have this node be written to the output netlist, so we need to add it to the nodes to delete list */ + module_hard_block_node_refs_and_info->luts_dffeas_nodes_to_remove.push_back(curr_module_node); + + } + } + } + + // update the node list for the current module + main_module->number_of_nodes = node_list_with_hard_blocks->array_size; + main_module->array_of_nodes = (t_node**)(node_list_with_hard_blocks->pointer); + + // we also need to delete the dynamic memory we created + vtr::free(node_list_with_hard_blocks); + + return; + +} + +/** + * @details This function creates an array of unconnected + * ports for a hard block. Additionally, a + * 't_hard_block_port_info' structure is created (found + * in 'hard_block_recog.h') to store the unconnected + * port array; so that the port information can be + * found given a hard block type. + * + * @param hard_block_arch_model This is a 't_model' structure (found in + * 'logic_types.h') and contains information + * about a hard block within the FPGA + * architecture. + * + * @param storage_of_hard_block_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The port + * information of the hard blocks are stored + * in here. + * + * @return A boolean value is returned indicating whether the hard block within + * the FPGA had any ports. + * + */ +static bool create_and_initialize_all_hard_block_ports(t_model* hard_block_arch_model, t_hard_block_recog* storage_of_hard_block_info) +{ + int hard_block_port_index = 0; + std::string hard_block_arch_model_name = hard_block_arch_model->name; + bool result = true; + + // get the hard block ports + t_model_ports* input_ports = hard_block_arch_model->inputs; + t_model_ports* output_ports = hard_block_arch_model->outputs; + + //initialize a hard block node port array + create_hard_block_port_info_structure(storage_of_hard_block_info,hard_block_arch_model_name); + + // handle input ports + hard_block_port_index += extract_and_store_hard_block_model_ports(storage_of_hard_block_info, input_ports, hard_block_arch_model_name,hard_block_port_index, INPUT_PORTS); + + // handle output ports + hard_block_port_index += extract_and_store_hard_block_model_ports(storage_of_hard_block_info, output_ports, hard_block_arch_model_name,hard_block_port_index, OUTPUT_PORTS); + + // check to see if the hard block has no ports + if (hard_block_port_index == HARD_BLOCK_WITH_NO_PORTS) + { + + result = false; + + } + + return result; +} + +/** + * @details Creates and initializes a 't_hard_block_port_info' + * structure. This structure is then stored within + * the 't_hard_block_recog' structure. + * + * @param storage_of_hard_block_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The port + * information of the hard blocks are stored + * in here. + * + * @param hard_block_type_name The name of a hard block in the architecture + * file. Will be used to index the structure + * created in here when storing it inside + * the 't_hard_block_recog' structure. + */ +static void create_hard_block_port_info_structure(t_hard_block_recog* storage_of_hard_block_info, std::string hard_block_type_name) +{ + t_hard_block_port_info curr_hard_block_port_storage; + + // initialize with default parameters + (curr_hard_block_port_storage.hard_block_ports).pointer = NULL; + (curr_hard_block_port_storage.hard_block_ports).allocated_size = 0; + (curr_hard_block_port_storage.hard_block_ports).array_size = 0; + + /* store the newly created structure. Since this structure will + preserve port information for a single hard block type within + the FPGA architecture, we will use the corresponding hard block + name as the index when storing this newly created structure. + */ + storage_of_hard_block_info->hard_block_type_name_to_port_info.insert({hard_block_type_name,curr_hard_block_port_storage}); + + return; + +} + +/** + * @details For a given hard block in the architecture, this function converts + * the ports of the hard block from 't_model_ports' to a 't_array_ref' + * type. Then the converted ports are stored. + * + * The port information for all new types of hard blocks + * can be found within the FPGA architecture. For each hard block, + * the ports info can be found in a structure called 't_model_ports' + * (found in 'logic_types.h'). The ports are arranged in a linked lsit + * structure. + * + * THis function goes through the 't_model_ports' and stores them + * within a 't_node_port_association' structure (found in 'vqm_dll.h'). + * And instead of a linked list, the ports are stored in an array + * within a 't_array_ref' structure (found in 'vqm_dll.h'). Finally, + * the port array is stored within the 't_hard_block_port_info' + * (found in 'hard_block_recog.h') that is associated to the specific + * hard block the ports belong to. + * + * We need to do this conversion because the hard blocks that will + * eventually added to the netlist will be of type 't_node' and they + * store ports in the form of a 't_node_port_association' array. + * Additionally, for ports that are bussed, initially, they were + * represented as a single 't_model_port' structure with the size + * of the port included, whereas they need to be seperated into + * individual ports (which is also done here). + * + * @param storage_of_hard_block_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The port + * information of the hard blocks are stored + * in here. + * + * @param curr_hard_block_model_port This is a 't_model_port' structure (found + * in 'logic_types.h') that represents a port + * for a hard block. + * + * @param curr_hard_block_type_name A string that represents the specific type + * of hard block the ports that are being + * converted too belong to. + * + * @param port_index A tracker variable that indexes each port within the + * port array. For example, if we had 3 ports (test, test_1 + * , test_2) and they were sized 1,3,5 respectively. Then + * after processing test, port index will be 0, then after + * processing test_1, port_index will be 1, and finally + * port_index will be 5. port index identifies the index at + * which each port starts. + * + * @param port_type A string that describes whether the ports that are + * being processed are input or output ports. + * + */ +static int extract_and_store_hard_block_model_ports(t_hard_block_recog* storage_of_hard_block_info, t_model_ports* curr_hard_block_model_port, std::string curr_hard_block_type_name,int port_index, std::string port_type) +{ + t_array_ref* equivalent_hard_block_node_port_array = NULL; + int starting_port_index = port_index; + + /* 't_model_ports' is setup as a linked list structure. + So iterate through the list.*/ + while (curr_hard_block_model_port != NULL) + { + // convert the current port from the 't_model_port' to 't_node_port_association structure. and store it inside an array. + equivalent_hard_block_node_port_array = convert_hard_block_model_port_to_hard_block_node_port(curr_hard_block_model_port); + + // take the converted port array from above and store it + store_hard_block_port_info(storage_of_hard_block_info, curr_hard_block_type_name, curr_hard_block_model_port->name, &equivalent_hard_block_node_port_array, &port_index); + + curr_hard_block_model_port = curr_hard_block_model_port->next; + + } + + // check to see whether the hard block has no input or output ports + if (starting_port_index == port_index) + { + VTR_LOG_WARN("Model '%s' found in the architecture file does not have %s ports\n", curr_hard_block_type_name.c_str(), port_type.c_str()); + } + + return port_index; +} + +/** + * @details Given a port for hard block in the form of a + * 't_model_port' structure, this function creates an + * equivalent port in the form of a 't_node_port_association' + * structure. The created ports are then stored within an + * array. + * + * @param hard_block_model_port This is a 't_model_port' structure (found + * in 'logic_types.h') that represents a port + * for a hard block. + * + */ +static t_array_ref* convert_hard_block_model_port_to_hard_block_node_port(t_model_ports* hard_block_model_port) +{ + t_node_port_association* curr_hard_block_node_port = NULL; + t_array_ref* port_array = NULL; + char* curr_hard_block_model_port_name = hard_block_model_port->name; + int port_size = hard_block_model_port->size; + + //create memory to store the port array + port_array = create_and_initialize_t_array_ref_struct(); + + // if a port is a bus, we need to create seperate port structures for each and every signal of the bus + for (int port_index = 0; port_index < port_size; port_index++) + { + // hard blocks will not have indexed wire assignments + // doesnt do anything different I think + curr_hard_block_node_port = create_unconnected_node_port_association(curr_hard_block_model_port_name, port_index, PORT_WIRE_NOT_INDEXED); + + // store the newly created specific port index within the entire port + // array + append_array_element((intptr_t)curr_hard_block_node_port, port_array); + } + + // handle the case where the port is not bussed + if (port_size == PORT_NOT_BUS) + { + curr_hard_block_node_port->port_index = PORT_WIRE_NOT_INDEXED; + } + + return port_array; + +} + +/** + * @details Creates a unconnected hard block port in the form of + * a 't_node_port_association' structure (found in + * 'vqm_dll.h'). + * + * @param port_name A string describing the name of the port to be + * created. + * + * @param port_index If the port is a bus, then this parameter specifies + * the index within the bus that the port will be + * created for. + * + * @param wire_index If the assigned net to this port is a bus, then this + * parameter specifies the index of the net that is + * associated with this port. + * + */ +static t_node_port_association* create_unconnected_node_port_association(char *port_name, int port_index, int wire_index) +{ + // allocate memory for the port + t_node_port_association* curr_hard_block_node_port = NULL; + curr_hard_block_node_port = (t_node_port_association*)vtr::malloc(sizeof(t_node_port_association)); + + // initialize all the port parameters + curr_hard_block_node_port->port_name = (char*)vtr::malloc(sizeof(char) * (strlen(port_name) + 1)); + strcpy(curr_hard_block_node_port->port_name, port_name); + + curr_hard_block_node_port->port_index = port_index; + curr_hard_block_node_port->associated_net = NULL; + curr_hard_block_node_port->wire_index = wire_index; + + return curr_hard_block_node_port; +} + +/** + * @details All the port information for a given hard block + * is stored inside an array that is located within + * a 't_hard_block_port_info' structure (found in + * 'hard_block_recog.h'). This function takes a single + * port and appends it to an array of other ports that + * belong to the hard block. This function also stores the + * index of where the previously appended port begins within + * the port array. + * + * @param storage_of_hard_block_port_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The port + * information of the hard blocks are stored + * in here. + * + * @param curr_hard_block_type_name A string that represents the name of the + * hard block type the port to store belongs + * to. + * + * @param curr_port_name A string that represents the name of the current + * port to store. + * + * @param curr_port_array A 't_array_def' structure (found in 'vqm_dll.h') + * that contains the current port to store. + * + * @param port_index A integer that defines the index within the port array + * that the current port is located at. + * + */ +static void store_hard_block_port_info(t_hard_block_recog* storage_of_hard_block_port_info, std::string curr_hard_block_type_name,std::string curr_port_name, t_array_ref** curr_port_array, int* port_index) +{ + + std::unordered_map::iterator curr_port_info = ((storage_of_hard_block_port_info->hard_block_type_name_to_port_info).find(curr_hard_block_type_name)); + + // appending the current port to the array of ports for the current hard block + copy_array_ref(*curr_port_array, &(curr_port_info->second).hard_block_ports); + + // insert the port name to the current index + (curr_port_info->second).port_name_to_port_start_index.insert(std::pair(curr_port_name, *port_index)); + + // update the index that the next port will begin at + *port_index += (*curr_port_array)->array_size; + + // store the current port size + (curr_port_info->second).port_name_to_port_size.insert(std::pair(curr_port_name, (*curr_port_array)->array_size)); + + /* we don't need to the current port anymore, since it is appended to the array of ports for the corresponding hard block. So delete the memory for it.*/ + vtr::free((*curr_port_array)->pointer); + vtr::free(*curr_port_array); + + *curr_port_array = NULL; + + return; +} + +/** + * @details Given two 't_array_ref' structures (found in 'vqm_dll.h'), + * this function copies the array contents from one structure and + * appends it the other structure. + * + * @param array_ref_orig The 't_array_ref' structure that will have its + * array contents copied. + * + * @param array_ref_copy The 't_array_ref' structure that will have copied + * contents added to its array. + */ +static void copy_array_ref(t_array_ref* array_ref_orig, t_array_ref* array_ref_copy) +{ + int array_size = array_ref_orig->array_size; + + // append the array elements from one 't_array_ref' structure to the other + for (int index = 0; index < array_size; index++) + { + append_array_element((intptr_t)array_ref_orig->pointer[index], array_ref_copy); + } + + return; +} + +/** + * @details Creates and initializes an empty 't_array_ref' structure. + * + */ +static t_array_ref* create_and_initialize_t_array_ref_struct(void) +{ + t_array_ref* empty_array = NULL; + + empty_array = (t_array_ref*)vtr::malloc(sizeof(t_array_ref)); + + // initially the 't_array_ref' structure has no contents. + // Making sure that the structure parameters reflect that.s + empty_array->pointer = NULL; + empty_array->array_size = 0; + empty_array->allocated_size = 0; + + return empty_array; + +} + +/** + * @details Every time a hard block instance is added to the module + * (netlist), a reference is stored inside a list of + * hard blocks within the design. This function returns + * an index to a hard block instance within the list of stored + * hard blocks in the module using the hard block instance name. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. This datastructure + * stores a list of references to the hard + * block instances within the design. + * This parameter stores a map data structure + * that associates hard block instance names + * to their index in the list. + * + * @param curr_module_node_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the hard + * block instance name and the type of hard + * block. + * + */ +static int find_hard_block_instance(t_hard_block_recog* module_hard_block_node_refs_and_info, t_parsed_hard_block_port_info* curr_module_node_info) +{ + int hard_block_instance_index = 0; + + std::unordered_map::iterator curr_hard_block_instance_index_ref; + + std::string curr_hard_block_instance_name = curr_module_node_info->hard_block_name; + + /* if we previously found a module node that represented a different port of the same hard block instance, we would have already created the node to represent the hard block instance. + + Search the current list of already created hard block instances for the hard block instance the current node is a part (remember that the current node represents a port for a hard block). + We are using hard block names to idetify each instance.*/ + curr_hard_block_instance_index_ref = module_hard_block_node_refs_and_info->hard_block_instance_name_to_index.find(curr_hard_block_instance_name); + + // now check the search result to see whether the hard block instance the current node is a part of was already created (if it exists in our internal list) + if (curr_hard_block_instance_index_ref == module_hard_block_node_refs_and_info->hard_block_instance_name_to_index.end()) + { + // if we are here, then the hard block instance the current node belongs to does not exist + // return unique identifier + hard_block_instance_index = HARD_BLOCK_INSTANCE_DOES_NOT_EXIST; + + } + else + { + // if we are here then the the hard block instance the current node is a port of already exists. + //so we store its index to identify it from all the other hard block instaces found in the netlist (all hard block instances are stored in a vector within 't_hard_block_recog') + hard_block_instance_index = curr_hard_block_instance_index_ref->second; + } + + + return hard_block_instance_index; + +} + +/** + * @details This function creates a new hard block instance + * node ('t_node' structure). The newly created node is + * added to the module (netlist). A reference to the node + * is then stored for future access. + * + * @param module_node_list This is a 't_array_def' structure that stores + * all the nodes within the module (netlist) of the + * design. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. This data structure + * stores a list of references to the hard + * block instances within the design. + * + * @param curr_module_node_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the hard + * block instance name and the type of hard + * block. + * + * @return An integer that represents an index within a list of hard block + * instances in the netlist (module) that represents the position + * of a hard block instance that was newly added within this function. + * + */ +static int create_new_hard_block_instance(t_array_ref* module_node_list, t_hard_block_recog* module_hard_block_node_refs_and_info, t_parsed_hard_block_port_info* curr_module_node_info) +{ + // index to to the new 't_hard_block' struct being created here that is found within the 'hard_block_instances' vector + int created_hard_block_instance_index = 0; + + // storage for the newly created hard block ports + t_array_ref* created_hard_block_instance_ports = NULL; + + // storage for the new node within the current module that represents the new hard block instance being created here + t_node* hard_block_instance_node = NULL; + + //get the port information for the new hard block instances type + t_hard_block_port_info* port_info_for_curr_hard_block_type = &((module_hard_block_node_refs_and_info->hard_block_type_name_to_port_info.find(curr_module_node_info->hard_block_type))->second); + + // create a new set of ports for the new hard block instance + created_hard_block_instance_ports = create_unconnected_hard_block_instance_ports(port_info_for_curr_hard_block_type); + + // create a new node for the new hard block instance + hard_block_instance_node = create_new_hard_block_instance_node(created_hard_block_instance_ports, curr_module_node_info); + + //store the new hard block instance information created above within a 't_hard_block' struct and get the index to where it is stored + created_hard_block_instance_index = store_new_hard_block_instance_info(module_hard_block_node_refs_and_info, port_info_for_curr_hard_block_type, hard_block_instance_node, curr_module_node_info); + + // append the newly created node that represents the new hard block instance to the node list for the module + append_array_element((intptr_t)hard_block_instance_node, module_node_list); + + // delete the array ref struct used to store the reference to the newly created hard block instance ports + vtr::free(created_hard_block_instance_ports); + + return created_hard_block_instance_index; +} + +/** + * @details This function takes a lut or dff node that represents a hard + * block port and takes the net connected to it and assigns it + * to the corresponding port in the hard block instance node in the + * module (netlist). + * + * The following steps are performed: + * - The lut or dff node is processed to find the port within those + * nodes that is connected to an external net. + * - The corresponding hard block instance port that is represented by + * the lut or dff is then found. + * - Finally, the net connected to the lut or dff port found previously + * is then assigned to the hard block instance port. + * + * @param curr_module_node A 't_node' structure (found in 'vqm_dll.h') + * that represents a lut or dff within the module + * (netlist). + * + * @param curr_module_node_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the hard + * block instance name and the type of hard + * block. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. This data structure + * stores a list of references to the hard + * block instances within the design. + * + * @param curr_hard_block_instance_index an integer that identifies a single + * hard block instance within the list + * of all hard blocks in the netlist. This + * parameter identifies the hard block + * that contains the corresponding + * lut or dff node. + * + */ +static void assign_net_to_hard_block_instance_port(t_node* curr_module_node, t_parsed_hard_block_port_info* curr_module_node_info, t_hard_block_recog* module_hard_block_node_refs_and_info, int curr_hard_block_instance_index) +{ + t_hard_block* curr_hard_block_instance = &(module_hard_block_node_refs_and_info->hard_block_instances[curr_hard_block_instance_index]); + + std::unordered_map::iterator curr_hard_block_type_port_info = module_hard_block_node_refs_and_info->hard_block_type_name_to_port_info.find(curr_module_node_info->hard_block_type); + + int port_to_assign_index = 0; + + t_node_port_association* curr_module_node_port_connected_to_hard_block_instance_net = get_lut_dffeas_port_connected_to_hard_block_instance_net(curr_module_node, module_hard_block_node_refs_and_info->target_device_info); + + port_to_assign_index = identify_port_index_within_hard_block_type_port_array(&(curr_hard_block_type_port_info->second), curr_module_node_info, curr_module_node); + + // assign the net to the crresponding hard block instance port // + handle_net_assignment(curr_module_node, curr_hard_block_instance, port_to_assign_index, curr_module_node_port_connected_to_hard_block_instance_net, curr_module_node_info); + + return; +} + +/** + * @details The luts and dff that represent hard block ports + * generally contain a number of ports themselves. This + * function determines the port within the luts and dff + * that is connected to an external (we need to connect this + * net to the hard block port). + * + * We find that for luts, there are generally two ports, an + * input port (dataa, datab, datac, datad, datae, dataf) and + * and output port (combout). When processing luts, this function + * returns the port that is not named 'combout'. + * + * When looking at dff, there are three ports (clk, d, q). When + * processing dff, this function returns the 'q' port. + * + * @param curr_module_node A 't_node' structure (found in 'vqm_dll.h') + * that represents a lut or dff within the module + * (netlist). + * + */ +static t_node_port_association* get_lut_dffeas_port_connected_to_hard_block_instance_net(t_node* curr_module_node, DeviceInfo target_device_info) +{ + t_node_port_association* port_connected_to_hard_block_instance_net = NULL; + + int number_of_ports_in_node = curr_module_node->number_of_ports; + + std::string curr_module_node_type = curr_module_node->type; + std::string curr_module_node_name = curr_module_node->name; + + std::string curr_port_name; + + // store what type of port we expect the net to be connected to + std::string expected_port_type; + + if (!(curr_module_node_type.compare(target_device_info.lut_type_name))) + { + + // we are here if the current node is LUT + + // if the current node is a LUT, then the LUT represents an input port and so the LUT input should be connected to the net + expected_port_type.assign(INPUT_PORTS); + } + else + { + // we are here if the current node is a DFF + + // if the current node is a DFF, then the DFF represents an output port and so the DFF output should be connected to the net + expected_port_type.assign(OUTPUT_PORTS); + } + + for (int i = 0; i < number_of_ports_in_node; i++) + { + + curr_port_name.assign(curr_module_node->array_of_ports[i]->port_name); + + if (!(curr_module_node_type.compare(target_device_info.lut_type_name))) + { + // if the node is a LUT // + if (curr_port_name.compare(target_device_info.lut_output_port)) + { + // if the port is not the LUT output ie. "combout" port // + + /* if we are here then we know that the LUT the current node + represents must be an input port for hard block instance it is part of. We also know that this type of LUT only has an input and output port in the vqm netlist. Finally we know that the current port must be an input, so it's connected net is what we want to assign to the hard block instance port. */ + port_connected_to_hard_block_instance_net = curr_module_node->array_of_ports[i]; + + break; + } + + } + else + { + // if the node is a flip flop // + if (!(curr_port_name.compare(target_device_info.dff_output_port))) + { + // if the port is a D flip flop output ie. "q" port // + + /* if we are here then we know that the DFF the current node + represents mus be an output port for hard block instance it is part of. Finally we know that the current port must be an output, so its connected net is what we want to assign to the hard block instance port. */ + port_connected_to_hard_block_instance_net = curr_module_node->array_of_ports[i]; + + break; + } + } + } + + if (port_connected_to_hard_block_instance_net == NULL) + { + /* we did not find any ports that fit the conditions above + and this means that the current node, which represents a hard block + instance port does not have an accompanying net. This should never happen, so throw an error and indicate to the user */ + throw vtr::VtrError("The vqm netlist node '" + curr_module_node_name + "' does not have an " + expected_port_type + " port."); + } + + return port_connected_to_hard_block_instance_net; +} + +/** + * @details All the ports of a hard block are arranged within + * an array that is inside a t_array_ref structure. + * This function returns the index of where a port + * is located within the array. + * + * @param curr_hard_block_type_port_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the + * indices of ports within the port + * array of a hard block. + * + * @param curr_module_node_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the hard + * block instance name and the type of hard + * block. + * + * @param curr_module_node A 't_node' structure (found in 'vqm_dll.h') + * that represents a lut or dff within the module + * (netlist). Used to report error information. + * + */ +static int identify_port_index_within_hard_block_type_port_array(t_hard_block_port_info* curr_hard_block_type_port_info, t_parsed_hard_block_port_info* curr_module_node_info, t_node* curr_module_node) +{ + int identified_port_index = 0; + + int port_end_index = 0; + + // error related identifiers + std::string curr_module_node_name = curr_module_node->name; + + // identifier to store the port size of the current port + std::unordered_map::iterator found_port_size; + + /* use the mapping structure within the hard block port info struct + to find the index the port can be found in within the port array of a given hard block. If the port is vectored, then the index returned is the beginning of the vectored port (ie. vectored_port[0])*/ + std::unordered_map::iterator found_port_start_index = curr_hard_block_type_port_info->port_name_to_port_start_index.find(curr_module_node_info->hard_block_port_name); + + // check to see whether the port name exists + if (found_port_start_index == (curr_hard_block_type_port_info->port_name_to_port_start_index.end())) + { + // port does not exist, so throw and error and indicate it to the user + throw vtr::VtrError("The vqm netlist node '" + curr_module_node_name + "' represents a port: '" + curr_module_node_info->hard_block_port_name +"' within hard block model: '" + curr_module_node_info->hard_block_type + "'. But this port does not exist within the given hard block model as described in the architecture file."); + } + + + // at this point the port belongs to the hard block model // + + /* now we assign the found base port index and increment it by the parsed index from the node name (ie hard_block_port_index from t_parsed_hard_block_port_info). + + For example support a port array is arranged as follows: + index: 0 1 2 3 + port_array: port_1[0] port_1[1] port_1[2] port_1[2] + + Now if we want the index of port_1[1], the base port index would be 0, then we need to increment by 1. This is why the increment is requried. + + If the port is not vectored, the increment value would be 0. So the case is handled + */ + identified_port_index = found_port_start_index->second; + identified_port_index += curr_module_node_info->hard_block_port_index; + + // now we check whether the port index is within the port range // + + // if the port size of the current port (using internal mapping structure found in the hard block port info struct) + // this should result in a valid value as we already verfied whether the port exists + found_port_size = curr_hard_block_type_port_info->port_name_to_port_size.find(curr_module_node_info->hard_block_port_name); + + // calculate port end index + port_end_index = found_port_start_index->second + (found_port_size->second - 1); + + // verify if the current port index is out of ranged by chekcing if it is larger than the maximum index for the port + if (identified_port_index > port_end_index) + { + // port index is out of range, so throw an error and indicate it to the user + throw vtr::VtrError("The vqm netlist node '" + curr_module_node_name + "' represents a port: '" + curr_module_node_info->hard_block_port_name +"' at index: " + std::to_string(curr_module_node_info->hard_block_port_index) + ", within hard block model: '" + curr_module_node_info->hard_block_type + "'. But this port index is out of range in the given hard block model as described in the architecture file."); + } + + return identified_port_index; + +} + +/** + * @details This function connects a port of a given hard block instance node + * to a net within the netlist. The net here is a 't_pin_def' + * structure (found in 'vqm_dll.h'). + * + * @param curr_module_node 't_node' structure + * (found in 'vqm_dll.h') that represents + * a lut or dff node within the module (netlist). + * This is used to get the node name. + * + * @param curr_hard_block_instance a 't_hard_block' structure that represents + * a hard block instance. + * + * @param port_to_assign_index The specific port within the port array that we + * need to connect the net to. + * + * @param port_connected_to_hard_block_instance_net This is a + * 't_node_port_association' structure + * that represents a port that is + * currently connected to the net we + * want to assign to the hard block. + * This port is part of a lut or dff + * node. + * + * @param curr_module_node_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the hard + * block instance name and the type of hard + * block. + */ +static void handle_net_assignment(t_node* curr_module_node, t_hard_block* curr_hard_block_instance, int port_to_assign_index, t_node_port_association* port_connected_to_hard_block_instance_net, t_parsed_hard_block_port_info* curr_module_node_info) +{ + std::string curr_module_node_name = curr_module_node->name; + + t_pin_def* net_to_assign = port_connected_to_hard_block_instance_net->associated_net; + + t_node_port_association* port_to_assign = curr_hard_block_instance->hard_block_instance_node_reference->array_of_ports[port_to_assign_index]; + + // need to check whether the current port was previouly assigned to a net + if ((port_to_assign->associated_net) == NULL) + { + // port was not assigned to a net previously // + + // assign the net to the port ("connect" them) + // since we just connected a port, we have one less port to assign, so update the "ports left to assign" tracker + port_to_assign->associated_net = net_to_assign; + curr_hard_block_instance->hard_block_ports_not_assigned -= 1; + + // assign the wire index of the net that we connect to the hard block port + port_to_assign->wire_index = port_connected_to_hard_block_instance_net->wire_index; + } + else + { + // the port was already assigned previouly, this is an error, since we cannot have multiple nets connected to the same port + // so report an error + throw vtr::VtrError("The vqm netlist node '" + curr_module_node_name + "' represents a port: '" + curr_module_node_info->hard_block_port_name +"' within hard block model: '" + curr_module_node_info->hard_block_type + "'. But this port was already represented previously, therefore the current netlist node is a duplication of the port. A port can only have one netlist node representing it."); + } + + return; +} + +/** + * @details Looking at the figure at the top, there are essentially + * two types of luts, that represents hard block ports. The first + * type of lut represents input ports and the second type of lut + * represents an output port. + * + * This function checks to see whether a ndde is a lut that represents + * and output or not. + * + * @param curr_module_node A 't_node' structure + * (found in 'vqm_dll.h') that represents + * a lut or dff node within the module (netlist). + * + */ +static bool is_hard_block_port_legal(t_node* curr_module_node, DeviceInfo target_device_info) +{ + + bool result = false; + + std::string curr_module_node_type = curr_module_node->type; + + // now we check whether the current node is a lut that represents an input port or the current node is a DFF + if ((curr_module_node->number_of_ports != target_device_info.lut_output_port_size) || (!(curr_module_node_type.compare(target_device_info.dff_type_name)))) + { + result = true; + } + + return result; + +} + +/** + * @details This function creates a 't_array_ref' structure + * to store a new array of ports that represent the + * connectivity of a hard block instance within the design. + * + * All the new hard blocks in the design are previously + * processed and a template of their ports are stored inside + * a 't_hard_block_port_info' (found in hard_block_recog.h') structure. + * By template, we mean that all the default information about the + * ports are stored and the ports are unconnected. + * + * In this function, the 't_array_ref' structure is initialized + * by copying the port information stored inside the + * 't_hard_block_port_info' structure. + * + * @param curr_hard_block_type_port_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * the hard block type the current + * instance is of. In this function, this + * parameter is used to copy the port + * information about the hard block. + * + */ +static t_array_ref* create_unconnected_hard_block_instance_ports(t_hard_block_port_info* curr_hard_block_type_port_info) +{ + t_array_ref* template_for_hard_block_ports = &(curr_hard_block_type_port_info->hard_block_ports); + t_array_ref* hard_block_instance_port_array = NULL; + int number_of_ports = template_for_hard_block_ports->array_size; + + // temporarily store single port parameters + char* port_name = NULL; + int port_index = 0; + int port_wire_index = 0; + + // store the newly created port + t_node_port_association* temp_port = NULL; + + // convert the template hard block ports into a port format + t_node_port_association** template_port_array = (t_node_port_association**)template_for_hard_block_ports->pointer; + + // create memory to store the ports for the newly created hard block instance + hard_block_instance_port_array = create_and_initialize_t_array_ref_struct(); + + // go through the template ports, copy their parameters to a new set of ports that will be for the new hard block instance we are creating + for (int i = 0; i < number_of_ports; i++) + { + port_name = template_port_array[i]->port_name; + port_index = template_port_array[i]->port_index; + port_wire_index = template_port_array[i]->wire_index; + + temp_port = create_unconnected_node_port_association(port_name, port_index, port_wire_index); + + append_array_element((intptr_t)temp_port, hard_block_instance_port_array); + } + + return hard_block_instance_port_array; + +} + +/** + * @details This function creates a 't_node' structure + * for a hard block instance that need to be added to the + * netlist. The following steps are performed on the + * 't_node' structure: + * - store the array of unconnected ports to describe + * the connectivity of this hard block instance + * - store the name of the hard block instance and also its + * type + * + * @param curr_hard_block_instance_ports A 't_array_ref' structure (found in + * 'vqm_dll.h') that contains an array + * of unconnected ports that represent + * the connectivity of a hard block + * instance. + * + * @param curr_hard_block_instance_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * about the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the + * total number of ports in the hard + * block. + * + */ +static t_node* create_new_hard_block_instance_node(t_array_ref* curr_hard_block_instance_ports, t_parsed_hard_block_port_info* curr_hard_block_instance_info) +{ + t_node* new_hard_block_instance = NULL; + + char* hard_block_instance_name = NULL; + char* hard_block_instance_type = NULL; + + int hard_block_instance_name_size = strlen((curr_hard_block_instance_info->hard_block_name).c_str()) + 1; + int hard_block_instance_type_size = strlen((curr_hard_block_instance_info->hard_block_type).c_str()) + 1; + + // create the node for the new hard block instance + new_hard_block_instance = (t_node*)vtr::malloc(sizeof(t_node)); + + // assign the ports and their count for the new hard block + new_hard_block_instance->array_of_ports = (t_node_port_association**)curr_hard_block_instance_ports->pointer; + + new_hard_block_instance->number_of_ports = curr_hard_block_instance_ports->array_size; + + // hard blocks will not have any parameters so indicate that + new_hard_block_instance->number_of_params = 0; + new_hard_block_instance->array_of_params = NULL; + + // create storage for the name and type of the current hard block and store their values // + + hard_block_instance_name = (char*)vtr::malloc(sizeof(char)*hard_block_instance_name_size); + hard_block_instance_type = (char*)vtr::malloc(sizeof(char)*hard_block_instance_type_size); + + strcpy(hard_block_instance_name, (curr_hard_block_instance_info->hard_block_name).c_str()); + strcpy(hard_block_instance_type, (curr_hard_block_instance_info->hard_block_type).c_str()); + + new_hard_block_instance->name = hard_block_instance_name; + new_hard_block_instance->type = hard_block_instance_type; + + return new_hard_block_instance; + +} + +/** + * @details This function performs the following steps: + * - Stores a reference to a newly added hard block node + * to the module (netlist) and as well as information + * about the port assignments of the hard block into a + * 't_hard_block' datastructure (found in 'hard_block_recog.h') + * - The 't_hard_block' is then stored into a vector of other + * hard blocks in the design + * - Finally, the index of the 't_hard_block' structure within the + * previous vector is stored inside a map datastructure and the + * key was the name of the hard block instance within the design + * and this was done for a quick lookup. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The reference to + * the newly added hard block instance in the + * design will be stored in here. + * + * @param curr_hard_block_type_port_info This is a 't_hard_block_port_info' + * structure and can be found in + * 'hard_block_recog.h'. This structure + * stores the port information about + * about the hard block type the current + * instance is of. In this function, this + * parameter is used to determine the + * total number of ports in the hard + * block. + * + * @param new_hard_block_instance_node A 't_node' structure + * (found in 'vqm_dll.h') that represents + * a single hard block instance within the + * design. + * + * @param curr_module_node_info This is a 't_parsed_hard_block_port_info' + * structure. It is defined in 'hard_block_recog.h'. + * This structure is created to store relevant + * information about a hard block port + * whenever a node is identified as representing + * a hard block port. In this function, this + * parameter provides the name of the corresponding + * hard block instance within the design. + * + */ +static int store_new_hard_block_instance_info(t_hard_block_recog* module_hard_block_node_refs_and_info, t_hard_block_port_info* curr_hard_block_type_port_info, t_node* new_hard_block_instance_node, t_parsed_hard_block_port_info* curr_module_node_info) +{ + int new_hard_block_instance_index = 0; + + t_hard_block new_hard_block_instance_info; + + // store information regarding the new hard block instance being added to the module node list to a new t_hard_block struct // + + new_hard_block_instance_info.hard_block_instance_node_reference = new_hard_block_instance_node; + + // initially all ports for the newly created hard block instance are unassigned + new_hard_block_instance_info.hard_block_ports_not_assigned = curr_hard_block_type_port_info->hard_block_ports.array_size; + + // insert the hard block (t_hard_block struct) to the list of all hard block instances + module_hard_block_node_refs_and_info->hard_block_instances.push_back(new_hard_block_instance_info); + + // since we added the new hard block instance (t_hard_block struct) at the end of vector, the index will be the last position with the list + new_hard_block_instance_index = module_hard_block_node_refs_and_info->hard_block_instances.size() - 1; + + /* now create a mapping between the new hard block instance name and the index it is located in with the list of all hard block instances, so we can find it quicly using just the hard block instance name*/ + module_hard_block_node_refs_and_info->hard_block_instance_name_to_index.insert(std::pair(curr_module_node_info->hard_block_name,new_hard_block_instance_index)); + + return new_hard_block_instance_index; +} + +/** + * @details Given an arbritary array of pointers, this function + * stores the array and its properties into a + * 't_array_ref' structure. + * + * @param array_to_store array of pointers + * + * @param array_size size of the array passed to this function + * + */ +static t_array_ref* create_t_array_ref_from_array(void** array_to_store, int array_size) +{ + t_array_ref* array_reference = NULL; + int array_allocated_size = 0; + + array_reference = (t_array_ref*)vtr::malloc(sizeof(t_array_ref)); + + // determine how large the array is + array_allocated_size = calculate_array_size_using_bounds(array_size); + + // assign the array and its corresponding size information to the array ref struct + array_reference->allocated_size = array_allocated_size; + array_reference->array_size = array_size; + array_reference->pointer = array_to_store; + + return array_reference; + +} + +/** + * @details Given a node within the module (netlist) that is a lut or dff + * which represents a hard block port, this function identifies + * the following information: + * - The name of the hard block instance which the current port that + * the node represents belongs too + * - The specific hard block this node is a part of (figuring out + * which type of hard block the port that the node represents + * belongs too) + * - The name of the port that the current node represents + * - If the port is a bus, then the specific index the current + * node represents + * + * @param curr_module_node A 't_node' structure (found in 'vqm_dll.h') that + * is a lut or dff which represents a port of + * hard block. + * + * @param hard_block_type_name_list A list of the hard block names that need + * to be properly added to the netlist. + * + */ +static t_parsed_hard_block_port_info extract_hard_block_port_info_from_module_node(t_node* curr_module_node, std::vector* hard_block_type_name_list) +{ + std::string curr_module_node_name = curr_module_node->name; + + // container to hold all the names of the different hierachy levels found for the current node in the netlist(refer to 'split_node_name function' for more info) + std::vector components_of_module_node_name; + + int index_of_node_name_component_with_hard_block_type_info = 0; + int index_of_node_name_component_with_hard_block_port_info = 0; + + // data structure to hold all the extract port information + t_parsed_hard_block_port_info stored_port_info; + + split_node_name(curr_module_node_name, &components_of_module_node_name, VQM_NODE_NAME_DELIMITER); + + // if the node name does not have atleast two hierarhcy levels, then it cannot be a hard block port so we cannot extract any more information. + // a hard block port in the vqm netlist must have atleast the port information and the next top level block name it is connected to. As shown below: + // For example, \router:test_noc_router|payload[8]~QIC_DANGLING_PORT_I is a hard block payload port connected to a router block. + // \Add0~9_I is not a hard block port + if ((components_of_module_node_name.size()) >= 2) + { + // looking at the comment above, regardless of how many hierarchy levels an node has, the lowest level (last index) will contain the port info and the second lowest level (second last index) will contain the hard block type. + // so we store those indices below + index_of_node_name_component_with_hard_block_type_info = components_of_module_node_name.size() - 2; + index_of_node_name_component_with_hard_block_port_info = components_of_module_node_name.size() - 1; + + stored_port_info.hard_block_type.assign(identify_hard_block_type(hard_block_type_name_list, components_of_module_node_name[index_of_node_name_component_with_hard_block_type_info])); + + // if the hard block type was empty, then the current node does not represent a hard block port, therefore we cannot extract any more info + /* For example, sha256_pc:\sha256_gen:10:sha256_pc_gen:sha256_2|altshift_taps:q_w_rtl_2|shift_taps_b8v:auto_generated|altsyncram_6aa1:altsyncram5|ram_block6a17550~I meets all the conditions to get here, but it is a ram block */ + if (!(stored_port_info.hard_block_type.empty())) + { + // if the hard block type was verified then we can go ahead and store its name and also its parsed port name and index + + stored_port_info.hard_block_name.assign(construct_hard_block_name(&components_of_module_node_name, VQM_NODE_NAME_DELIMITER)); + + identify_hard_block_port_name_and_index(&stored_port_info, components_of_module_node_name[index_of_node_name_component_with_hard_block_port_info]); + } + } + + return stored_port_info; + +} + +/** + * @details Given a string, this function splits the string into multiple + * pieces by a delimiter character. + * + * All the node names within the module (netlist) are composed of + * multiple elements based on a nodes 'hierarchy' within the design. + * + * For example: + * '\router:test_noc_router|sc_flit[0]~0_I' + * + * In the node name above, the first component is + * '\router:test_noc_router' and the second component + * is 'sc_flit[0]~0_I'. The first component in this example + * describes the name of the module and the second component + * describes the specific port of the module. We want to divide + * the node name into multiple components and this is done within + * this function. + * + * @param original_node_name The name of a node within the module (netlist). + * + * @param node_name_components A list of strings that are seperated components + * of the original_node_name string. + * + * @param delimiter A character that will be used to seperate the + * original_node_name string above into multiple + * pieces. + * + */ +static void split_node_name(std::string original_node_name, std::vector* node_name_components, std::string delimiter) +{ + + // positional trackers to determine the beginning and end position of each hierarchy level (component of the node name) of the current node within the design. The positions are updated as we go through the node name and identify every level of hierarchy. + size_t start_of_current_node_name_component = 0; + size_t end_of_current_node_name_component = 0; + + // go through the node name, then identify and store each hierarchy level of the node (represented as a component of the original node name), found using the delimiter + while ((end_of_current_node_name_component = original_node_name.find(delimiter, start_of_current_node_name_component)) != std::string::npos) + { + // store the current node name component (current hierarchy level) + node_name_components->push_back(original_node_name.substr(start_of_current_node_name_component, end_of_current_node_name_component - start_of_current_node_name_component)); + + // update position for the next node name component (next hierarchy level) + start_of_current_node_name_component = end_of_current_node_name_component + delimiter.length(); + + } + + // since the last component (the port info is not follwed by a delimiter we need to handle it here and store it) + node_name_components->push_back(original_node_name.substr(start_of_current_node_name_component, end_of_current_node_name_component - start_of_current_node_name_component)); + + return; +} + +/** + * @details This function iterates through a list of hard + * block names and checks to see if any of the + * names are a substring within a given t_module (netlist) + * node name. If a substring is found, then the matched + * hard block name is returned. The returned name is the + * basically the type of hard block this node belongs to. + * + * @param hard_block_type_name_list A list of the hard block names that need + * to be properly added to the netlist. + * + * @param curr_node_name_component The name of node within the t_module + * structure. + * + */ +static std::string identify_hard_block_type(std::vector* hard_block_type_name_list, std::string curr_node_name_component) +{ + std::vector::iterator hard_block_type_name_traverser; + + std::string hard_block_type_name_to_find; + + // stores the matched hard block name. Default is empty, meaning that + // nothing was matched + std::string hard_block_type = ""; + + size_t match_result = 0; + + // iterate through each hard block name in the list + for (hard_block_type_name_traverser = hard_block_type_name_list->begin(); hard_block_type_name_traverser != hard_block_type_name_list->end(); hard_block_type_name_traverser++) + { + // adding the colon operator to the end of the current hard block name + // ex. 'test' -> 'test:' + /* If a node belongs to a hard block, then the hard block name is + generally followed by a colon operator, which is why need to append it. + for a 'router' block we can expect something like this: + \router:test_noc_router|sc_flit[8]~reg0_I, where router is follwed by : + + This also helps ignore invalid cases where a block instantiation has + a hard block name within it, even though the block is not of that hard + block type. + For example: + test_block router_one + */ + hard_block_type_name_to_find.assign(*hard_block_type_name_traverser + HARD_BLOCK_TYPE_NAME_SEPERATOR); + + match_result = curr_node_name_component.find(hard_block_type_name_to_find); + + // check to see if the node belongs to the current hard block type + // (the current hard block name is a substring within the node name) + if (match_result != std::string::npos) + { + hard_block_type.assign(*hard_block_type_name_traverser); + break; + } + + } + + return hard_block_type; +} + +/** + * @details Given a list of strings, this function combines the strings + * together using a delimitter and generates a combined string. + * + * @param node_name_components A list of strings that will be combined + * together to generate a single string output. + * @param delimiter A character that will be used to seperate the list elements + * above in the generated string output. + * + */ +static std::string construct_hard_block_name(std::vector*node_name_components, std::string delimiter) +{ + // stores the full name of the hard block the current node is part of, the current node represents a port of a hard block + std::string curr_hard_block_name = ""; + + /* the hard block name should not include the specific port the current node represents (so we reduce the size by 1 to remove it when constructing the hard block name) + the last index of the node name components vector contains the port information, so by reducing the size of the vector by one we can ignore the port info when constructing the hard block name + + For example, if the current node name for a 'router' block was: + '\router:test_noc_router|sc_flit[8]~reg0_I' + we know that the 'sc_flit[8]~reg0_I' is a specific port, so + we can remove it when constructing the hard block name. Which + would just be '\router:test_noc_router|'. */ + int number_of_node_name_components = node_name_components->size() - 1; + + // go through the node name components and combine them together to form the hard block name. + for (int i = 0; i < number_of_node_name_components; i++) + { + curr_hard_block_name += (*node_name_components)[i] + delimiter; + } + + return curr_hard_block_name; + +} + +/** + * @details Given a node in the module (netlist) that we know represents + * a hard block port, this function takes a component of the node name + * and determines the specific hard block port and as well as the + * index of the port if it is a bus. The determined values are then + * stored. + * + * @param curr_hard_block_port This is a 't_parsed_hard_block_port_info' + * structure. It is defined in 'hard_block_recog.h'. + * This structure is created to store relevant + * information about a hard block port + * whenever a node is identified as representing + * a hard block port. + * + * @param curr_node_name_component Whenever a node is identified as representing + * a hard block port, only a portion of the name + * contains useful information about the hard block port. + * For example, suppose a node name for a + * 'router' block port was: + * '\router:test_noc_router|sc_flit[8]~reg0_I' + * + * Looking at the name above, only the + * component 'sc_flit[8]~reg0_I' contains + * information above the port. So that + * component is passed for this parameter. + * + */ +static void identify_hard_block_port_name_and_index (t_parsed_hard_block_port_info* curr_hard_block_port, std::string curr_node_name_component) +{ + // identifer to check whether the port defined in the current node name is a bus (ex. payload[1]~QIC_DANGLING_PORT_I) + std::regex port_is_a_bus ("(.*)[[]([0-9]*)\]~(?:.*)"); + + // identifier to check whether the current port defined in the current node name isn't a bus (ex. value~9490_I) + std::regex port_is_not_a_bus ("(.*)~(?:.*)"); + + // when we compare the given node name component with the previous identifiers, the port name and port index are extracted and they are stored in the variable below + std::smatch port_info; + + // handle either the case where the current port defined in the provided node name is either a bus or not + // we should never not match with either case below + if (std::regex_match(curr_node_name_component, port_info, port_is_a_bus, std::regex_constants::match_default)) + { + // if we are here, then the port is a bus + + // store the extracted port name and index to be used externally + curr_hard_block_port->hard_block_port_name.assign(port_info[PORT_NAME]); + + curr_hard_block_port->hard_block_port_index = std::stoi(port_info[PORT_INDEX]); + } + else if (std::regex_match(curr_node_name_component, port_info, port_is_not_a_bus, std::regex_constants::match_default)) + { + // if we are here then the port was not a bus + // not need to update port index as the default value represents a port that is not a bus + + // store the extracted port name + curr_hard_block_port->hard_block_port_name.assign(port_info[PORT_NAME]); + + } + + return; +} + +/** + * @details This function removes all the nodes in the module (netlist) + * that are luts and dff which represent hard block ports. After + * identifying and adding the necessary hard block to the netlist + * the lut and dff that represent the hard block ports are not + * required, so they need to be removed from the netlist. + * + * @param main_module This contains all the netlist information, such as luts, + * flip flops, and other types of blocks. All the nets within + * the netlist are also included. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The full list + * of lut and dff nodes that need to be + * removed should be found here. + * + */ +static void remove_luts_dffeas_nodes_representing_hard_block_ports(t_module* main_module, t_hard_block_recog* module_hard_block_node_refs_and_info) +{ + // reference to the list of luts/dffeas nodes we need to remove + std::vector* list_of_nodes_to_remove = &(module_hard_block_node_refs_and_info->luts_dffeas_nodes_to_remove); + + t_node** module_node_list = main_module->array_of_nodes; + int number_of_nodes_in_module = main_module->number_of_nodes; + + // iterator to go through the list of nodes we will be removing + std::vector::iterator node_to_remove; + + // go through the list of nodes we need to remove and delete them + // from the node array within the module + for(node_to_remove = list_of_nodes_to_remove->begin(); node_to_remove != list_of_nodes_to_remove->end(); node_to_remove++) + { + remove_node(*node_to_remove, module_node_list, number_of_nodes_in_module); + + } + + // we need to fix the gaps created in the node array after removing all luts/dffeas nodes previously + reorganize_module_node_list(main_module); + + return; +} + +/** + * @details This function goes through all the newly added hard block nodes + * to the module (netlsit) and checks to see that every port has a + * valid connection to a net. If a port is unassigned for any hard + * block, then an error is thrown. + * + * We expect that every hard block should have all its port assigned + * to a net, even if the connection 'ground' of 'vdd. Having an + * unconnected port implies that the generate vqm netlist was + * incorrect and we check for this here. + * + * @param module_hard_block_node_refs_and_info This is a data structure of type + * 't_hard_block_recog', which can be found + * in 'hard_block_recog.h'. The full list + * of newly added hard block nodes need to be + * stored in here. + * + */ +static void verify_hard_blocks(t_hard_block_recog* module_hard_block_node_refs_and_info) +{ + + // If we find a hard block instance that has ports unassigned, we store its information in the variables below + t_node* incomplete_hard_block_instance = NULL; + t_node_port_association* temp_port = NULL; + std::string incomplete_hard_block_instance_name = ""; + std::string incomplete_hard_block_instance_type = ""; + std::string unassigned_port_name = ""; + + std::vector* list_of_hard_block_instances = &(module_hard_block_node_refs_and_info->hard_block_instances); + + // we order the vector of t_hard_blocks from the largest to smallest number of ports not assigned + std::sort(list_of_hard_block_instances->begin(), list_of_hard_block_instances->end(), sort_hard_blocks_by_valid_connections); + + /* We just need to check the first element of the previously sorted vector of hard block instances in the design to see if has any ports unassigned. If the first element has all ports connected, then this means all other hard block instances in the design have all their ports assigned (since we ordered them from largest to smallest number of ports unassigned). + + If we find that the the hard block instance has a port or more that is unassigned, we go through all the ports and throw an error on the first unassgined port. + */ + if (!(list_of_hard_block_instances->empty()) && (((list_of_hard_block_instances->begin())->hard_block_ports_not_assigned) != 0)) + { + incomplete_hard_block_instance = (list_of_hard_block_instances->begin())->hard_block_instance_node_reference; + + // iterate through all ports in the hard block + for (int i = 0; i < incomplete_hard_block_instance->number_of_ports; i++) + { + temp_port = incomplete_hard_block_instance->array_of_ports[i]; + + // check each port too see if it was unassigned + if (temp_port->associated_net == NULL) + { + // if we are here, then the port was unassigned + + // store some information about the port that is unassgined the hard block instance it is part of + incomplete_hard_block_instance_name.assign( incomplete_hard_block_instance->name); + incomplete_hard_block_instance_type.assign(incomplete_hard_block_instance->type); + unassigned_port_name.assign(temp_port->port_name); + + + // we store ports that are not bussed with an index of -1, we dont want to show that to the user, so we use two different error messages for ports that are a bus and ports which are not + if ((temp_port->port_index) == PORT_WIRE_NOT_INDEXED) + { + throw vtr::VtrError("The hard block instance '" + incomplete_hard_block_instance_name + "', which is of hard block type: '"+ incomplete_hard_block_instance_type + "' has a port: '" + unassigned_port_name +"' that is unassigned. This means that the port was not included in the provided vqm netlist."); + } + else + { + throw vtr::VtrError("The hard block instance '" + incomplete_hard_block_instance_name + "', which is of hard block type: '"+ incomplete_hard_block_instance_type + "' has a port: '" + unassigned_port_name +"[" + std::to_string(temp_port->port_index)+"]' that is unassigned. This means that the port was not found in the provided vqm netlist."); + } + + } + } + } + + return; + +} + +/** + * @details This function deletes any memory that was allocated + * to store the port information of the new types of + * hard blocks (internally modelling them). + * + * We need to store all the port information for each + * and every new type of hard block. This is done by + * dynamically creating 't_hard_block_port_info' structures + * for each hard block. We handle the deletion of this + * memory here. + * + * @param hard_block_type_name_to_port_info_map A map + * that contains all the + * 't_hard_block_port_info' structures, + * which store the port information + * for all the new hard blocks. This + * data structure can be found within + * 't_hard_block_recog'. + * + */ +static void delete_hard_block_port_info(std::unordered_map* hard_block_type_name_to_port_info_map) +{ + std::unordered_map::iterator curr_hard_block_port_info = hard_block_type_name_to_port_info_map->begin(); + + while (curr_hard_block_port_info != (hard_block_type_name_to_port_info_map->end())) + { + + int number_of_ports = (curr_hard_block_port_info->second).hard_block_ports.array_size; + + uintptr_t* ports_to_delete = (uintptr_t*)((curr_hard_block_port_info->second).hard_block_ports.pointer); + + deallocate_array(ports_to_delete, number_of_ports, free_port_association); + + curr_hard_block_port_info++; + + } + + return; +} + +/** + * @details A utility function that is used with std::sort to help + * sort a vector of 't_hard_block' structures from largest + * to smallest number of unconnected ports. + * + */ +bool sort_hard_blocks_by_valid_connections(t_hard_block instance_one, t_hard_block instance_two) +{ + + return ((instance_one.hard_block_ports_not_assigned) > (instance_two.hard_block_ports_not_assigned)); + +} \ No newline at end of file diff --git a/utils/vqm2blif/src/base/hard_block_recog.h b/utils/vqm2blif/src/base/hard_block_recog.h new file mode 100644 index 00000000000..c24d7bd76cd --- /dev/null +++ b/utils/vqm2blif/src/base/hard_block_recog.h @@ -0,0 +1,241 @@ +#ifndef HARD_BLOCK_RECOG_H +#define HARD_BLOCK_RECOG_H + +/* VQM new primitive hard block recognition header file +* +* The purpose of this file is to identify and instantiate +* user defined hard blocks, which will later be writtent to a .blif file. +* For more information, refer to 'hard_block_recog.cpp'. +* +* This file contains the definition of the main data structures +* that are instantiated when trying to identify and create user +* created hard blocks. +* +* This file also contains all the declarations of functions that can be +* used to interact with and manipulate the main data structures. Additionally, +* some utility functions are added to help identify whether a hard block was +* included within the design. +* For more information, refer to 'hard_block_recog.cpp'. +* +*/ + + +// need to use vtr::malloc for memory allocation (only in the files here) +// no need to rcheck for failed malloc (NULL pointer), this is already checked in vtr::malloc + + +// user VTR libraries +#include "vqm_dll.h" +#include "physical_types.h" +#include "logic_types.h" +#include "vtr_error.h" +#include "vtr_log.h" +#include "vqm_common.h" +#include "vtr_memory.h" +#include "logic_types.h" + +// user vqm2blif libraries +#include "vqm2blif_util.h" +#include "cleanup.h" + +// standard libraries +#include +#include +#include +#include + +// useful spacing definitions +#define TOP_LEVEL 0 +#define SECOND_LEVEL 1 + +#define PORT_WIRE_NOT_INDEXED -1 + +#define INPUT_PORTS "input" +#define OUTPUT_PORTS "output" + +#define HARD_BLOCK_WITH_NO_PORTS 0 +#define DEFAULT_PORT_INDEX 0 + +// unique identifier that seperates a hard block type name (module name), from the specific instance name +#define HARD_BLOCK_TYPE_NAME_SEPERATOR ":" + +// a node name in reference to a hard block port (dffeas and stratic_lcell blocks) in the vqm netlist file consists of multiple hierarchy levels. +// for example we can have the following name: \router_interconnect:test_router_interconnect|router:noc_router|id[2]~QIC_DANGLING_PORT_I (this has 3 hierarchy levels) +// each level of hierarchy (module within module) is seperated by the delimiter character defined below. The last level of hierarchy is the output net of the block +#define VQM_NODE_NAME_DELIMITER "|" + +// a port that is a bus is a vectored port. So a single port name with multiple indices. +// We use the size of the port to determine whether the port is a bus or not. +// A port size of 1 represents a port that is not a bus, whereas a port size greater than 1 represents a bus. +// So a non-bus port size is represented below +#define PORT_NOT_BUS 1 + +// We use regex to extract the port name and index from the vqm netlist. +// For example, given the string payload[1]~QIC_DANGLING_PORT_I, we would extract 'payload' and '1'. +// For the case where a port is not a bus, we would just extract the port name. +// Now the extracted information is stored in an array of strings, and the indices of where the port name and index are stored is found below. +#define PORT_NAME 1 +#define PORT_INDEX 2 + +// used to identify the case where a hard block instance is not found within the netlist +#define HARD_BLOCK_INSTANCE_DOES_NOT_EXIST -1 + +/* Structure Declarations */ + + +/* +* The port information (in an array) for an arbritary user defined +* hard block in the design is stored in s_hard_block_port_info. Then a +* mapping is provided which can +* help identify the specific location within the port array +* that a given port name begins at. This data structure is created +* for each type of hard block. +*/ +typedef struct s_hard_block_port_info +{ + // mapping structure to quickly identify where a specific port begins + std::unordered_map port_name_to_port_start_index; + + // mapping structure to determine the size of each port + std::unordered_map port_name_to_port_size; + + // An array of all the ports within the hard block is stored here + // refer to 'vqm_dll.h' for more information + t_array_ref hard_block_ports; + +} t_hard_block_port_info; + + +/* +* Each hard block instance that will eventually be written to the blif file +* will need to be stored as a 't_node' type (refer to 'vqm_dll.h'). +* This node will then be stored within a node array and enclosed +* within a 't_module' variable (refer to 'vqm_dll.h'). +* +* A reference to a single hard block node (one instance of a hard block +* within the design) is stored inside s_hard_block, so it essentially +* represents a hard block instance. +* This way, the node can be accessed quickly whenever changes need to be +* applied. Additional information about the hard block is also stored as well. +* +* The node itself is stored internally within the a node array. And +* located inside a module. +*/ +typedef struct s_hard_block +{ + + // helps keep track of the number of hard block ports we have left to assign a net to + int hard_block_ports_not_assigned = 0; + + // a reference to the corresponding hard block node that represents this + // particular hard block instance + t_node* hard_block_instance_node_reference; + +}t_hard_block; + +/* +* Below is the main data structure used for hard block +* identification and creation. This data strcuture contains +* the names and port info of every type of user defined hard blocks. It +* also stores all hard blocks that were instantiated within +* the user design and an accompanying data strcuture to quickly identify +* a specific hard block instance. All functions will primarily interact +* with this data structure. +*/ +typedef struct s_hard_block_recog +{ + + // store the port information of each and every type of + // user defined hard blocks. All ports connected to each + // hard block type are stored. + std::unordered_map hard_block_type_name_to_port_info; + + // store each hard block instantiated within a + // user design + std::vector hard_block_instances; + + /* + Given a specific hard block instance name, we need to quickly + identify the corresponding hard block structure instance. + We do this by using the map data structure below, + where a hard block instance name is associated with an index + within the hard block vector above (stores all instances within the design). + */ + std::unordered_map hard_block_instance_name_to_index; + + /* + The luts and dffeas used to model ports are all stored as nodes and + we will need to remove them from the netlist after adding all the + hard blocks to the netlist.The ports that are modelled by the luts and dffeas + aren't needed once all the hard blocks within the design are added to the netlist. + The luts and dffeas are simply repeating the port information, so we can remove them. + + The nodelist is simply an array. Now deleting lut/dffeas nodes while still creating additional + hard block nodes could cause some problems, so we will delete all these nodes at the end. + Therefore we need to keep a reference for all the luts and dffeas nodes that + represent hard block ports here, so we can them remove later on. + */ + std::vector luts_dffeas_nodes_to_remove; // look into using array index instead + + // variable to store parameters specific to the fpga device used + DeviceInfo target_device_info; + +}t_hard_block_recog; + +/* +* When we go through the .vqm file, the ports of any user defined hard block +* will be represented as a LUT (stratix_lcell), or flip flop (dffeas) +* (for more info refer to 'hard_block_recog.cpp'). The generated names found +* in the .vqm file for the two previous blocks contain a lot of information +* about the hard block. The structure below is used to store the information, +* which includes the hard block name, hard block type, the specfic hard +* block port and if the port is a bus, then the specific index. +*/ +typedef struct s_parsed_hard_block_port_info +{ + // name used to identify the unique hard block the current port belongs to + // multiple ports can belong to the same hard block and there can be multiple hard blocks in the design + std::string hard_block_name = ""; + + // the specific type of hard block the current port belongs to + // the user can define multiple hard block types + std::string hard_block_type = ""; + + // the port name defined in the current block (LUT or dffeas) + std::string hard_block_port_name = ""; + + // index of the port defined in the current block (LUT or dffeas) + // initialized to an index equivalent to what a port that is not a bus would have + int hard_block_port_index = DEFAULT_PORT_INDEX; + +}t_parsed_hard_block_port_info; + + +/* Global Function Declarations +* +* For more information about functions, refer to +* 'hard_block_recog.cpp' +*/ + +/* +* Function: add_hard_blocks_to_netlist +* +* Goes through a netlist and removes luts/dff that represent +* specific hard block ports and then the corresponding hard block +* is added to the netlist. +* +* Parameters: +* t_module* - A reference to the netlist of the design +* t_arch* - a pointer to the FPGA architecture that design will +* target +* std::vector* - a reference to a list of hard block names + that need to be identified within the + netlist. +* +* std::string - the name of the architecture file (".xml") + std::string - the name of the Quartus generated netlist file (".vqm") +* +*/ +void add_hard_blocks_to_netlist(t_module* main_module, t_arch* main_arch, std::vector* list_hard_block_type_names, std::string arch_file_name, std::string vqm_file_name, std::string device); + +#endif diff --git a/utils/vqm2blif/src/base/vqm2blif_util.cpp b/utils/vqm2blif/src/base/vqm2blif_util.cpp index 90ff3e9539d..41ee839a9e5 100644 --- a/utils/vqm2blif/src/base/vqm2blif_util.cpp +++ b/utils/vqm2blif/src/base/vqm2blif_util.cpp @@ -21,6 +21,8 @@ void print_usage (t_boolean terminate){ cout << "\t-include_unused_subckt_pins\n"; cout << "\t-remove_const_nets\n"; cout << "\t-eblif_format\n"; + cout << "\t-insert_custom_hard_blocks ...\n"; + cout << "\t-device (if not provided then default is 'stratixiv')\n"; //Hide experimental options by default //cout << "\t-split_multiclock_blocks\n"; //cout << "\t-split_carry_chain_logic\n"; @@ -49,6 +51,113 @@ void verify_format (string* filename, string extension){ //============================================================================================ //============================================================================================ + + +void verify_hard_block_type_name(string curr_hard_block_type_name){ +// verifies whether the hard block name (type of hard block) provided by the user meets verilog/VHDL naming rules. VHDL naming rules are a subset of verilog, so we just check verilog. (Using verilog 2001 standard) + + // naming rules have 2 main conditions: + // Condition 1: the first charatcer must be a lowercase/uppercase alphabetical character. Or the first character can be a underscore. + // Condition 2: The remaning characters must be a lowercase/uppercase alphabetical character, or a underscore, or a single digit number or the '$' character + // the rules above are checked with the identifier below + std::regex verilog_VHDL_naming_rules_one ("^[a-zA-Z_][a-zA-Z_\$0-9]*[a-zA-Z_\$0-9]$"); + + // verilog names can also contain any characters, as long as they are escaped with a '\' at the start of the identifer. For example, \reset- + // we check this using the identifier below + std::regex verilog_VHDL_naming_rules_two ("[\\](.*)", std::regex_constants::extended); // need std::regex_constants::extended as it supports '\\' character + + if ((!(std::regex_match(curr_hard_block_type_name, verilog_VHDL_naming_rules_one))) && (!(std::regex_match(curr_hard_block_type_name, verilog_VHDL_naming_rules_two)))) + { + // the hard block type name did not meet the verilog naming rules + // Display error message to user + std::cout << "ERROR:The provided Hard Block Type Name '" << curr_hard_block_type_name << "' did not meet the verilog/VHDL naming rules.\n"; + + std::cout << "********\n"; + + std::cout << "Please ensure the provided Hard Block Type Names follow the conditions below:\n"; + + std::cout << "\tCondition 1: The first character of the Hard Block Type Name should either be a-z, A-Z or _\n"; + + std::cout << "\tCondition 2: The remaining characters of the Hard Block Type Name should either be a-z, A-Z, 0-9, _ or $\n"; + + std::cout << "Alternatively, any character can be used for the hard block type name, as long as the name is escaped using '\\' character. For example, '\\reset-' would be legal.\n"; + + exit(1); + + } + + return; + +} + +//============================================================================================ +//============================================================================================ + +void verify_device(string device_name) +{ + /* + The list of devices that this program can support and their parameters are stored within a map structure ('device_parameter_database'). We + check whether the provided device by the user supported by comparing it + to the devices within the map. + */ + + std::map::const_iterator device_support_status; + + device_support_status = device_parameter_database.find(device_name); + + // checks to see whether we support the given device + if (device_support_status == device_parameter_database.end()) + { + // if we are here then we don't support the user supplied device + std::cout << "ERROR:The provided device is not supported."; + std::cout << " Only the following devices are supported:\n"; + + device_support_status = device_parameter_database.begin(); + + while (device_support_status != device_parameter_database.end()) + { + std::cout << device_support_status->first << "\n"; + device_support_status++; + } + + exit(1); + } + + return; + +} + +//============================================================================================ +//============================================================================================ + +//============================================================================================ +//============================================================================================ + +void cleanup_hard_block_type_name(string* curr_hard_block_type_name){ +// if the hard block type name was escaped by '\', we need to remove the '\' character from the name + + // matching token below tries to determine whether the current hard block type name was escaped with '\' + std::regex verilog_naming_rules ("[\\](.*)", std::regex_constants::extended); + + // stores the hard block type name without the '\' character + std::smatch matches; + + // store the matched strings and also determine whether + // the hard block type name is actually escaped. if not escaped, then we do not need to do anything + if (std::regex_match(*curr_hard_block_type_name, matches, verilog_naming_rules, std::regex_constants::match_default)) + { + // if we are here, there should be two string matches + // the first match is the entire hard block type name with the escape character '\' + // the second match should just be the hard block type name without the escape character '\' + + // below we just store the second match + curr_hard_block_type_name->assign(matches[matches.size() - 1]); + } + + return; + +} + void construct_filename (char* filename, const char* path, const char* ext){ //Constructs a char* filename from a given path and extension. diff --git a/utils/vqm2blif/src/base/vqm2blif_util.h b/utils/vqm2blif/src/base/vqm2blif_util.h index 39a4d8ec1d5..14f48c27606 100644 --- a/utils/vqm2blif/src/base/vqm2blif_util.h +++ b/utils/vqm2blif/src/base/vqm2blif_util.h @@ -43,6 +43,7 @@ #include #include #include +#include #include "vqm_dll.h" //VQM Parser #include "hash.h" //Hash Table Functions @@ -82,7 +83,9 @@ enum v_OptionBaseToken OT_REMOVE_CONST_NETS, OT_INCLUDE_UNUSED_SUBCKT_PINS, OT_EBLIF_FORMAT, - OT_UNKNOWN + OT_UNKNOWN, + OT_INSERT_CUSTOM_HARD_BLOCKS, + OT_DEVICE }; struct cstrcomp{ //operator structure to compare C-strings within a map class @@ -106,6 +109,17 @@ struct RamInfo { t_node_port_association* port_b_output_clock = nullptr; }; +// stores relevant information for a given FPGA device +// currently, just storing the strings used to idenitify luts and dff primitives and their ports within the vqm netlist +// add additional parameters as needed +struct DeviceInfo { + std::string lut_type_name; + std::string lut_output_port; + int lut_output_port_size; + + std::string dff_type_name; + std::string dff_output_port; +}; //============================================================================================ // GLOBAL FUNCTIONS @@ -115,6 +129,15 @@ struct RamInfo { void verify_format (string* filename, string extension); //verifies a given string ends in ".extension" +// verifies whether the hard block type name provided by the user meets verilog naming rules +void verify_hard_block_type_name(string curr_hard_block_name); + +// checks to see that the device name supplied matches the devices we can support +void verify_device(string device_name); + +// if the hard block type name was escaped by '\', we need to remove the '\' character from the name (refer to function above) +void cleanup_hard_block_type_name(string* curr_hard_block_name); + void construct_filename (char* filename, const char* path, const char* ext); //constructs a filename based on the path and termination passed //Naming Conventions @@ -164,6 +187,19 @@ extern e_clean clean_mode; //user-set flag dictating how to clean away buffers/i extern t_boolean buffd_outs; //user-set flag that regulates whether to keep buffered outputs +//============================================================================================ +// DEVICE SPECIFIC INFORMATION +//============================================================================================ + +/* + A database that stores parameters for different types of FPGA devices. + Currently only the stratix 4 device parameters are stored. +*/ +const map device_parameter_database { + // stratix 4 device + {"stratixiv", {"stratixiv_lcell_comb", "combout", 1, "dffeas", "q" }} +}; + #endif diff --git a/utils/vqm2blif/src/main.cpp b/utils/vqm2blif/src/main.cpp index cfc9f033f9a..0495eb7231f 100644 --- a/utils/vqm2blif/src/main.cpp +++ b/utils/vqm2blif/src/main.cpp @@ -81,6 +81,7 @@ #include "lut_stats.h" #include "vtr_error.h" #include "physical_types.h" +#include "hard_block_recog.h" #include @@ -140,13 +141,18 @@ t_boolean print_unused_subckt_pins; //user-set flag which controls whether subck //this option to be true. t_boolean eblif_format; //If true, writes circuit in extended BLIF (.eblif) format (supported by YOSYS & VPR) +t_boolean insert_custom_hard_blocks; // user-set flag. Which if true, helps find and filter user-defined hard blocks within the netlist (based on the supplied hard block names) and then instantiates the hard blocks within the blif file. + // or if false, then the netlist is processed without identifying any hard blocks. + // this option should only be used if the original user-design contained custom hard-blocks. + // The user-defined hard block names need to be provided + //============================================================================================ // FUNCTION DECLARATIONS //============================================================================================ //Setup Functions -void cmd_line_parse (int argc, char** argv, string* sourcefile, string* archfile, - string* outfile); +void cmd_line_parse (int argc, char** argv, string* sourcefile, string* archfile, string* outfile, + string* device, std::vector* hard_block_list); void setup_tokens (tokmap* tokens); //Execution Functions @@ -263,6 +269,13 @@ int main(int argc, char* argv[]) //used to construct output filenames from project name //char* filename necessitated by vqm_parse_file() + // a list which stores all the user supplied custom hard block type names + std::vector hard_block_type_name_list; + + // indicates the type of device the circuit is targetting + // we set the default value to the stratix 4 device + string device = "stratixiv"; + //************************************************************************************************* // Begin Conversion //************************************************************************************************* @@ -271,7 +284,7 @@ int main(int argc, char* argv[]) cout << "This parser reads a .vqm file and converts it to .blif format.\n\n" ; //verify command-line is correct, populate input variables and global mode flags. - cmd_line_parse(argc, argv, &source_file, &arch_file, &out_file); + cmd_line_parse(argc, argv, &source_file, &arch_file, &out_file, &device,&hard_block_type_name_list); setup_lut_support_map (); //initialize LUT support for cleanup and elaborate functions @@ -369,8 +382,29 @@ int main(int argc, char* argv[]) //file contains cleaned data read from the .vqm structures. } } + + // only process the netlist for any custom hard blocks if the user provided valid hard block names + if (insert_custom_hard_blocks) + { + cout << "\n>> Identifying and instantiating custom hard blocks within the netlist.\n"; + processStart = clock(); + try + { + add_hard_blocks_to_netlist(my_module,&arch,&hard_block_type_name_list, arch_file, source_file, device); + } + catch(const vtr::VtrError& error) + { + VTR_LOG_ERROR("%s\n", ((std::string)error.what()).c_str()); + exit(1); + } + + processEnd = clock(); + + cout << "\n>> Custom hard block instantiations took " << (float)(processEnd - processStart)/CLOCKS_PER_SEC << " seconds.\n" ; + } + //Reorganize netlist data into structures conducive to .blif writing. if (verbose_mode){ cout << "\n>> Initializing BLIF model\n" ; @@ -428,8 +462,8 @@ int main(int argc, char* argv[]) // SETUP FUNCTIONS //============================================================================================ -void cmd_line_parse (int argc, char** argv, string* sourcefile, string* archfile, - string* outfile){ +void cmd_line_parse (int argc, char** argv, string* sourcefile, string* archfile, string* outfile, + string* device, std::vector* hard_block_type_name_list){ /* Interpret the command-line arguments, accepting the input files, output file, and various * mode settings from the user. * @@ -471,6 +505,10 @@ void cmd_line_parse (int argc, char** argv, string* sourcefile, string* archfile remove_const_nets = T_FALSE; print_unused_subckt_pins = T_FALSE; eblif_format = T_FALSE; + insert_custom_hard_blocks = T_FALSE; + + // temporary storage to hold hard block names from the argument list + std::string curr_hard_block_name; //Now read the command line to configure input variables. for (int i = 1; i < argc; i++){ @@ -591,6 +629,86 @@ void cmd_line_parse (int argc, char** argv, string* sourcefile, string* archfile case OT_EBLIF_FORMAT: eblif_format = T_TRUE; break; + case OT_INSERT_CUSTOM_HARD_BLOCKS: + // first check whether an accompanying hard block type name was supplied (user provides the names of all the various types of custom hard blocks within the design, essentially this is the module names in their HDL design) + if ( i+1 == argc ){ + // no hard block type name was supplied so throw an error + cout << "ERROR: Missing Hard Block Module Names.\n" ; + print_usage (T_TRUE); + } + + // now we loop through and process the following arguments + while (T_TRUE) + { + // first check whether the next argument is a new option or a supplied hard block type name + it = CmdTokens.find(argv[i+1]); + + // case where the next argument is a command line option + if (it != CmdTokens.end()) + { + // When we come here, we need to finish processing further command line arguments for hard block type names. Since we have a different command-line option. + + // case one below is when the user didnt provide any hard block type names and just provided the next command-line option + if (!insert_custom_hard_blocks) + { + // since no hard block type names were supplied, we throw an error + cout << "ERROR: Missing Hard Block Module Names.\n"; + print_usage (T_TRUE); + } + else + { + // the user already provided a legal hard block type name, so if we come here then the user simply just wanted to use another command line option, so we just leave this case statement. + break; + } + } + else + { + // when we are here, the user provided an argument which is potentially a valid hard block type name + curr_hard_block_name.assign(argv[i+1]); + + // check if the provided name is valid + verify_hard_block_type_name(curr_hard_block_name); + + // if the hard block type name was escaped by '\', then we need to remove the '\' character from the string (look at 'verify_hard_block_type_name' function for more info) + cleanup_hard_block_type_name(&curr_hard_block_name); + + // if we are here then the provided name was valid. + insert_custom_hard_blocks = T_TRUE; + + // check to see whether the user repeated a hard block type name + if ((std::find(hard_block_type_name_list->begin(), hard_block_type_name_list->end(), curr_hard_block_name)) == hard_block_type_name_list->end()) + { + hard_block_type_name_list->push_back(curr_hard_block_name); // add the hard block type name to the list + } + else + { + cout << "ERROR: Hard Block Module name '" << curr_hard_block_name << "' was repeated.\n"; + print_usage (T_TRUE); + } + + // update argument index to show that we have processed the current hard block type name successfully + i++; + } + + // we handle the case where the next argument is empty (if we continued then an exception will be thrown in the find function above) + if (i+1 == argc) + { + // at this point we would have read a vlid input. + // Safe to assume that the user just finished providing hard block names + break; + } + } + break; + case OT_DEVICE: + if ( i+1 == argc ){ + cout << "\nERROR: Missing device type.\n" ; + print_usage(T_TRUE); + } + //Store the next argument as the device type + device->assign((string)argv[i+1]); + verify_device(*device); // make sure we can support the provided device + i++; //Increment past the next argument + break; default: //Should never get here; unknown tokens aren't mapped. cout << "\nERROR: Token " << argv[i] << " mishandled.\n" ; @@ -643,6 +761,8 @@ void setup_tokens (tokmap* tokens){ tokens->insert(tokpair("-remove_const_nets", OT_REMOVE_CONST_NETS)); tokens->insert(tokpair("-include_unused_subckt_pins", OT_INCLUDE_UNUSED_SUBCKT_PINS)); tokens->insert(tokpair("-eblif_format", OT_EBLIF_FORMAT)); + tokens->insert(tokpair("-insert_custom_hard_blocks", OT_INSERT_CUSTOM_HARD_BLOCKS)); + tokens->insert(tokpair("-device", OT_DEVICE)); } //============================================================================================