Example #1
0
def step_resnet50_set_fifo_depths(model: ModelWrapper,
                                  cfg: DataflowBuildConfig):
    """
    Depending on the auto_fifo_depths setting, do one of the following:
    * if auto_fifo_depths=True:  Run the `InsertAndSetFIFODepths` transformation
    to attempt to determine the FIFO sizes that provide full throughput. Involves
    running stitched-IP rtlsim and may take a long time.
    * if auto_fifo_depths=False:  Assume the folding config file contains FIFO
    sizes as well. Runs the `InsertFIFO` transformation, then
    `ApplyConfig(cfg.folding_config_file)`, and finally `RemoveShallowFIFOs`.
    Coherency with config file node naming is ensured by calling
    `GiveUniqueNodeNames`.
    """

    if cfg.auto_fifo_depths:
        model = model.transform(
            InsertAndSetFIFODepths(
                cfg._resolve_fpga_part(),
                cfg._resolve_hls_clk_period(),
                vivado_ram_style=cfg.large_fifo_mem_style.value,
            ))
    else:
        # assume folding cfg json contains FIFO sizes too
        # insert DWCs, FIFOs and run ApplyConfig once more
        model = model.transform(InsertDWC())
        # need to make sure all FIFOs are created so that their depth can be
        # set by ApplyConfig, so create_shallow_fifos=True
        model = model.transform(InsertFIFO(create_shallow_fifos=True))
        model = model.transform(GiveUniqueNodeNames())
        model = model.transform(GiveReadableTensorNames())
        if cfg.folding_config_file is not None:
            model = model.transform(ApplyConfig(cfg.folding_config_file))
        # remove any shallow FIFOs
        model = model.transform(RemoveShallowFIFOs())

    # extract the final configuration and save it as json
    hw_attrs = [
        "PE",
        "SIMD",
        "ram_style",
        "depth",
        "impl_style",
        "resType",
        "mem_mode",
        "runtime_writeable_weights",
    ]
    extract_model_config_to_json(model,
                                 cfg.output_dir + "/final_hw_config.json",
                                 hw_attrs)

    # after FIFOs are ready to go, call PrepareIP and HLSSynthIP again
    # this will only run for the new nodes (e.g. FIFOs and DWCs)
    model = model.transform(
        PrepareIP(cfg._resolve_fpga_part(), cfg._resolve_hls_clk_period()))
    model = model.transform(HLSSynthIP())
    model = model.transform(ReplaceVerilogRelPaths())
    return model
def prep_model_for_rtlsim(model, src_dir="/tmp/finn_dev_justin"):
    # Make copies of all IP Cores, so that ReplaceVerilogRelPaths is not a permanent change
    randstring = get_rand_string(10)
    copy_dir = "/tmp/finn_dev_justin/rtlsim_" + randstring
    print(f"Copying IP to folder {copy_dir}")
    model = copy_ip(model, copy_dir, src_dir)
    model = model.transform(ReplaceVerilogRelPaths())
    model = model.transform(SetExecMode("rtlsim"))
    model = model.transform(PrepareRTLSim())
    return model
def step_hls_ipgen(model: ModelWrapper, cfg: DataflowBuildConfig):
    """Run Vivado HLS synthesis on generated code for HLSCustomOp nodes,
    in order to generate IP blocks."""

    model = model.transform(HLSSynthIP())
    model = model.transform(ReplaceVerilogRelPaths())
    report_dir = cfg.output_dir + "/report"
    os.makedirs(report_dir, exist_ok=True)
    estimate_layer_resources_hls = model.analysis(hls_synth_res_estimation)
    with open(report_dir + "/estimate_layer_resources_hls.json", "w") as f:
        json.dump(estimate_layer_resources_hls, f, indent=2)
    return model
Example #4
0
def test_end2end_mobilenet_gen_hls_ip():
    model = load_test_checkpoint_or_skip(
        build_dir + "/end2end_mobilenet_dataflow_model.onnx")
    start = time.time()
    model = model.transform(PrepareIP(test_fpga_part, target_clk_ns))
    model = model.transform(HLSSynthIP())
    model = model.transform(ReplaceVerilogRelPaths())
    end = time.time()
    elapsed_time = end - start
    f = open(build_dir + "/end2end_mobilenet_ipgen_time.txt", "w+")
    f.write("Execution time in seconds: " + str(elapsed_time))
    f.close()

    model = model.transform(AnnotateResources("hls"))
    model.save(build_dir + "/end2end_mobilenet_ipgen.onnx")
