diff --git a/openfpga/src/fpga_verilog/verilog_constants.h b/openfpga/src/fpga_verilog/verilog_constants.h index e355b7a5f..c8a01d375 100644 --- a/openfpga/src/fpga_verilog/verilog_constants.h +++ b/openfpga/src/fpga_verilog/verilog_constants.h @@ -48,4 +48,10 @@ constexpr char* SB_VERILOG_FILE_NAME_PREFIX = "sb_"; constexpr char* LOGICAL_MODULE_VERILOG_FILE_NAME_PREFIX = "logical_tile_"; constexpr char* GRID_VERILOG_FILE_NAME_PREFIX = "grid_"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_POSTFIX = "_top_formal_verification"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX = "_fm"; +constexpr char* FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME = "U0_formal_verification"; + +#define VERILOG_DEFAULT_SIGNAL_INIT_VALUE 0 + #endif diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp new file mode 100644 index 000000000..e2bceb75d --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.cpp @@ -0,0 +1,442 @@ +/******************************************************************** + * This file includes functions that are used to generate + * a Verilog module of a pre-configured FPGA fabric + *******************************************************************/ +#include <fstream> + +/* Headers from vtrutil library */ +#include "vtr_assert.h" +#include "vtr_log.h" +#include "vtr_time.h" + +/* Headers from openfpgautil library */ +#include "openfpga_port.h" +#include "openfpga_digest.h" + +#include "bitstream_manager_utils.h" +#include "openfpga_atom_netlist_utils.h" + +#include "openfpga_naming.h" + +#include "verilog_constants.h" +#include "verilog_writer_utils.h" +#include "verilog_testbench_utils.h" +#include "verilog_preconfig_top_module.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/******************************************************************** + * Print module declaration and ports for the pre-configured + * FPGA top module + * The module ports do exactly match the input benchmark + *******************************************************************/ +static +void print_verilog_preconfig_top_module_ports(std::fstream& fp, + const std::string& circuit_name, + const AtomContext& atom_ctx) { + + /* Validate the file stream */ + valid_file_stream(fp); + + /* Module declaration */ + fp << "module " << circuit_name << std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX); + fp << " (" << std::endl; + + /* Add module ports */ + size_t port_counter = 0; + + /* Port type-to-type mapping */ + std::map<AtomBlockType, enum e_dump_verilog_port_type> port_type2type_map; + port_type2type_map[AtomBlockType::INPAD] = VERILOG_PORT_INPUT; + port_type2type_map[AtomBlockType::OUTPAD] = VERILOG_PORT_OUTPUT; + + /* Print all the I/Os of the circuit implementation to be tested*/ + for (const AtomBlockId& atom_blk : atom_ctx.nlist.blocks()) { + /* We only care I/O logical blocks !*/ + if ( (AtomBlockType::INPAD != atom_ctx.nlist.block_type(atom_blk)) + && (AtomBlockType::OUTPAD != atom_ctx.nlist.block_type(atom_blk)) ) { + continue; + } + if (0 < port_counter) { + fp << "," << std::endl; + } + /* Both input and output ports have only size of 1 */ + BasicPort module_port(std::string(atom_ctx.nlist.block_name(atom_blk) + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX)), 1); + fp << generate_verilog_port(port_type2type_map[atom_ctx.nlist.block_type(atom_blk)], module_port); + + /* Update port counter */ + port_counter++; + } + + fp << ");" << std::endl; + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Print internal wires for the pre-configured FPGA top module + * The internal wires are tailored for the ports of FPGA top module + * which will be different in various configuration protocols + *******************************************************************/ +static +void print_verilog_preconfig_top_module_internal_wires(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module) { + /* Validate the file stream */ + valid_file_stream(fp); + + /* Global ports of top-level module */ + print_verilog_comment(fp, std::string("----- Local wires for FPGA fabric -----")); + for (const ModulePortId& module_port_id : module_manager.module_ports(top_module)) { + BasicPort module_port = module_manager.module_port(top_module, module_port_id); + fp << generate_verilog_port(VERILOG_PORT_WIRE, module_port) << ";" << std::endl; + } + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Connect global ports of FPGA top module to constants except: + * 1. operating clock, which should be wired to the clock port of + * this pre-configured FPGA top module + *******************************************************************/ +static +void print_verilog_preconfig_top_module_connect_global_ports(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const CircuitLibrary& circuit_lib, + const std::vector<CircuitPortId>& global_ports, + const std::vector<std::string>& benchmark_clock_port_names) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin Connect Global ports of FPGA top module -----")); + + /* Global ports of the top module in module manager do not carry any attributes, + * such as is_clock, is_set, etc. + * Therefore, for each global port in the top module, we find the circuit port in the circuit library + * which share the same name. We can access to the attributes. + * To gurantee the correct link between global ports in module manager and those in circuit library + * We have performed some critical check in check_circuit_library() for global ports, + * where we guarantee all the global ports share the same name must have the same attributes. + * So that each global port with the same name is unique! + */ + for (const BasicPort& module_global_port : module_manager.module_ports_by_type(top_module, ModuleManager::MODULE_GLOBAL_PORT)) { + CircuitPortId linked_circuit_port_id = CircuitPortId::INVALID(); + /* Find the circuit port with the same name */ + for (const CircuitPortId& circuit_port_id : global_ports) { + if (0 != module_global_port.get_name().compare(circuit_lib.port_prefix(circuit_port_id))) { + continue; + } + linked_circuit_port_id = circuit_port_id; + break; + } + /* Must find one valid circuit port */ + VTR_ASSERT(CircuitPortId::INVALID() != linked_circuit_port_id); + /* Port size should match! */ + VTR_ASSERT(module_global_port.get_width() == circuit_lib.port_size(linked_circuit_port_id)); + /* Now, for operating clock port, we should wire it to the clock of benchmark! */ + if ( (CIRCUIT_MODEL_PORT_CLOCK == circuit_lib.port_type(linked_circuit_port_id)) + && (false == circuit_lib.port_is_prog(linked_circuit_port_id)) ) { + /* Wiring to each pin of the global port: benchmark clock is always 1-bit */ + for (const size_t& pin : module_global_port.pins()) { + for (const std::string& clock_port_name : benchmark_clock_port_names) { + BasicPort module_clock_pin(module_global_port.get_name(), pin, pin); + BasicPort benchmark_clock_pin(clock_port_name + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), 1); + print_verilog_wire_connection(fp, module_clock_pin, benchmark_clock_pin, false); + } + } + /* Finish, go to the next */ + continue; + } + + /* For other ports, give an default value */ + std::vector<size_t> default_values(module_global_port.get_width(), circuit_lib.port_default_value(linked_circuit_port_id)); + print_verilog_wire_constant_values(fp, module_global_port, default_values); + } + + print_verilog_comment(fp, std::string("----- End Connect Global ports of FPGA top module -----")); + + /* Add an empty line as a splitter */ + fp << std::endl; +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * This function uses 'assign' syntax to impost the bitstream at mem port + * while uses 'force' syntax to impost the bitstream at mem_inv port + *******************************************************************/ +static +void print_verilog_preconfig_top_module_assign_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin assign bitstream to configuration memories -----")); + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector<ConfigBlockId> block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + /* Wire it to the configuration bit: access both data out and data outb ports */ + std::vector<size_t> config_data_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_data_values.push_back(bitstream_manager.bit_value(config_bit)); + } + print_verilog_wire_constant_values(fp, config_data_port, config_data_values); + } + + fp << "initial begin" << std::endl; + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector<ConfigBlockId> block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + std::vector<size_t> config_datab_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_datab_values.push_back(!bitstream_manager.bit_value(config_bit)); + } + print_verilog_force_wire_constant_values(fp, config_datab_port, config_datab_values); + } + + fp << "end" << std::endl; + + print_verilog_comment(fp, std::string("----- End assign bitstream to configuration memories -----")); +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * This function uses '$deposit' syntax to do so + *******************************************************************/ +static +void print_verilog_preconfig_top_module_deposit_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + /* Validate the file stream */ + valid_file_stream(fp); + + print_verilog_comment(fp, std::string("----- Begin deposit bitstream to configuration memories -----")); + + fp << "initial begin" << std::endl; + + for (const ConfigBlockId& config_block_id : bitstream_manager.blocks()) { + /* We only cares blocks with configuration bits */ + if (0 == bitstream_manager.block_bits(config_block_id).size()) { + continue; + } + /* Build the hierarchical path of the configuration bit in modules */ + std::vector<ConfigBlockId> block_hierarchy = find_bitstream_manager_block_hierarchy(bitstream_manager, config_block_id); + /* Drop the first block, which is the top module, it should be replaced by the instance name here */ + /* Ensure that this is the module we want to drop! */ + VTR_ASSERT(0 == module_manager.module_name(top_module).compare(bitstream_manager.block_name(block_hierarchy[0]))); + block_hierarchy.erase(block_hierarchy.begin()); + /* Build the full hierarchy path */ + std::string bit_hierarchy_path(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME); + for (const ConfigBlockId& temp_block : block_hierarchy) { + bit_hierarchy_path += std::string("."); + bit_hierarchy_path += bitstream_manager.block_name(temp_block); + } + bit_hierarchy_path += std::string("."); + + /* Find the bit index in the parent block */ + BasicPort config_data_port(bit_hierarchy_path + generate_configuration_chain_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + BasicPort config_datab_port(bit_hierarchy_path + generate_configuration_chain_inverted_data_out_name(), + bitstream_manager.block_bits(config_block_id).size()); + + /* Wire it to the configuration bit: access both data out and data outb ports */ + std::vector<size_t> config_data_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_data_values.push_back(bitstream_manager.bit_value(config_bit)); + } + print_verilog_deposit_wire_constant_values(fp, config_data_port, config_data_values); + + std::vector<size_t> config_datab_values; + for (const ConfigBitId config_bit : bitstream_manager.block_bits(config_block_id)) { + config_datab_values.push_back(!bitstream_manager.bit_value(config_bit)); + } + print_verilog_deposit_wire_constant_values(fp, config_datab_port, config_datab_values); + } + + fp << "end" << std::endl; + + print_verilog_comment(fp, std::string("----- End deposit bitstream to configuration memories -----")); +} + +/******************************************************************** + * Impose the bitstream on the configuration memories + * We branch here for different simulators: + * 1. iVerilog Icarus prefers using 'assign' syntax to force the values + * 2. Mentor Modelsim prefers using '$deposit' syntax to do so + *******************************************************************/ +static +void print_verilog_preconfig_top_module_load_bitstream(std::fstream& fp, + const ModuleManager& module_manager, + const ModuleId& top_module, + const BitstreamManager& bitstream_manager) { + print_verilog_comment(fp, std::string("----- Begin load bitstream to configuration memories -----")); + + print_verilog_preprocessing_flag(fp, std::string(ICARUS_SIMULATOR_FLAG)); + + /* Use assign syntax for Icarus simulator */ + print_verilog_preconfig_top_module_assign_bitstream(fp, module_manager, top_module, bitstream_manager); + + fp << "`else" << std::endl; + + /* Use assign syntax for Icarus simulator */ + print_verilog_preconfig_top_module_deposit_bitstream(fp, module_manager, top_module, bitstream_manager); + + print_verilog_endif(fp); + + print_verilog_comment(fp, std::string("----- End load bitstream to configuration memories -----")); +} + + +/******************************************************************** + * Top-level function to generate a Verilog module of + * a pre-configured FPGA fabric. + * + * Pre-configured FPGA fabric + * +-------------------------------------------- + * | + * | FPGA fabric + * | +-------------------------------+ + * | | | + * | 0/1---->|FPGA global ports | + * | | | + * benchmark_clock----->|--------->|FPGA_clock | + * | | | + * benchmark_inputs---->|--------->|FPGA mapped I/Os | + * | | | + * benchmark_outputs<---|<---------|FPGA mapped I/Os | + * | | | + * | 0/1---->|FPGA unmapped I/Os | + * | | | + * fabric_bitstream---->|--------->|Internal_configuration_ports | + * | +-------------------------------+ + * | + * +------------------------------------------- + * + * Note: we do NOT put this module in the module manager. + * Because, it is not a standard module, where we force configuration signals + * This module is a wrapper for the FPGA fabric to be compatible in + * the port map of input benchmark. + * It includes wires to force constant values to part of FPGA datapath I/Os + * All these are hard to implement as a module in module manager + *******************************************************************/ +void print_verilog_preconfig_top_module(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const CircuitLibrary& circuit_lib, + const std::vector<CircuitPortId>& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir) { + std::string timer_message = std::string("Write pre-configured FPGA top-level Verilog netlist for design '") + circuit_name + std::string("'"); + + /* Start time count */ + vtr::ScopedStartFinishTimer timer(timer_message); + + /* Create the file stream */ + std::fstream fp; + fp.open(verilog_fname, std::fstream::out | std::fstream::trunc); + + /* Validate the file stream */ + check_file_stream(verilog_fname.c_str(), fp); + + /* Generate a brief description on the Verilog file*/ + std::string title = std::string("Verilog netlist for pre-configured FPGA fabric by design: ") + circuit_name; + print_verilog_file_header(fp, title); + + /* Print preprocessing flags and external netlists */ + print_verilog_include_defines_preproc_file(fp, verilog_dir); + + print_verilog_include_netlist(fp, std::string(verilog_dir + std::string(DEFINES_VERILOG_SIMULATION_FILE_NAME))); + + /* Print module declaration and ports */ + print_verilog_preconfig_top_module_ports(fp, circuit_name, atom_ctx); + + /* Find the top_module */ + ModuleId top_module = module_manager.find_module(generate_fpga_top_module_name()); + VTR_ASSERT(true == module_manager.valid_module_id(top_module)); + + /* Print internal wires */ + print_verilog_preconfig_top_module_internal_wires(fp, module_manager, top_module); + + /* Instanciate FPGA top-level module */ + print_verilog_testbench_fpga_instance(fp, module_manager, top_module, + std::string(FORMAL_VERIFICATION_TOP_MODULE_UUT_NAME)); + + /* Find clock ports in benchmark */ + std::vector<std::string> benchmark_clock_port_names = find_atom_netlist_clock_port_names(atom_ctx.nlist); + + /* Connect FPGA top module global ports to constant or benchmark global signals! */ + print_verilog_preconfig_top_module_connect_global_ports(fp, module_manager, top_module, + circuit_lib, global_ports, + benchmark_clock_port_names); + + /* Connect I/Os to benchmark I/Os or constant driver */ + print_verilog_testbench_connect_fpga_ios(fp, module_manager, top_module, + atom_ctx, place_ctx, io_location_map, + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + std::string(FORMAL_VERIFICATION_TOP_MODULE_PORT_POSTFIX), + (size_t)VERILOG_DEFAULT_SIGNAL_INIT_VALUE); + + /* Assign FPGA internal SRAM/Memory ports to bitstream values */ + print_verilog_preconfig_top_module_load_bitstream(fp, module_manager, top_module, + bitstream_manager); + + /* Testbench ends*/ + print_verilog_module_end(fp, std::string(circuit_name) + std::string(FORMAL_VERIFICATION_TOP_MODULE_POSTFIX)); + + /* Close the file stream */ + fp.close(); +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h new file mode 100644 index 000000000..0c8e54708 --- /dev/null +++ b/openfpga/src/fpga_verilog/verilog_preconfig_top_module.h @@ -0,0 +1,34 @@ +#ifndef VERILOG_PRECONFIG_TOP_MODULE_H +#define VERILOG_PRECONFIG_TOP_MODULE_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include <vector> +#include <string> +#include "circuit_types.h" +#include "vpr_types.h" +#include "module_manager.h" +#include "bitstream_manager.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +void print_verilog_preconfig_top_module(const ModuleManager& module_manager, + const BitstreamManager& bitstream_manager, + const CircuitLibrary& circuit_lib, + const std::vector<CircuitPortId>& global_ports, + const AtomContext& atom_ctx, + const PlacementContext& place_ctx, + const IoLocationMap& io_location_map, + const std::string& circuit_name, + const std::string& verilog_fname, + const std::string& verilog_dir); + +} /* end namespace openfpga */ + +#endif diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.cpp b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp new file mode 100644 index 000000000..d2786e88d --- /dev/null +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.cpp @@ -0,0 +1,34 @@ +/*************************************************************************************** + * This file includes most utilized functions that are used to acquire data from + * VPR atom netlist (users' netlist to implement) + ***************************************************************************************/ + +/* Headers from vtrutil library */ +#include "vtr_log.h" +#include "vtr_assert.h" +#include "vtr_time.h" + +/* Headers from vtrutil library */ +#include "atom_netlist_utils.h" + +#include "openfpga_atom_netlist_utils.h" + +/* begin namespace openfpga */ +namespace openfpga { + +/*************************************************************************************** + * Find the names of all the atom blocks that drive clock nets + ***************************************************************************************/ +std::vector<std::string> find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist) { + std::vector<std::string> clock_names; + + std::set<AtomPinId> clock_pins = find_netlist_logical_clock_drivers(atom_nlist); + for (const AtomPinId& clock_pin : clock_pins) { + const AtomBlockId& atom_blk = atom_nlist.port_block(atom_nlist.pin_port(clock_pin)); + clock_names.push_back(atom_nlist.block_name(atom_blk)); + } + + return clock_names; +} + +} /* end namespace openfpga */ diff --git a/openfpga/src/utils/openfpga_atom_netlist_utils.h b/openfpga/src/utils/openfpga_atom_netlist_utils.h new file mode 100644 index 000000000..180181687 --- /dev/null +++ b/openfpga/src/utils/openfpga_atom_netlist_utils.h @@ -0,0 +1,22 @@ +#ifndef OPENFPGA_ATOM_NETLIST_UTILS_H +#define OPENFPGA_ATOM_NETLIST_UTILS_H + +/******************************************************************** + * Include header files that are required by function declaration + *******************************************************************/ +#include <vector> +#include <string> +#include "atom_netlist.h" + +/******************************************************************** + * Function declaration + *******************************************************************/ + +/* begin namespace openfpga */ +namespace openfpga { + +std::vector<std::string> find_atom_netlist_clock_port_names(const AtomNetlist& atom_nlist); + +} /* end namespace openfpga */ + +#endif