def get_all_stream_if_stats(vcd_file, stream_ifs=None, sort_by="{'V': 1, 'R': 0}", num_workers=None): """Return a list of streaming interface stats, sorted by the percentage for the given sort_by key. If stream_ifs is None, all streaming interface stats will be returned, otherwise treated as a list of interface names to return the stats for. By default the number of parallel workers from the environment variable NUM_DEFAULT_WORKERS will be used. This behavior can be changed on a per call basis by supplying the optional parameter: num_workers """ if stream_ifs is None: stream_ifs = list_stream_if(vcd_file) if num_workers is None: num_workers = get_num_default_workers() with mp.Pool(num_workers) as p: stream_ifs = map(lambda x: (x, vcd_file), stream_ifs) all_stats = p.map(_get_stats, stream_ifs) def sort_key(x): stat = x[1] (samples, percent) = stat[sort_by] return percent ret = sorted(all_stats, key=sort_key) return ret
def __init__(self, num_workers=None): super().__init__() if num_workers is None: self._num_workers = get_num_default_workers() else: self._num_workers = num_workers assert self._num_workers >= 0, "Number of workers must be nonnegative." if self._num_workers == 0: self._num_workers = mp.cpu_count()
def get_all_fifo_count_max(vcd_file, fifo_count_signals=None): """Return a list of max FIFO counts. If fifo_count_signals is None, all FIFO count signals will be returned, otherwise treated as a list of signal names to return the stats for.""" if fifo_count_signals is None: fifo_count_signals = list_fifo_count_signals(vcd_file) with mp.Pool(get_num_default_workers()) as p: fifo_count_signals = map(lambda x: (x, vcd_file), fifo_count_signals) all_stats = p.map(_get_fifo_max, fifo_count_signals) return all_stats
def get_all_stream_if_stats(vcd_file, stream_ifs=None, sort_by="{'V': 1, 'R': 0}"): """Return a list of streaming interface stats, sorted by the percentage for the given sort_by key. If stream_ifs is None, all streamin interface stats will be returned, otherwise treated as a list of interface names to return the stats for.""" if stream_ifs is None: stream_ifs = list_stream_if(vcd_file) with mp.Pool(get_num_default_workers()) as p: stream_ifs = map(lambda x: (x, vcd_file), stream_ifs) all_stats = p.map(_get_stats, stream_ifs) def sort_key(x): stat = x[1] (samples, percent) = stat[sort_by] return percent ret = sorted(all_stats, key=sort_key) return ret
def apply(self, model): # ensure non-relative readmemh .dat files model = model.transform(ReplaceVerilogRelPaths()) ip_dirs = ["list"] # add RTL streamer IP ip_dirs.append("/workspace/finn/finn-rtllib/memstream") # ensure that all nodes are fpgadataflow, and that IPs are generated for node in model.graph.node: assert is_finn_op(node.domain), "Found non-FINN node" backend_attribute = get_by_name(node.attribute, "backend") assert backend_attribute is not None, "Backend node attribute is not set." backend_value = backend_attribute.s.decode("UTF-8") assert (backend_value == "fpgadataflow" ), """Backend node attribute is not set to "fpgadataflow".""" node_inst = getCustomOp(node) ip_dir_value = node_inst.get_nodeattr("ip_path") assert os.path.isdir( ip_dir_value), "IP generation directory doesn't exist." ip_dirs += [ip_dir_value] self.create_cmds += node_inst.code_generation_ipi() my_producer = model.find_producer(node.input[0]) self.connect_clk_rst(node) self.connect_axi(node) if my_producer is None: # first node in graph self.connect_s_axis_external(node) if node.op_type == "TLastMarker": assert (node_inst.get_nodeattr("Direction") == "in" ), """Output TLastMarker incorrect direction""" elif node.op_type == "IODMA" and len(model.graph.node) != 1: # don't apply this check for a 1-node partition assert (node_inst.get_nodeattr("direction") == "in" ), """Input DMA incorrect direction""" else: # intermediate node # wire up input(s) to previous node output(s) # foreach input # find producer # find index of producer output connected to our target input # get names of hdl interfaces for input and producer output # issue a TCL directive to connect input to output # if FC layer with mode "decoupled", add a streamer on input 1 for i in range(len(node.input)): producer = model.find_producer(node.input[i]) if producer is None: continue j = list(producer.output).index(node.input[i]) src_intf_name = getCustomOp( producer).get_verilog_top_module_intf_names( )["m_axis"][j] dst_intf_name = node_inst.get_verilog_top_module_intf_names( )["s_axis"][i] self.connect_cmds.append( "connect_bd_intf_net [get_bd_intf_pins %s/%s] " "[get_bd_intf_pins %s/%s]" % (producer.name, src_intf_name, node.name, dst_intf_name)) if model.find_consumers(node.output[0]) is None: # last node in graph self.connect_m_axis_external(node) if node.op_type == "TLastMarker": assert (node_inst.get_nodeattr("Direction") == "out" ), """Output TLastMarker incorrect direction""" elif node.op_type == "IODMA" and len(model.graph.node) != 1: assert (node_inst.get_nodeattr("direction") == "out" ), """Output DMA incorrect direction""" # create a temporary folder for the project prjname = "finn_vivado_stitch_proj" vivado_stitch_proj_dir = make_build_dir(prefix="vivado_stitch_proj_") model.set_metadata_prop("vivado_stitch_proj", vivado_stitch_proj_dir) # start building the tcl script tcl = [] # create vivado project tcl.append("create_project %s %s -part %s" % (prjname, vivado_stitch_proj_dir, self.fpgapart)) # add all the generated IP dirs to ip_repo_paths ip_dirs_str = " ".join(ip_dirs) tcl.append("set_property ip_repo_paths [%s] [current_project]" % ip_dirs_str) tcl.append("update_ip_catalog") # create block design and instantiate all layers block_name = self.ip_name tcl.append('create_bd_design "%s"' % block_name) tcl.extend(self.create_cmds) tcl.extend(self.connect_cmds) fclk_mhz = 1 / (self.clk_ns * 0.001) fclk_hz = fclk_mhz * 1000000 model.set_metadata_prop("clk_ns", str(self.clk_ns)) tcl.append("set_property CONFIG.FREQ_HZ %f [get_bd_ports /ap_clk]" % fclk_hz) tcl.append("regenerate_bd_layout") tcl.append("validate_bd_design") tcl.append("save_bd_design") # create wrapper hdl (for rtlsim later on) bd_base = "%s/%s.srcs/sources_1/bd/%s" % ( vivado_stitch_proj_dir, prjname, block_name, ) bd_filename = "%s/%s.bd" % (bd_base, block_name) tcl.append("make_wrapper -files [get_files %s] -top" % bd_filename) wrapper_filename = "%s/hdl/%s_wrapper.v" % (bd_base, block_name) tcl.append("add_files -norecurse %s" % wrapper_filename) model.set_metadata_prop("wrapper_filename", wrapper_filename) # synthesize to DCP and export stub, DCP and constraints if self.vitis: tcl.append( "set_property SYNTH_CHECKPOINT_MODE Hierarchical [ get_files %s ]" % bd_filename) tcl.append( "set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} " "-value {-mode out_of_context} -objects [get_runs synth_1]") num_workers = get_num_default_workers() assert num_workers >= 0, "Number of workers must be nonnegative." if num_workers == 0: num_workers = mp.cpu_count() tcl.append("launch_runs synth_1 -jobs %s" % str(num_workers)) tcl.append("wait_on_run [get_runs synth_1]") tcl.append("open_run synth_1 -name synth_1") tcl.append("write_verilog -force -mode synth_stub %s.v" % block_name) tcl.append("write_checkpoint %s.dcp" % block_name) tcl.append("write_xdc %s.xdc" % block_name) tcl.append("report_utilization -file %s_partition_util.rpt" % block_name) # export block design itself as an IP core block_vendor = "xilinx_finn" block_library = "finn" block_vlnv = "%s:%s:%s:1.0" % (block_vendor, block_library, block_name) model.set_metadata_prop("vivado_stitch_vlnv", block_vlnv) model.set_metadata_prop("vivado_stitch_ifnames", str(self.intf_names)) tcl.append( ("ipx::package_project -root_dir %s/ip -vendor %s " "-library %s -taxonomy /UserIP -module %s -import_files") % (vivado_stitch_proj_dir, block_vendor, block_library, block_name)) tcl.append("set_property core_revision 2 [ipx::find_open_core %s]" % block_vlnv) tcl.append("ipx::create_xgui_files [ipx::find_open_core %s]" % block_vlnv) # if targeting Vitis, add some properties to the IP if self.vitis: tcl.append( "ipx::remove_bus_parameter FREQ_HZ " "[ipx::get_bus_interfaces CLK.AP_CLK -of_objects [ipx::current_core]]" ) # replace source code with dcp tcl.append( "set_property sdx_kernel true [ipx::find_open_core %s]" % block_vlnv) tcl.append( "set_property sdx_kernel_type rtl [ipx::find_open_core %s]" % block_vlnv) tcl.append( "set_property supported_families { } [ipx::find_open_core %s]" % block_vlnv) tcl.append( "set_property xpm_libraries {XPM_CDC XPM_MEMORY XPM_FIFO} " "[ipx::find_open_core %s]" % block_vlnv) tcl.append("set_property auto_family_support_level level_2 " "[ipx::find_open_core %s]" % block_vlnv) # remove all files from synthesis and sim groups # we'll replace with DCP, stub, and xdc tcl.append( "ipx::remove_all_file " "[ipx::get_file_groups xilinx_anylanguagebehavioralsimulation]" ) tcl.append("ipx::remove_all_file " "[ipx::get_file_groups xilinx_anylanguagesynthesis]") tcl.append( "ipx::remove_file_group " "xilinx_anylanguagebehavioralsimulation [ipx::current_core]") tcl.append("ipx::remove_file_group " "xilinx_anylanguagesynthesis [ipx::current_core]") # remove sim and src folders tcl.append("file delete -force %s/ip/sim" % vivado_stitch_proj_dir) tcl.append("file delete -force %s/ip/src" % vivado_stitch_proj_dir) # copy and add DCP, stub, and xdc tcl.append("file mkdir %s/ip/dcp" % vivado_stitch_proj_dir) tcl.append("file mkdir %s/ip/impl" % vivado_stitch_proj_dir) tcl.append("file copy -force %s.dcp %s/ip/dcp" % (block_name, vivado_stitch_proj_dir)) tcl.append("file copy -force %s.xdc %s/ip/impl" % (block_name, vivado_stitch_proj_dir)) tcl.append( "ipx::add_file_group xilinx_implementation [ipx::current_core]" ) tcl.append( "ipx::add_file impl/%s.xdc [ipx::get_file_groups xilinx_implementation]" % block_name) tcl.append( "set_property used_in [list implementation] " "[ipx::get_files impl/%s.xdc " "-of_objects [ipx::get_file_groups xilinx_implementation]]" % block_name) tcl.append("ipx::add_file_group " "xilinx_synthesischeckpoint [ipx::current_core]") tcl.append("ipx::add_file dcp/%s.dcp " "[ipx::get_file_groups xilinx_synthesischeckpoint]" % block_name) tcl.append( "ipx::add_file_group xilinx_simulationcheckpoint [ipx::current_core]" ) tcl.append("ipx::add_file dcp/%s.dcp " "[ipx::get_file_groups xilinx_simulationcheckpoint]" % block_name) tcl.append("ipx::update_checksums [ipx::find_open_core %s]" % block_vlnv) tcl.append("ipx::save_core [ipx::find_open_core %s]" % block_vlnv) # export list of used Verilog files (for rtlsim later on) tcl.append( "set all_v_files [get_files -filter {FILE_TYPE == Verilog " + "&& USED_IN_SYNTHESIS == 1} ]") v_file_list = "%s/all_verilog_srcs.txt" % vivado_stitch_proj_dir tcl.append("set fp [open %s w]" % v_file_list) # write each verilog filename to all_verilog_srcs.txt tcl.append("foreach vf $all_v_files {puts $fp $vf}") tcl.append("close $fp") # write the project creator tcl script tcl_string = "\n".join(tcl) + "\n" with open(vivado_stitch_proj_dir + "/make_project.tcl", "w") as f: f.write(tcl_string) # create a shell script and call Vivado make_project_sh = vivado_stitch_proj_dir + "/make_project.sh" working_dir = os.environ["PWD"] with open(make_project_sh, "w") as f: f.write("#!/bin/bash \n") f.write("cd {}\n".format(vivado_stitch_proj_dir)) f.write("vivado -mode batch -source make_project.tcl\n") f.write("cd {}\n".format(working_dir)) bash_command = ["bash", make_project_sh] process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) process_compile.communicate() return (model, False)
def apply(self, model): # create a config file and empty list of xo files config = [] idma_idx = 0 odma_idx = 0 aximm_idx = 0 axilite_idx = 0 global_clk_ns = 0 instance_names = {} for node in model.graph.node: assert node.op_type == "StreamingDataflowPartition", "Invalid link graph" sdp_node = getCustomOp(node) dataflow_model_filename = sdp_node.get_nodeattr("model") kernel_model = ModelWrapper(dataflow_model_filename) ipstitch_path = kernel_model.get_metadata_prop( "vivado_stitch_proj") if ipstitch_path is None or (not os.path.isdir(ipstitch_path)): raise Exception( "No stitched IPI design found for %s, apply CreateStitchedIP first." % node.name) vivado_stitch_vlnv = kernel_model.get_metadata_prop( "vivado_stitch_vlnv") if vivado_stitch_vlnv is None: raise Exception( "No vlnv found for %s, apply CreateStitchedIP first." % node.name) ip_dirs = ["list"] ip_dirs += collect_ip_dirs(kernel_model, ipstitch_path) ip_dirs_str = "[%s]" % (" ".join(ip_dirs)) config.append( "set_property ip_repo_paths " "[concat [get_property ip_repo_paths [current_project]] %s] " "[current_project]" % ip_dirs_str) config.append("update_ip_catalog -rebuild -scan_changes") # get metadata property clk_ns to calculate clock frequency clk_ns = float(kernel_model.get_metadata_prop("clk_ns")) if clk_ns > global_clk_ns: global_clk_ns = clk_ns # gather info on connectivity # assume each node connected to outputs/inputs is DMA: # has axis, aximm and axilite # everything else is axis-only # assume only one connection from each ip to the next # all aximm allocated to DDR[0] # all kernels allocated to SLR0 producer = model.find_producer(node.input[0]) consumer = model.find_consumers(node.output[0]) # define kernel instances # name kernels connected to graph inputs as idmaxx # name kernels connected to graph inputs as odmaxx if producer is None or consumer is None: if producer is None: instance_names[node.name] = "idma" + str(idma_idx) elif consumer is None: instance_names[node.name] = "odma" + str(odma_idx) config.append("create_bd_cell -type ip -vlnv %s %s" % (vivado_stitch_vlnv, instance_names[node.name])) config.append( "connect_bd_intf_net [get_bd_intf_pins %s/m_axi_gmem0] " "[get_bd_intf_pins smartconnect_0/S%02d_AXI]" % (instance_names[node.name], aximm_idx)) config.append( "connect_bd_intf_net [get_bd_intf_pins %s/s_axi_control] " "[get_bd_intf_pins axi_interconnect_0/M%02d_AXI]" % (instance_names[node.name], axilite_idx)) idma_idx += 1 aximm_idx += 1 axilite_idx += 1 else: instance_names[node.name] = node.name config.append("create_bd_cell -type ip -vlnv %s %s" % (vivado_stitch_vlnv, instance_names[node.name])) config.append("connect_bd_net [get_bd_pins %s/ap_clk] " "[get_bd_pins smartconnect_0/aclk]" % instance_names[node.name]) config.append("connect_bd_net [get_bd_pins %s/ap_rst_n] " "[get_bd_pins smartconnect_0/aresetn]" % instance_names[node.name]) # connect streams if producer is not None: for i in range(len(node.input)): producer = model.find_producer(node.input[i]) if producer is not None: j = list(producer.output).index(node.input[i]) config.append( "connect_bd_intf_net [get_bd_intf_pins %s/s_axis_%d] " "[get_bd_intf_pins %s/m_axis_%d]" % ( instance_names[node.name], i, instance_names[producer.name], j, )) # create a temporary folder for the project vivado_pynq_proj_dir = make_build_dir(prefix="vivado_zynq_proj_") model.set_metadata_prop("vivado_pynq_proj", vivado_pynq_proj_dir) fclk_mhz = int(1 / (global_clk_ns * 0.001)) # create a TCL recipe for the project ipcfg = vivado_pynq_proj_dir + "/ip_config.tcl" config = "\n".join(config) + "\n" with open(ipcfg, "w") as f: f.write(templates.custom_zynq_shell_template % ( fclk_mhz, axilite_idx, aximm_idx, self.platform, pynq_part_map[self.platform], config, self.enable_debug, get_num_default_workers(), )) # create a TCL recipe for the project synth_project_sh = vivado_pynq_proj_dir + "/synth_project.sh" working_dir = os.environ["PWD"] with open(synth_project_sh, "w") as f: f.write("#!/bin/bash \n") f.write("cd {}\n".format(vivado_pynq_proj_dir)) f.write("vivado -mode tcl -source %s\n" % ipcfg) f.write("cd {}\n".format(working_dir)) # call the synthesis script bash_command = ["bash", synth_project_sh] process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE) process_compile.communicate() bitfile_name = (vivado_pynq_proj_dir + "/finn_zynq_link.runs/impl_1/top_wrapper.bit") if not os.path.isfile(bitfile_name): raise Exception("Synthesis failed, no bitfile found") deploy_bitfile_name = vivado_pynq_proj_dir + "/resizer.bit" copy(bitfile_name, deploy_bitfile_name) # set bitfile attribute model.set_metadata_prop("bitfile", deploy_bitfile_name) hwh_name = (vivado_pynq_proj_dir + "/finn_zynq_link.srcs/sources_1/bd/top/hw_handoff/top.hwh") if not os.path.isfile(hwh_name): raise Exception("Synthesis failed, no hardware handoff file found") deploy_hwh_name = vivado_pynq_proj_dir + "/resizer.hwh" copy(hwh_name, deploy_hwh_name) model.set_metadata_prop("hw_handoff", deploy_hwh_name) # filename for the synth utilization report synth_report_filename = vivado_pynq_proj_dir + "/synth_report.xml" model.set_metadata_prop("vivado_synth_rpt", synth_report_filename) return (model, False)