Example #5
0
def test_fpgadataflow_fifo_rtlsim(Shape, folded_shape, depth, finn_dtype):

    # generate input data
    x = gen_finn_dt_tensor(finn_dtype, Shape)
    input_dict = prepare_inputs(x, finn_dtype)

    model = make_single_fifo_modelwrapper(Shape, depth, folded_shape, finn_dtype)

    model = model.transform(SetExecMode("rtlsim"))
    model = model.transform(InsertTLastMarker())
    model = model.transform(GiveUniqueNodeNames())
    model = model.transform(PrepareIP(test_fpga_part, target_clk_ns))
    model = model.transform(HLSSynthIP())
    model = model.transform(PrepareRTLSim())
    y = oxe.execute_onnx(model, input_dict)["outp"]
    assert (
        y == x
    ).all(), """The output values are not the same as the
       input values anymore."""
    assert y.shape == tuple(Shape), """The output shape is incorrect."""

    model = model.transform(ReplaceVerilogRelPaths())
    model = model.transform(CreateStitchedIP(test_fpga_part))
    model = model.transform(MakePYNQProject(test_pynq_board))
    model = model.transform(SynthPYNQProject())
    model = model.transform(MakePYNQDriver())
    ip = os.environ["PYNQ_IP"]
    username = os.getenv("PYNQ_USERNAME", "xilinx")
    password = os.getenv("PYNQ_PASSWORD", "xilinx")
    port = os.getenv("PYNQ_PORT", 22)
    target_dir = os.getenv("PYNQ_TARGET_DIR", "/home/xilinx/finn")
    model = model.transform(DeployToPYNQ(ip, port, username, password, target_dir))

    res = throughput_test(model)
    expected_dict = {}
    expected_dict["runtime[ms]"] = []
    expected_dict["throughput[images/s]"] = []
    expected_dict["DRAM_in_bandwidth[Mb/s]"] = []
    expected_dict["DRAM_out_bandwidth[Mb/s]"] = []
    for key in expected_dict:
        assert (
            key in res
        ), """Throughput test not successful, no value for {}
        in result dictionary""".format(
            key
        )
Example #6
0
def test_end2end_tfc_w1a2_ip_stitch():
    model = ModelWrapper(build_dir + "/end2end_tfc_w1a2_ipgen.onnx")
    model = model.transform(ReplaceVerilogRelPaths())
    model = model.transform(CreateStitchedIP(test_fpga_part))
    model.save(build_dir + "/end2end_tfc_w1a2_ipstitch.onnx")
Example #7
0
    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 test_fpgadataflow_fclayer_large_depth_decoupled_mode(
        mem_mode, idt, wdt, act, nf, sf, mw, mh):
    if nf == -1:
        nf = mh
    if sf == -1:
        sf = mw
    pe = mh // nf
    simd = mw // sf
    assert mh % pe == 0
    assert mw % sf == 0
    # generate weights
    W = gen_finn_dt_tensor(wdt, (mw, mh))
    # generate input data
    x = gen_finn_dt_tensor(idt, (1, mw))
    if act is None:
        # no activation, produce accumulators
        T = None
        tdt = None
        if wdt == DataType.BIPOLAR and idt == DataType.BIPOLAR:
            odt = DataType.UINT32
        else:
            odt = DataType.INT32
    else:
        odt = act
        (min, max) = calculate_signed_dot_prod_range(idt, wdt, mw)
        n_steps = act.get_num_possible_values() - 1
        T = np.random.randint(min, max - 1, (mh, n_steps)).astype(np.float32)
        # provide non-decreasing thresholds
        T = np.sort(T, axis=1)
        # generate thresholds for activation
        if wdt == DataType.BIPOLAR and idt == DataType.BIPOLAR:
            tdt = DataType.UINT32
            # bias thresholds to be positive
            T = np.ceil((T + mw) / 2)
            assert (T >= 0).all()
        else:
            tdt = DataType.INT32
    model = make_single_fclayer_modelwrapper(W, pe, simd, wdt, idt, odt, T,
                                             tdt)
    for node in model.graph.node:
        # lookup op_type in registry of CustomOps
        inst = getCustomOp(node)
        inst.set_nodeattr("mem_mode", mem_mode)

    # prepare input data
    input_dict = prepare_inputs(x, idt, wdt)
    if wdt == DataType.BIPOLAR and idt == DataType.BIPOLAR:
        # convert inputs to binary and use xnorpopcountmatmul
        y = xp.xnorpopcountmatmul((x + 1) / 2, (W + 1) / 2)
    else:
        y = np.matmul(x, W)
    if T is not None:
        y = multithreshold(y, T)
        if act == DataType.BIPOLAR:
            # binary to bipolar
            y = 2 * y - 1
        else:
            # signed offset
            y += act.min()
    oshape = model.get_tensor_shape("outp")
    y_expected = y.reshape(oshape)
    # TODO split up into several dependent tests -- need to check how this
    # works for parametrized tests...
    model = model.transform(SetExecMode("rtlsim"))
    model = model.transform(GiveUniqueNodeNames())
    model = model.transform(PrepareIP("xc7z020clg400-1", 5))
    model = model.transform(HLSSynthIP())
    model = model.transform(ReplaceVerilogRelPaths())
    model = model.transform(PrepareRTLSim())
    y_produced = oxe.execute_onnx(model, input_dict)["outp"]
    assert (y_produced.reshape(
        y_expected.shape) == y_expected).all(), "rtlsim failed"

    hls_synt_res_est = model.analysis(hls_synth_res_estimation)
    assert "StreamingFCLayer_Batch_0" in hls_synt_res_est
Example #9
0
 def apply(self, model):
     model = model.transform(ReplaceVerilogRelPaths())
     return super().apply(model)