blob: 8500eb4921b129eb43f8cf8b16d8b1da2bbe26b6 [file] [log] [blame]
/*
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sstream>
#include <vector>
#include "vtr_error.h"
#include "vtr_time.h"
#include "odin_ii.h"
#include "argparse.hpp"
#include "arch_util.h"
#include "odin_globals.h"
#include "odin_types.h"
#include "netlist_utils.h"
#include "arch_types.h"
#include "parse_making_ast.h"
#include "netlist_create_from_ast.h"
#include "ast_util.h"
#include "read_xml_config_file.h"
#include "read_xml_arch_file.h"
#include "partial_map.h"
#include "multipliers.h"
#include "netlist_check.h"
#include "read_blif.h"
#include "output_blif.h"
#include "netlist_cleanup.h"
#include "hard_blocks.h"
#include "memories.h"
#include "simulate_blif.h"
#include "netlist_visualizer.h"
#include "adders.h"
#include "subtractions.h"
#include "vtr_util.h"
#include "vtr_path.h"
#include "vtr_memory.h"
#define DEFAULT_OUTPUT "."
int current_parse_file = -1;
t_arch Arch;
global_args_t global_args;
std::vector<t_physical_tile_type> physical_tile_types;
std::vector<t_logical_block_type> logical_block_types;
int block_tag = -1;
ids default_net_type = WIRE;
enum ODIN_ERROR_CODE
{
SUCCESS,
ERROR_INITIALIZATION,
ERROR_PARSE_CONFIG,
ERROR_PARSE_ARGS,
ERROR_PARSE_ARCH,
ERROR_SYNTHESIS,
ERROR_PARSE_BLIF,
};
static ODIN_ERROR_CODE synthesize_verilog()
{
double elaboration_time = wall_time();
printf("--------------------------------------------------------------------\n");
printf("High-level synthesis Begin\n");
/* Perform any initialization routines here */
find_hard_multipliers();
find_hard_adders();
//find_hard_adders_for_sub();
register_hard_blocks();
module_names_to_idx = sc_new_string_cache();
/* parse to abstract syntax tree */
printf("Parser starting - we'll create an abstract syntax tree. Note this tree can be viewed using Grap Viz (see documentation)\n");
init_parser();
parse_to_ast();
/**
* Note that the entry point for ast optimzations is done per module with the
* function void next_parsed_verilog_file(ast_node_t *file_items_list)
*/
/* after the ast is made potentially do tagging for downstream links to verilog */
if (global_args.high_level_block.provenance() == argparse::Provenance::SPECIFIED)
add_tag_data();
/**
* Now that we have a parse tree (abstract syntax tree [ast]) of
* the Verilog we want to make into a netlist.
*/
printf("Converting AST into a Netlist. Note this netlist can be viewed using GraphViz (see documentation)\n");
create_netlist();
// Can't levelize yet since the large muxes can look like combinational loops when they're not
check_netlist(verilog_netlist);
//START ################# NETLIST OPTIMIZATION ############################
/* point for all netlist optimizations. */
printf("Performing Optimizations of the Netlist\n");
if(hard_multipliers)
{
/* Perform a splitting of the multipliers for hard block mults */
reduce_operations(verilog_netlist, MULTIPLY);
iterate_multipliers(verilog_netlist);
clean_multipliers();
}
if (sp_memory_list || dp_memory_list)
{
/* Perform a splitting of any hard block memories */
iterate_memories(verilog_netlist);
free_memory_lists();
}
if(hard_adders)
{
/* Perform a splitting of the adders for hard block add */
reduce_operations(verilog_netlist, ADD);
iterate_adders(verilog_netlist);
clean_adders();
/* Perform a splitting of the adders for hard block sub */
reduce_operations(verilog_netlist, MINUS);
iterate_adders_for_sub(verilog_netlist);
clean_adders_for_sub();
}
//END ################# NETLIST OPTIMIZATION ############################
if (configuration.output_netlist_graphs )
graphVizOutputNetlist(configuration.debug_output_path, "optimized", 1, verilog_netlist); /* Path is where we are */
/* point where we convert netlist to FPGA or other hardware target compatible format */
printf("Performing Partial Map to target device\n");
partial_map_top(verilog_netlist);
/* Find any unused logic in the netlist and remove it */
remove_unused_logic(verilog_netlist);
/**
* point for outputs. This includes soft and hard mapping all structures to the
* target format. Some of these could be considred optimizations
*/
printf("Outputting the netlist to the specified output format\n");
output_blif(global_args.output_file.value().c_str(), verilog_netlist);
module_names_to_idx = sc_free_string_cache(module_names_to_idx);
cleanup_parser();
elaboration_time = wall_time() - elaboration_time;
printf("Successful High-level synthesis by Odin\n\tBlif file available at %s\n\tRan in ",global_args.output_file.value().c_str());
print_time(elaboration_time);
printf("\n");
printf("--------------------------------------------------------------------\n");
report_mult_distribution();
report_add_distribution();
report_sub_distribution();
deregister_hard_blocks();
//cleanup netlist
free_netlist(verilog_netlist);
return SUCCESS;
}
netlist_t *start_odin_ii(int argc,char **argv)
{
try
{
/* Some initialization */
one_string = vtr::strdup(ONE_VCC_CNS);
zero_string = vtr::strdup(ZERO_GND_ZERO);
pad_string = vtr::strdup(ZERO_PAD_ZERO);
printf("--------------------------------------------------------------------\n");
printf("Welcome to ODIN II version 0.1 - the better High level synthesis tools++ targetting FPGAs (mainly VPR)\n");
printf("Email: jamieson.peter@gmail.com and ken@unb.ca for support issues\n\n");
}
catch(vtr::VtrError& vtr_error)
{
printf("Odin failed to initialize %s with exit code%d\n", vtr_error.what(), ERROR_INITIALIZATION);
exit(ERROR_INITIALIZATION);
}
try
{
/* Set up the global arguments to their default. */
set_default_config();
/* get the command line options */
get_options(argc, argv);
create_directory(configuration.debug_output_path);
}
catch(vtr::VtrError& vtr_error)
{
printf("Odin Failed Reading The command line arguments %s with exit code%d\n", vtr_error.what(), ERROR_PARSE_ARGS);
exit(ERROR_PARSE_ARGS);
}
catch(argparse::ArgParseError& arg_error)
{
printf("Odin Failed Reading The command line arguments %s with exit code%d\n", arg_error.what(), ERROR_PARSE_ARGS);
exit(ERROR_PARSE_ARGS);
}
/* read the confirguration file .. get options presets the config values just in case theyr'e not read in with config file */
if (global_args.config_file.provenance() == argparse::Provenance::SPECIFIED)
{
printf("Reading Configuration file\n");
try
{
read_config_file(global_args.config_file.value().c_str());
}
catch(vtr::VtrError& vtr_error)
{
printf("Odin Failed Reading Configuration file %s with exit code%d\n", vtr_error.what(), ERROR_PARSE_CONFIG);
exit(ERROR_PARSE_CONFIG);
}
}
/* read the FPGA architecture file */
if (global_args.arch_file.provenance() == argparse::Provenance::SPECIFIED)
{
printf("Reading FPGA Architecture file\n");
try
{
XmlReadArch(global_args.arch_file.value().c_str(), false, &Arch, physical_tile_types, logical_block_types);
}
catch(vtr::VtrError& vtr_error)
{
printf("Odin Failed to load architecture file: %s with exit code%d\n", vtr_error.what(), ERROR_PARSE_ARCH);
exit(ERROR_PARSE_ARCH);
}
}
/* do High level Synthesis */
if (global_args.blif_file.provenance() != argparse::Provenance::SPECIFIED)
{
ODIN_ERROR_CODE error_code = synthesize_verilog();
if(error_code)
{
printf("Odin Failed to parse Verilog with exit status: %d\n", error_code);
exit(error_code);
}
}
/*************************************************************
* begin simulation section
*/
netlist_t *odin_netlist = NULL;
if(global_args.blif_file.provenance() == argparse::Provenance::SPECIFIED
|| global_args.interactive_simulation
|| global_args.sim_num_test_vectors
|| global_args.sim_vector_input_file.provenance() == argparse::Provenance::SPECIFIED)
{
// if we started with a verilog file read the output that was made since
// the simulator can only simulate blifs
if(global_args.blif_file.provenance() != argparse::Provenance::SPECIFIED)
{
configuration.list_of_file_names = { global_args.output_file };
current_parse_file =0;
}
try
{
odin_netlist = read_blif();
}
catch(vtr::VtrError& vtr_error)
{
printf("Odin Failed to load blif file: %s with exit code:%d \n", vtr_error.what(), ERROR_PARSE_BLIF);
exit(ERROR_PARSE_BLIF);
}
}
/* Simulate netlist */
if(odin_netlist && !global_args.interactive_simulation
&& (global_args.sim_num_test_vectors || (global_args.sim_vector_input_file.provenance() == argparse::Provenance::SPECIFIED) ))
{
printf("Netlist Simulation Begin\n");
create_directory(global_args.sim_directory);
simulate_netlist(odin_netlist);
}
printf("--------------------------------------------------------------------\n");
printf("Odin ran with exit status: %d\n", SUCCESS);
return odin_netlist;
}
int terminate_odin_ii(netlist_t *odin_netlist)
{
free_netlist(odin_netlist);
//Clean-up
free_arch(&Arch);
free_type_descriptors(logical_block_types);
free_type_descriptors(physical_tile_types);
return 0;
}
struct ParseInitRegState {
int from_str(std::string str)
{
if (str == "0") return 0;
else if (str == "1") return 1;
else if (str == "X") return -1;
std::stringstream msg;
msg << "Invalid conversion from '" << str << "' (expected one of: " << argparse::join(default_choices(), ", ") << ")";
throw argparse::ArgParseConversionError(msg.str());
}
std::string to_str(int val)
{
if (val == 0) return "0";
else if (val == 1) return "1";
else if (val == -1) return "X";
std::stringstream msg;
msg << "Invalid conversion from " << val;
throw argparse::ArgParseConversionError(msg.str());
}
std::vector<std::string> default_choices()
{
return {"0", "1", "X"};
}
};
/*---------------------------------------------------------------------------------------------
* (function: get_options)
*-------------------------------------------------------------------------*/
void get_options(int argc, char** argv) {
auto parser = argparse::ArgumentParser(argv[0]);
global_args.program_name = parser.prog();
auto& input_grp = parser.add_argument_group("input files");
input_grp.add_argument(global_args.config_file, "-c")
.help("Configuration file")
.metavar("XML_CONFIGURATION_FILE")
;
input_grp.add_argument(global_args.verilog_files, "-V")
.help("list of Verilog HDL file")
.nargs('+')
.metavar("VERILOG_FILE")
;
input_grp.add_argument(global_args.blif_file, "-b")
.help("BLIF file")
.metavar("BLIF_FILE")
;
auto& output_grp = parser.add_argument_group("output files");
output_grp.add_argument(global_args.output_file, "-o")
.help("Output file path")
.default_value("default_out.blif")
.metavar("OUTPUT_FILE_PATH")
;
auto& other_grp = parser.add_argument_group("other options");
other_grp.add_argument(global_args.show_help, "-h")
.help("Display this help message")
.action(argparse::Action::HELP)
;
other_grp.add_argument(global_args.arch_file, "-a")
.help("VTR FPGA architecture description file (XML)")
.metavar("ARCHITECTURE_FILE")
;
other_grp.add_argument(global_args.permissive, "--permissive")
.help("Turn possible_error_messages into warning_messages ... unexpected behaviour may occur")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_grp.add_argument(global_args.write_netlist_as_dot, "-G")
.help("Output netlist graph in graphviz .dot format")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_grp.add_argument(global_args.write_ast_as_dot, "-A")
.help("Output AST graph in graphviz .dot format")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_grp.add_argument(global_args.all_warnings, "-W")
.help("Print all warnings (can be substantial)")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_grp.add_argument(global_args.adder_def, "--adder_type")
.help("DEPRECATED")
.default_value("N/A")
.metavar("N/A")
;
other_grp.add_argument(global_args.adder_cin_global, "--adder_cin_global")
.help("Defines if the first cin of an adder/subtractor is connected to a global gnd/vdd instead of a dummy adder generating a gnd/vdd.")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_grp.add_argument(global_args.top_level_module_name, "--top_module")
.help("Allow to overwrite the top level module that odin would use")
.metavar("TOP_LEVEL_MODULE_NAME")
;
auto& rand_sim_grp = parser.add_argument_group("random simulation options");
rand_sim_grp.add_argument(global_args.sim_num_test_vectors, "-g")
.help("Number of random test vectors to generate")
.metavar("NUM_VECTORS")
;
rand_sim_grp.add_argument(global_args.sim_min_coverage, "--coverage")
.help("using the g argument we will simulate in blocks until a certain coverage is attained")
.metavar("MIN_COVERAGE")
;
rand_sim_grp.add_argument(global_args.sim_achieve_best, "--best_coverage")
.help("using the g argument we will simulate in blocks until best coverage is attained")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
rand_sim_grp.add_argument(global_args.sim_random_seed, "-r")
.help("Random seed")
.default_value("0")
.metavar("SEED")
;
rand_sim_grp.add_argument(global_args.sim_hold_low, "-L")
.help("list of primary inputs to hold high at cycle 0, and low for all subsequent cycles")
.nargs('+')
.metavar("PRIMARY_INPUTS")
;
rand_sim_grp.add_argument(global_args.sim_hold_high, "-H")
.help("list of primary inputs to hold low at cycle 0, and high for all subsequent cycles")
.nargs('+')
.metavar("PRIMARY_INPUTS")
;
auto& vec_sim_grp = parser.add_argument_group("vector simulation options");
vec_sim_grp.add_argument(global_args.sim_vector_input_file, "-t")
.help("File of predefined input vectors to simulate")
.metavar("INPUT_VECTOR_FILE")
;
vec_sim_grp.add_argument(global_args.sim_vector_output_file, "-T")
.help("File of predefined output vectors to check against simulation")
.metavar("OUTPUT_VECTOR_FILE")
;
auto& other_sim_grp = parser.add_argument_group("other simulation options");
other_sim_grp.add_argument(global_args.parralelized_simulation, "-j")
.help("Number of threads allowed for simulator to use")
.default_value("1")
.metavar("PARALEL NODE COUNT")
;
other_sim_grp.add_argument(global_args.parralelized_simulation_in_batch, "--batch")
.help("DEPRECATED")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
.metavar("BATCH FLAG")
;
other_sim_grp.add_argument(global_args.sim_directory, "-sim_dir")
.help("Directory output for simulation")
.default_value(DEFAULT_OUTPUT)
.metavar("SIMULATION_DIRECTORY")
;
other_sim_grp.add_argument(global_args.sim_generate_three_valued_logic, "-3")
.help("Generate three valued logic, instead of binary")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_sim_grp.add_argument(global_args.interactive_simulation, "--interractive_simulation")
.help("prevent Odin from freeing element so that application leveraging the simulator can use the nodes")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_sim_grp.add_argument<int,ParseInitRegState>(global_args.sim_initial_value, "-U")
.help("Default initial register state")
.default_value("X")
.metavar("INIT_REG_STATE")
;
other_sim_grp.add_argument(global_args.sim_output_both_edges, "-E")
.help("Output after both edges of the clock (This is by default)")
.default_value("true")
.action(argparse::Action::STORE_TRUE)
;
other_sim_grp.add_argument(global_args.sim_output_both_edges, "-R")
.help("DEPRECATED Output after rising edges of the clock only (Default after both edges)")
.default_value("true")
.action(argparse::Action::STORE_TRUE)
;
other_sim_grp.add_argument(global_args.read_mif_input, "--read_mif")
.help("look for a mif file to read")
.default_value("false")
.action(argparse::Action::STORE_TRUE)
;
other_sim_grp.add_argument(global_args.sim_additional_pins, "-p")
.help("list of additional pins/nodes to monitor during simulation.\n"
"Eg: \"-p input~0 input~1\" monitors pin 0 and 1 of input, \n"
" or \"-p input\" monitors all pins of input as a single port. \n"
" or \"-p input~\" monitors all pins of input as separate ports. (split) \n"
"- Note: Non-existent pins are ignored. \n"
"- Matching is done via strstr so general strings will match \n"
" all similar pins and nodes.\n"
" (Eg: FF_NODE will create a single port with all flipflops) \n")
.nargs('+')
.metavar("PINS_TO_MONITOR")
;
parser.parse_args(argc, argv);
//Check required options
if(!only_one_is_true({
global_args.config_file.provenance() == argparse::Provenance::SPECIFIED, //have a config file
global_args.blif_file.provenance() == argparse::Provenance::SPECIFIED, //have a blif file
global_args.verilog_files.value().size() > 0 //have a verilog input list
})){
parser.print_usage();
error_message(ARG_ERROR,0,-1, "%s", "Must include only one of either:\n\ta config file(-c)\n\ta blif file(-b)\n\ta verilog file(-V)\n");
}
//adjust thread count
int thread_requested = global_args.parralelized_simulation;
int max_thread = std::thread::hardware_concurrency();
global_args.parralelized_simulation.set(
std::max(1, std::min( thread_requested, std::min( (CONCURENCY_LIMIT-1) , max_thread )))
,argparse::Provenance::SPECIFIED
);
//Allow some config values to be overriden from command line
if (!global_args.verilog_files.value().empty())
{
//parse comma separated list of verilog files
configuration.list_of_file_names = global_args.verilog_files.value();
}
else if(global_args.blif_file.provenance() == argparse::Provenance::SPECIFIED)
{
configuration.list_of_file_names = { std::string(global_args.blif_file) };
}
if (global_args.arch_file.provenance() == argparse::Provenance::SPECIFIED) {
configuration.arch_file = global_args.arch_file;
}
if (global_args.write_netlist_as_dot.provenance() == argparse::Provenance::SPECIFIED) {
configuration.output_netlist_graphs = global_args.write_netlist_as_dot;
}
if (global_args.write_ast_as_dot.provenance() == argparse::Provenance::SPECIFIED) {
configuration.output_ast_graphs = global_args.write_ast_as_dot;
}
if (global_args.adder_cin_global.provenance() == argparse::Provenance::SPECIFIED) {
configuration.adder_cin_global = global_args.adder_cin_global;
}
if(global_args.sim_directory.value() == DEFAULT_OUTPUT)
{
global_args.sim_directory.set(configuration.debug_output_path, argparse::Provenance::SPECIFIED);
}
if(configuration.debug_output_path == DEFAULT_OUTPUT)
{
configuration.debug_output_path = global_args.sim_directory;
}
if(global_args.permissive.value())
{
warning_message(ARG_ERROR,-1,-1, "%s", "Permissive flag is ON. Undefined behaviour may occur\n");
}
}
/*---------------------------------------------------------------------------
* (function: set_default_options)
*-------------------------------------------------------------------------*/
void set_default_config()
{
/* Set up the global configuration. */
configuration.output_type = std::string("blif");
configuration.output_ast_graphs = 0;
configuration.output_netlist_graphs = 0;
configuration.print_parse_tokens = 0;
configuration.output_preproc_source = 0; // TODO: unused
configuration.debug_output_path = std::string(DEFAULT_OUTPUT);
configuration.arch_file = "";
configuration.fixed_hard_multiplier = 0;
configuration.split_hard_multiplier = 0;
configuration.split_memory_width = 0;
configuration.split_memory_depth = 0;
configuration.adder_cin_global = false;
/*
* Soft logic cutoffs. If a memory or a memory resulting from a split
* has a width AND depth below these, it will be converted to soft logic.
*/
configuration.soft_logic_memory_width_threshold = 0;
configuration.soft_logic_memory_depth_threshold = 0;
}