|
| 1 | +import importlib.util |
| 2 | +import json |
1 | 3 | import os
|
| 4 | +import shutil |
| 5 | +import subprocess |
2 | 6 | import sys
|
3 | 7 |
|
4 | 8 | from hls4ml.backends import VivadoBackend
|
5 | 9 | from hls4ml.model.flow import get_flow, register_flow
|
6 |
| -from hls4ml.report import parse_vivado_report |
| 10 | +from hls4ml.report import aggregate_graph_reports, parse_vivado_report |
| 11 | +from hls4ml.utils.simulation_utils import ( |
| 12 | + annotate_axis_stream_widths, |
| 13 | + prepare_tb_inputs, |
| 14 | + read_testbench_log, |
| 15 | + write_verilog_testbench, |
| 16 | +) |
7 | 17 |
|
8 | 18 |
|
9 | 19 | class VitisBackend(VivadoBackend):
|
@@ -98,29 +108,131 @@ def build(
|
98 | 108 | export=False,
|
99 | 109 | vsynth=False,
|
100 | 110 | fifo_opt=False,
|
| 111 | + log_to_stdout=True, |
101 | 112 | ):
|
102 | 113 | if 'linux' in sys.platform:
|
103 | 114 | found = os.system('command -v vitis_hls > /dev/null')
|
104 | 115 | if found != 0:
|
105 | 116 | raise Exception('Vitis HLS installation not found. Make sure "vitis_hls" is on PATH.')
|
106 | 117 |
|
107 |
| - curr_dir = os.getcwd() |
108 |
| - os.chdir(model.config.get_output_dir()) |
109 |
| - os.system( |
110 |
| - ( |
111 |
| - 'vitis_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} ' |
112 |
| - 'validation={validation} export={export} vsynth={vsynth} fifo_opt={fifo_opt}"' |
113 |
| - ).format( |
114 |
| - reset=reset, |
115 |
| - csim=csim, |
116 |
| - synth=synth, |
117 |
| - cosim=cosim, |
118 |
| - validation=validation, |
119 |
| - export=export, |
120 |
| - vsynth=vsynth, |
121 |
| - fifo_opt=fifo_opt, |
122 |
| - ) |
| 118 | + build_command = ( |
| 119 | + 'vitis_hls -f build_prj.tcl "reset={reset} csim={csim} synth={synth} cosim={cosim} ' |
| 120 | + 'validation={validation} export={export} vsynth={vsynth} fifo_opt={fifo_opt}"' |
| 121 | + ).format( |
| 122 | + reset=reset, |
| 123 | + csim=csim, |
| 124 | + synth=synth, |
| 125 | + cosim=cosim, |
| 126 | + validation=validation, |
| 127 | + export=export, |
| 128 | + vsynth=vsynth, |
| 129 | + fifo_opt=fifo_opt, |
123 | 130 | )
|
124 |
| - os.chdir(curr_dir) |
125 | 131 |
|
126 |
| - return parse_vivado_report(model.config.get_output_dir()) |
| 132 | + output_dir = model.config.get_output_dir() |
| 133 | + stdout_log = os.path.join(output_dir, 'build_stdout.log') |
| 134 | + stderr_log = os.path.join(output_dir, 'build_stderr.log') |
| 135 | + |
| 136 | + stdout_target = None if log_to_stdout else open(stdout_log, 'w') |
| 137 | + stderr_target = None if log_to_stdout else open(stderr_log, 'w') |
| 138 | + |
| 139 | + try: |
| 140 | + process = subprocess.Popen( |
| 141 | + build_command, shell=True, cwd=output_dir, stdout=stdout_target, stderr=stderr_target, text=True |
| 142 | + ) |
| 143 | + process.communicate() |
| 144 | + |
| 145 | + if process.returncode != 0: |
| 146 | + raise Exception(f'Build failed for {model.config.get_project_name()}. See logs for details.') |
| 147 | + finally: |
| 148 | + if not log_to_stdout: |
| 149 | + stdout_target.close() |
| 150 | + stderr_target.close() |
| 151 | + |
| 152 | + return parse_vivado_report(output_dir) |
| 153 | + |
| 154 | + def build_stitched_design( |
| 155 | + self, |
| 156 | + model, |
| 157 | + stitch_design=True, |
| 158 | + sim_stitched_design=False, |
| 159 | + export_stitched_design=False, |
| 160 | + graph_reports=None, |
| 161 | + simulation_input_data=None, |
| 162 | + ): |
| 163 | + |
| 164 | + nn_config = model.nn_config |
| 165 | + os.makedirs(nn_config['OutputDir'], exist_ok=True) |
| 166 | + stitched_design_dir = os.path.join(nn_config['OutputDir'], nn_config['StitchedProjectName']) |
| 167 | + if stitch_design: |
| 168 | + if os.path.exists(stitched_design_dir): |
| 169 | + shutil.rmtree(stitched_design_dir) |
| 170 | + os.makedirs(stitched_design_dir) |
| 171 | + |
| 172 | + spec = importlib.util.find_spec('hls4ml') |
| 173 | + hls4ml_path = os.path.dirname(spec.origin) |
| 174 | + ip_stitcher_path = os.path.join(hls4ml_path, 'templates/vivado/ip_stitcher.tcl') |
| 175 | + stdout_log = os.path.join(stitched_design_dir, 'stitcher_stdout.log') |
| 176 | + stderr_log = os.path.join(stitched_design_dir, 'stitcher_stderr.log') |
| 177 | + nn_config_path = os.path.join(stitched_design_dir, 'nn_config.json') |
| 178 | + testbench_path = os.path.join(stitched_design_dir, 'testbench.v') |
| 179 | + testbench_log_path = os.path.join(stitched_design_dir, 'testbench_log.csv') |
| 180 | + |
| 181 | + try: |
| 182 | + shutil.copy(ip_stitcher_path, stitched_design_dir) |
| 183 | + except Exception as e: |
| 184 | + print(f"Error: {e}. Cannot copy 'ip_stitcher.tcl' to {nn_config['StitchedProjectName']} folder.") |
| 185 | + |
| 186 | + # Verilog output bitwidths are rounded up and may differ from HLS output bitwidths |
| 187 | + if nn_config['outputs'][0]['pragma'] == 'stream': |
| 188 | + last_graph_project_path = os.path.join( |
| 189 | + model.graphs[-1].config.get_output_dir(), model.graphs[-1].config.get_project_dir() |
| 190 | + ) |
| 191 | + annotate_axis_stream_widths(nn_config, last_graph_project_path) |
| 192 | + with open(nn_config_path, "w") as file: |
| 193 | + json.dump(nn_config, file, indent=4) |
| 194 | + |
| 195 | + if sim_stitched_design: |
| 196 | + write_verilog_testbench(nn_config, testbench_path) |
| 197 | + tb_inputs = prepare_tb_inputs(simulation_input_data, nn_config['inputs']) |
| 198 | + model.write_tb_inputs(tb_inputs, stitched_design_dir) |
| 199 | + print('Verilog testbench and its input data were generated.') |
| 200 | + |
| 201 | + print('Running build process of stitched IP...\n') |
| 202 | + stitch_command = [ |
| 203 | + 'vivado', |
| 204 | + '-mode', |
| 205 | + 'batch', |
| 206 | + '-nojournal', |
| 207 | + '-nolog', |
| 208 | + '-notrace', |
| 209 | + '-source', |
| 210 | + ip_stitcher_path, |
| 211 | + '-tclargs', |
| 212 | + f'stitch_design={int(stitch_design)}', |
| 213 | + f'sim_design={int(sim_stitched_design)}', |
| 214 | + f'export_design={int(export_stitched_design)}', |
| 215 | + f"stitch_project_name={nn_config['StitchedProjectName']}", |
| 216 | + f"original_project_name={nn_config['OriginalProjectName']}", |
| 217 | + 'sim_verilog_file=testbench.v', |
| 218 | + ] |
| 219 | + |
| 220 | + with open(stdout_log, 'w') as stdout_file, open(stderr_log, 'w') as stderr_file: |
| 221 | + process = subprocess.Popen( |
| 222 | + stitch_command, cwd=stitched_design_dir, stdout=stdout_file, stderr=stderr_file, text=True, shell=False |
| 223 | + ) |
| 224 | + process.communicate() |
| 225 | + if process.returncode != 0: |
| 226 | + raise Exception(f"Stitching failed for {nn_config['StitchedProjectName']}. See logs for details.") |
| 227 | + |
| 228 | + stitched_report = {'StitchedDesignReport': {}} |
| 229 | + if stitch_design: |
| 230 | + stitched_report = aggregate_graph_reports(graph_reports) |
| 231 | + |
| 232 | + if sim_stitched_design: |
| 233 | + testbench_output = read_testbench_log(testbench_log_path, nn_config['outputs']) |
| 234 | + stitched_report['BehavSimResults'] = testbench_output['BehavSimResults'] |
| 235 | + stitched_report['StitchedDesignReport']['BestLatency'] = testbench_output['BestLatency'] |
| 236 | + stitched_report['StitchedDesignReport']['WorstLatency'] = testbench_output['WorstLatency'] |
| 237 | + |
| 238 | + return stitched_report |
0 commit comments