|
| 1 | +import json |
| 2 | +import zipfile |
| 3 | + |
| 4 | +from hls4ml.model.optimizer.optimizer import ConfigurableOptimizerPass, ModelOptimizerPass |
| 5 | + |
| 6 | + |
| 7 | +def initialize_large_fifos(model, profiling_fifo_depth): |
| 8 | + """Set all FIFO depths equal to a large value so that they can be profiled. |
| 9 | +
|
| 10 | + Args: |
| 11 | + model (ModelGraph): The model to which FIFO depth optimization is applied. |
| 12 | + profiling_fifo_depth (int): A large non-negative integer, must be larger than the max expected depth of the FIFOs. |
| 13 | +
|
| 14 | + Returns: |
| 15 | + Dict[str, int]: A dictionary containing FIFO names as keys and their initial depths as values is returned for |
| 16 | + comparison with the optimized depths. |
| 17 | + """ |
| 18 | + |
| 19 | + # filter all the output variables and keep only the internal FIFOs, excluding output objects that are not FIFOs and the |
| 20 | + # input and output FIFOs as they can't be profiled and are implementation dependant i.e AXI Stream, AXI Master or |
| 21 | + # connected to another IP |
| 22 | + vars_to_profile = { |
| 23 | + output_variable_name: output_variable |
| 24 | + for output_variable_name, output_variable in model.output_vars.items() |
| 25 | + if ('StreamVariable' in str(type(output_variable))) |
| 26 | + and output_variable != model.get_output_variables()[0] |
| 27 | + and output_variable != model.get_input_variables()[0] |
| 28 | + } |
| 29 | + |
| 30 | + # initialize all the fifos to `profiling_fifo_depth` so that they will be automatically implemented in BRAMs and so |
| 31 | + # they will be profiled. Alternatively, "config_dataflow -override_user_fifo_depth profiling_fifo_depth" can be |
| 32 | + # used inside build_prj.tcl to override all FIFO depths with the specified value |
| 33 | + initial_fifo_depths = {} |
| 34 | + for output_variable in vars_to_profile.values(): |
| 35 | + if output_variable.pragma: |
| 36 | + initial_fifo_depths[output_variable.name] = int(output_variable.pragma[1]) |
| 37 | + output_variable.pragma = (output_variable.pragma[0], profiling_fifo_depth) |
| 38 | + return initial_fifo_depths |
| 39 | + |
| 40 | + |
| 41 | +def execute_cosim_to_profile_fifos(model): |
| 42 | + """Execute a co-simulation with a test-bench that calls the top function to properly profile the max FIFO depths. |
| 43 | + Note that the top function needs to execute **least twice**, so user-provided input must have at least two samples. |
| 44 | +
|
| 45 | + Args: |
| 46 | + model (ModelGraph): The model to which FIFO depth optimization is applied. |
| 47 | + """ |
| 48 | + model.write() |
| 49 | + |
| 50 | + model.build( |
| 51 | + reset=False, |
| 52 | + csim=False, |
| 53 | + synth=True, |
| 54 | + cosim=True, |
| 55 | + validation=False, |
| 56 | + export=False, |
| 57 | + vsynth=False, |
| 58 | + fifo_opt=True, |
| 59 | + ) |
| 60 | + |
| 61 | + |
| 62 | +def get_vitis_optimized_fifo_depths(model): |
| 63 | + """Parse the files generated by the co-simulation to retrieve the optimized depths for the FIFOs. |
| 64 | + Attention, only the FIFOs between the layers are profiled! |
| 65 | +
|
| 66 | + Args: |
| 67 | + model (ModelGraph): The model to which FIFO depth optimization is applied. |
| 68 | +
|
| 69 | + Returns: |
| 70 | + Dict[str, int]: A dictionary that contains the FIFO names as keys and the optimized depths as values. |
| 71 | + """ |
| 72 | + # channel.zip is generated after the co-simulation and contains the chan_status*.csv files |
| 73 | + # in the chan_status*.csv files the max depth achieved during co-simulation can be found at the last (4th) line |
| 74 | + path_to_zip_file = ( |
| 75 | + model.config.get_output_dir() |
| 76 | + + '/' |
| 77 | + + model.config.get_project_name() |
| 78 | + + '_prj' |
| 79 | + + '/solution1/.autopilot/db/channel_depth_info/' |
| 80 | + ) |
| 81 | + |
| 82 | + with zipfile.ZipFile(f'{path_to_zip_file}channel.zip', 'r') as zip_ref: |
| 83 | + zip_ref.extractall(path_to_zip_file) |
| 84 | + |
| 85 | + # the channel_info.csv file contains the mapping of each fifo name (i.e layer4_out_U) to the respective |
| 86 | + # chan_status*.csv file |
| 87 | + names_file_path = ( |
| 88 | + model.config.get_output_dir() |
| 89 | + + '/' |
| 90 | + + model.config.get_project_name() |
| 91 | + + '_prj' |
| 92 | + + '/solution1/.autopilot/db/channel_info.csv' |
| 93 | + ) |
| 94 | + |
| 95 | + csv_fifo_depth_files = {} |
| 96 | + with open(names_file_path) as names_file: |
| 97 | + for line in names_file: |
| 98 | + layer_name = line.split(',')[1] |
| 99 | + csv_file_name = line.split(',')[3][:-1] |
| 100 | + csv_fifo_depth_files[layer_name] = csv_file_name |
| 101 | + |
| 102 | + optmized_fifo_depths = {} |
| 103 | + for layer_name, file_name in csv_fifo_depth_files.items(): |
| 104 | + with open(path_to_zip_file + file_name) as chan_status_file: |
| 105 | + lines = chan_status_file.readlines() |
| 106 | + optmized_fifo_depths[layer_name[:-2]] = int( |
| 107 | + lines[-1] |
| 108 | + ) # remove "_U" from the layer name string and keep the last line of the file that contains the max depth |
| 109 | + |
| 110 | + return optmized_fifo_depths |
| 111 | + |
| 112 | + |
| 113 | +def generate_depths_file(model, initial_fifo_depths, optimized_fifo_depths): |
| 114 | + """Generate a json file with the names of the FIFOs, the initial depths set by hls4ml and their optimized depths, |
| 115 | + for post-processing. The json file is not used by the rest of the pipeline, it is only produced for the user. |
| 116 | +
|
| 117 | + Args: |
| 118 | + model (ModelGraph): The model to which FIFO depth optimization is applied. |
| 119 | + initial_fifo_depths (Dict[str, int]): A dictionary that contains the FIFO names as keys and the initial |
| 120 | + depths as values. |
| 121 | + optimized_fifo_depths (Dict[str, int]): A dictionary that contains the FIFO names as keys and the optimized |
| 122 | + depths as values. |
| 123 | + """ |
| 124 | + depths = {} |
| 125 | + for fifo_name in initial_fifo_depths.keys(): |
| 126 | + depths[fifo_name] = {} |
| 127 | + depths[fifo_name]['initial'] = initial_fifo_depths[fifo_name] |
| 128 | + depths[fifo_name]['optimized'] = optimized_fifo_depths[fifo_name] |
| 129 | + |
| 130 | + with open(model.config.get_output_dir() + '/fifo_depths.json', 'w') as f: |
| 131 | + json.dump(depths, f, indent=4) |
| 132 | + |
| 133 | + |
| 134 | +def set_optimized_fifo_depths(model, optimized_fifo_depths): |
| 135 | + """Set the new optimized FIFO depths. |
| 136 | +
|
| 137 | + Args: |
| 138 | + model (ModelGraph): The model to which FIFO depth optimization is applied. |
| 139 | + optimized_fifo_depths (Dict[str, int]): A dictionary that contains the FIFO names as keys and the optimized |
| 140 | + depths as values. |
| 141 | + """ |
| 142 | + |
| 143 | + # iterate through the layer output FIFOs |
| 144 | + for output_variable in model.output_vars.values(): |
| 145 | + if 'StreamVariable' in str(type(output_variable)): |
| 146 | + if output_variable.pragma: |
| 147 | + |
| 148 | + if output_variable.name not in optimized_fifo_depths.keys(): |
| 149 | + continue |
| 150 | + |
| 151 | + filtered_depth = optimized_fifo_depths[output_variable.name] |
| 152 | + output_variable.pragma = (output_variable.pragma[0], filtered_depth) |
| 153 | + |
| 154 | + |
| 155 | +class FifoDepthOptimization(ConfigurableOptimizerPass, ModelOptimizerPass): |
| 156 | + def __init__(self): |
| 157 | + # use `profiling_fifo_depth = 0` to keep the default fifo depth |
| 158 | + # consider changing 100_000 either with a very very large value > of any total bram storage space |
| 159 | + # or via vitis 2023.2 c-simulation |
| 160 | + self.profiling_fifo_depth = 100_000 |
| 161 | + |
| 162 | + def transform(self, model): |
| 163 | + """Perform FIFO depth optimization between the FIFOs of all layers to reduce resource utilization as the |
| 164 | + initial FIFOs set by hls4ml might be larger than required. At the end of the optimization the FIFOs will |
| 165 | + have the largest depths achieved during co-simulation without causing any deadlocks between the layers |
| 166 | + (producer-consumer), thus no additional delays between the layers. In some cases, this optimization |
| 167 | + might lead to bigger FIFOs than initially set by the hls4ml tool in order to prevent deadlocks. |
| 168 | +
|
| 169 | + Args: |
| 170 | + model (ModelGraph): The model to which FIFO depth optimization is applied. |
| 171 | +
|
| 172 | + Raises: |
| 173 | + ValueError: If the FIFO depth for profiling provided by the user is not a non-negative integer. |
| 174 | + RuntimeError: If the IO type is not set to "io_stream". |
| 175 | +
|
| 176 | + Returns: |
| 177 | + bool: The execution state of the Optimizer Pass |
| 178 | + """ |
| 179 | + |
| 180 | + if not isinstance(self.profiling_fifo_depth, int) or self.profiling_fifo_depth <= 0: |
| 181 | + raise ValueError('The FIFO depth for profiling (profiling_fifo_depth variable) must be a non-negative integer.') |
| 182 | + |
| 183 | + # check axi-stream or io-stream |
| 184 | + if not (model.config.get_config_value('IOType') == 'io_stream'): |
| 185 | + raise RuntimeError('To use this optimization you have to set `IOType` field to `io_stream` in the HLS config.') |
| 186 | + |
| 187 | + initial_fifo_depths = initialize_large_fifos(model, self.profiling_fifo_depth) |
| 188 | + execute_cosim_to_profile_fifos(model) |
| 189 | + optimized_fifo_depths = get_vitis_optimized_fifo_depths(model) |
| 190 | + generate_depths_file(model, initial_fifo_depths, optimized_fifo_depths) |
| 191 | + set_optimized_fifo_depths(model, optimized_fifo_depths) |
| 192 | + |
| 193 | + print('FIFO optimization completed') |
| 194 | + |
| 195 | + return False |
0 commit comments