diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index 192416a496..ab09068133 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -380,11 +380,48 @@ class Synthesize : public Command { class EstimatePower : public Command { public: + static constexpr llvm::StringLiteral HDL = "hdl"; + static constexpr llvm::StringLiteral STAGE = "stage"; + EstimatePower(FrontendState &state) : Command("estimate-power", "Estimate the power consumption of the design using switching " "activity from simulation.", - state) {} + state) { + addOption({HDL, "HDL type, vhdl or verilog"}); + addOption({STAGE, + "The netlist used for functional simulation (pre or post " + "synthesis) in Modelsim to generate SAIF file, options are " + "'pre' and 'post' (default : 'pre')"}); + } + + CommandResult execute(CommandArguments &args) override; +}; + +class PowerEval : public Command { +public: + static constexpr llvm::StringLiteral HDL = "hdl"; + static constexpr llvm::StringLiteral STAGE = "stage"; + static constexpr llvm::StringLiteral FLATTEN_HIERARCHY = "flatten-hierarchy"; + + PowerEval(FrontendState &state) + : Command( + "evaluate-power", + "Runs the Vivado flow and vector-based power evaluation at " + "different design stages," + "using switching activity from simulation based on XSIM in Vivado.", + state) { + addOption({HDL, "HDL type, vhdl or verilog"}); + addOption({STAGE, + "Stage (synth or impl) to perform simulation with xsim and " + "vector-based power " + "evaluation, synthesis or implementation, default : synth"}); + addOption( + {FLATTEN_HIERARCHY, + "Control hierarchy flattening during synthesis. If set, the " + "fully flattened flow is used. If not set, the FLATTEN_HIERARCHY " + "none property is emitted."}); + } CommandResult execute(CommandArguments &args) override; }; @@ -808,7 +845,7 @@ CommandResult Simulate::execute(CommandArguments &args) { return execCmd(script, state.dynamaticPath, state.getKernelDir(), state.getOutputDir(), state.getKernelName(), state.vivadoPath, state.fpUnitsGenerator == "vivado" ? "true" : "false", - simulator, state.hdl); + simulator, floatToString(state.targetCP, 2), state.hdl); } CommandResult Visualize::execute(CommandArguments &args) { @@ -845,14 +882,77 @@ CommandResult EstimatePower::execute(CommandArguments &args) { if (!state.sourcePathIsSet(keyword)) return CommandResult::FAIL; + // Get simulation stage configuration + std::string stage = "pre"; + + if (auto it = args.options.find(STAGE); it != args.options.end()) { + if (it->second == "pre" || it->second == "post") { + stage = it->second; + } else { + llvm::errs() << "Unknow stage '" << it->second + << "', possible options are 'pre' and 'post'.\n"; + return CommandResult::FAIL; + } + } + + std::string script = + state.dynamaticPath + "/tools/dynamatic/power/estimate_power.py"; + + // clang-format off + return execCmd( + "python", script, + "--output_dir", state.getOutputDir(), + "--kernel_name", state.getKernelName(), + "--hdl", state.hdl, + "--synth", stage, + "--cp", floatToString(state.targetCP, 3) + ); + // clang-format on +} + +CommandResult PowerEval::execute(CommandArguments &args) { + // We need the source path to be set + if (!state.sourcePathIsSet(keyword)) + return CommandResult::FAIL; + + // Get simulation stage configuration + std::string stage = "synth"; + + if (auto it = args.options.find(STAGE); it != args.options.end()) { + if (it->second == "synth" || it->second == "impl") { + stage = it->second; + } else { + llvm::errs() << "Unknow stage '" << it->second + << "', possible options are 'synth' and 'impl'.\n"; + return CommandResult::FAIL; + } + } + + // Get flatten hierarchy configuration + std::string flattenHierarchy = "1"; + + if (auto it = args.options.find(FLATTEN_HIERARCHY); + it != args.options.end()) { + if (it->second == "0" || it->second == "1") { + flattenHierarchy = it->second; + } else { + llvm::errs() << "Unknow flatten hierarchy option '" << it->second + << "', possible options are '0' (not set) and '1' (set).\n"; + return CommandResult::FAIL; + } + } + std::string script = - state.dynamaticPath + "/tools/dynamatic/estimate_power/estimate_power.py"; + state.dynamaticPath + "/tools/dynamatic/power/power_eval.py"; // clang-format off return execCmd( "python", script, "--output_dir", state.getOutputDir(), "--kernel_name", state.getKernelName(), + "--hdl", state.hdl, + "--stage", stage, + (flattenHierarchy == "1" ? "--flatten_hierarchy" : ""), "--cp", floatToString(state.targetCP, 3) ); // clang-format on @@ -931,6 +1031,7 @@ int main(int argc, char **argv) { commands.add(state); commands.add(state); commands.add(state); + commands.add(state); commands.add(state); commands.add(state); diff --git a/tools/dynamatic/estimate_power/estimate_power.py b/tools/dynamatic/power/estimate_power.py similarity index 56% rename from tools/dynamatic/estimate_power/estimate_power.py rename to tools/dynamatic/power/estimate_power.py index a7604abdf3..e1d7223cb9 100755 --- a/tools/dynamatic/estimate_power/estimate_power.py +++ b/tools/dynamatic/power/estimate_power.py @@ -10,8 +10,6 @@ from pdb import run import shutil -from numpy import power - # Import file templates from script_templates import * @@ -29,7 +27,7 @@ class DesignFlag(Enum): class InputFlag(Enum): - PI = "pi" + PI = "pi" # Primary Inputs only ALL = "all" @@ -43,28 +41,36 @@ def run_command(command, power_analysis_dir): ################################################################ -def main(output_dir, kernel_name, clock_period): +def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_synth_netlist=False): print("[INFO] Running power estimation") + # Get the date for generating the scripts date = get_date() - vhdl_src_folder = os.path.join(output_dir, "hdl") - if not os.path.exists(vhdl_src_folder): - print(f"[ERROR] {vhdl_src_folder} not found. Please run the 'write-hdl' command") - return - - tb_file = os.path.join(output_dir, "sim", "HDL_SRC", f"tb_{kernel_name}.vhd") - if not os.path.exists(tb_file): - print(f"[ERROR] {tb_file} not found. Please run the 'simulate' command") - return - + # + # Step 1: Set up folders and file paths + # power_analysis_dir = os.path.join(output_dir, "power") - if os.path.exists(power_analysis_dir): shutil.rmtree(power_analysis_dir) os.makedirs(power_analysis_dir) + + hdl_src_folder = os.path.join(output_dir, "hdl") + if not os.path.exists(hdl_src_folder): + print(f"[ERROR] {hdl_src_folder} not found. Please run the 'write-hdl' command") + return + # For both VHDL and Verilog + tb_file_name = f"tb_{kernel_name}.vhd" if hdl == "vhdl" else f"tb_{kernel_name}.v" + tb_file = os.path.join(output_dir, "sim", "HDL_SRC", tb_file_name) + if not os.path.exists(tb_file): + print(f"[ERROR] {tb_file} not found. Please run the 'simulate' command") + return + + # + # Step 2: Generate the xdc file for synthesis in Vivado + # xdc_dict = { 'tcp': clock_period, 'halftcp': clock_period / 2 @@ -77,28 +83,51 @@ def main(output_dir, kernel_name, clock_period): target_path=period_xdc_file ) - - # Get all the input files - # As a list of imports for simulation.do - sim_inputs = [ - f"project addfile {vhdl_src_folder}/{f}" - for f in sorted(os.listdir(vhdl_src_folder)) + # + # Step 3: Get all files needed for simulation in Modelsim + # + # Get all the simulation hdl input files + # As a list of imports for the simulation.do + sim_hdl_inputs = [ + f"project addfile {hdl_src_folder}/{f}" + for f in sorted(os.listdir(hdl_src_folder)) if f.endswith(".vhd") ] - sim_inputs.append(f"project addfile {tb_file}") + sim_hdl_inputs.append(f"project addfile {tb_file}") + + # Add verilog files if hdl set to verilog + sim_folder = os.path.join(output_dir, "sim", "HDL_SRC") + if hdl == "verilog": + sim_hdl_inputs.extend([ + f"project addfile {sim_folder}/{f}" + for f in sorted(os.listdir(sim_folder)) + if f.endswith(".v") or f.endswith(".sv") + ]) # which file is included varies on pre/post synth workflow # so remove the pre-synth file by default - sim_inputs.remove(f"project addfile {vhdl_src_folder}/{kernel_name}.vhd") - sim_inputs = "\n".join(sim_inputs) - - # Get all the input VHDl files + if hdl != "verilog": + sim_hdl_inputs.remove(f"project addfile {hdl_src_folder}/{kernel_name}.vhd") + else: + sim_hdl_inputs.remove(f"project addfile {sim_folder}/{kernel_name}.v") + sim_hdl_inputs = "\n".join(sim_hdl_inputs) + + + # + # Step 4: Get all files needed for power estimation in Vivado + # + # Get all the synthesis HDL files # As a list of imports for report_power.tcl - vhdl_inputs = "\n".join( - f"read_vhdl -vhdl2008 $VHDL_SRC/{f}" - for f in sorted(os.listdir(vhdl_src_folder)) - if f.endswith(".vhd") - ) + if hdl == "verilog": + synth_hdl_inputs = "\n" + "\n".join( + f"read_verilog $HDL_SRC/{f}" + for f in sorted(os.listdir(hdl_src_folder)) + if f.endswith(".v")) + else: + synth_hdl_inputs = "\n".join( + f"read_vhdl -vhdl2008 $HDL_SRC/{f}" + for f in sorted(os.listdir(hdl_src_folder)) + if f.endswith(".vhd")) # We have four configurations for generating the power estimation on post-synthesis # netlist from vivado @@ -107,19 +136,20 @@ def main(output_dir, kernel_name, clock_period): # (Case 3) post_pi: Generating the SAIF file with post-synthesis simulation containing only PIs. # (Case 4) post_all: Generating the SAIF file with post-synthesis simulaiton containing all ports. - # Currently only running pre_all - design_flag = DesignFlag.PRE + # Currently only running case 2 and case 4 + design_flag = DesignFlag.PRE if not post_synth_netlist else DesignFlag.POST input_flag = InputFlag.ALL - # only need to do this synth if doing power estimation - # on the post-synth vhdl code + # + # Step 5: If doing power estimation on post-synthesis netlist, run synthesis first + # if design_flag == DesignFlag.POST: # Step 1: Run Vivado synthesis flow synthesis_dict = { 'date': date, 'design': kernel_name, - 'hdlsrc': vhdl_src_folder, - 'inputs': vhdl_inputs + 'hdlsrc': hdl_src_folder, + 'inputs': synth_hdl_inputs } synth_script = os.path.join(power_analysis_dir, "synthesis.tcl") @@ -131,33 +161,40 @@ def main(output_dir, kernel_name, clock_period): target_path=synth_script ) - print("[INFO] Pre-synthesizing" + + print("[INFO] Pre-synthesizing " + "to improve switching activity annotation for power estimation") # Run the synthesis flow - synthesis_command = f"cd {power_analysis_dir}; vivado -mode batch -source synthesis.tcl" + synthesis_command = f"cd {power_analysis_dir}; {vivado_cmd} -mode batch -source synthesis.tcl" if run_command(synthesis_command, power_analysis_dir): print("[INFO] Synthesis succeeded") else: print("[ERROR] Synthesis failed") return - # Step 2: Run Modelsim simulation + # + # Step 6: Generate .do file for Modelsim simulation to generate SAIF file + # if (input_flag == InputFlag.ALL): power_flag = "-r -in -inout -out -internal" else: power_flag = "" if (design_flag == DesignFlag.PRE): - design_src = os.path.join(vhdl_src_folder, f"{kernel_name}.vhd") + if hdl == "verilog": + design_src = os.path.join(hdl_src_folder, f"{kernel_name}.v") + else: + design_src = os.path.join(hdl_src_folder, f"{kernel_name}.vhd") else: + # Get the post-synthesis netlist + # We generate VHDL netlist for both verilog and vhdl designs design_src = os.path.join(power_analysis_dir, f"{kernel_name}_syn.vhd") stage = f"{design_flag.value}_{input_flag.value}" simulation_dict = { - 'hdlsrc': vhdl_src_folder, + 'hdlsrc': hdl_src_folder, 'design': kernel_name, - 'inputs': sim_inputs, + 'inputs': sim_hdl_inputs, 'designsrc': design_src, 'powerflag': power_flag, 'stage': stage @@ -172,7 +209,11 @@ def main(output_dir, kernel_name, clock_period): substitute_dict=simulation_dict, target_path=simulation_script ) - + + # + # Step 7: Run simulation to generate SAIF file + # + #! A license is required for power analysis in Modelsim print("[INFO] Simulating to obtain switching activity information") modelsim_command = f"cd {verify_folder}; vsim -c -do {simulation_script}" @@ -182,13 +223,15 @@ def main(output_dir, kernel_name, clock_period): print("[ERROR] Simulation failed") return - # Step 3: Run Power Estimation + # + # Step 8: Run Power Estimation in Vivado + # power_dict = { 'date': date, 'design': kernel_name, - 'hdlsrc': vhdl_src_folder, + 'hdlsrc': hdl_src_folder, 'report_folder': power_analysis_dir, - 'inputs': vhdl_inputs, + 'inputs': synth_hdl_inputs, 'saif': os.path.join(verify_folder, f"{stage}.saif"), } @@ -203,7 +246,7 @@ def main(output_dir, kernel_name, clock_period): # Generate and run the report_power tcl script report_power_cmd = ( f"cd {power_analysis_dir};" + - f"vivado -mode batch -source {report_power_script}" + f"{vivado_cmd} -mode batch -source {report_power_script}" ) run_command(report_power_cmd, power_analysis_dir) if run_command(report_power_cmd, power_analysis_dir): @@ -215,9 +258,25 @@ def main(output_dir, kernel_name, clock_period): if __name__ == "__main__": p = argparse.ArgumentParser() p.add_argument("--output_dir", required=True, help="Output folder") - p.add_argument("--kernel_name", required=True, help="Name of kernel ") + p.add_argument("--kernel_name", required=True, help="Name of kernel under test") + p.add_argument("--hdl", required=True, help="HDL type (verilog or vhdl)", choices=["verilog", "vhdl"], default="vhdl") + p.add_argument( + "--synth", + choices=["pre", "post"], + required=False, + help=( + "Generate the SAIF file by simulating the pre-synthesis netlist, " + "or the post-synthesis netlist. Using the post-synthesis netlist " + "gives higher accuracy, but currently simulation may hang on some kernels." + ), + default="pre" + ) + p.add_argument("--vivado_cmd", type=str, required=False, help="Vivado command", default="vivado") p.add_argument("--cp", type=float, required=True, help="Clock period for synthesis") args = p.parse_args() + + # Select which netlist to use for simulation + use_post_synth_netlist = (args.synth == "post") - main(args.output_dir, args.kernel_name, args.cp) + main(args.output_dir, args.kernel_name, args.hdl, args.cp, args.vivado_cmd, use_post_synth_netlist) \ No newline at end of file diff --git a/tools/dynamatic/power/power_eval.py b/tools/dynamatic/power/power_eval.py new file mode 100644 index 0000000000..c30d176286 --- /dev/null +++ b/tools/dynamatic/power/power_eval.py @@ -0,0 +1,363 @@ +#!/usr/bin/env python3 +#! For now the flow only supports Verilog backend + +################################################################ +# Environment Setup +################################################################ + +import os +from enum import Enum +import argparse + +# Import file templates +from script_templates import * + +# Small handwritten util functions +from utils import * + + +################################################################ +# Experiment Enums +################################################################ + +class DesignStage(Enum): + SYNTH = "synth" + IMPL = "impl" + + +def _normalize_hdl(hdl): + if hdl == "verilog-beta": + return "verilog" + return hdl + + +def _list_files(folder_path, extensions): + return sorted( + f for f in os.listdir(folder_path) + if f.endswith(tuple(extensions)) + ) + + +def _find_tb_file(sim_src_dir, kernel_name): + tb_vhd = os.path.join(sim_src_dir, f"tb_{kernel_name}.vhd") + if os.path.exists(tb_vhd): + return tb_vhd + tb_v = os.path.join(sim_src_dir, f"tb_{kernel_name}.v") + if os.path.exists(tb_v): + return tb_v + return "" + + +def _build_rtl_list(rtl_files): + return "\n".join(f" $V_SRC_DIR/{f} \\" for f in rtl_files) + + +def _build_sim_list(sim_files, tb_file): + lines = [] + tb_name = os.path.basename(tb_file) + for fname in sim_files: + if fname == tb_name: + continue + lines.append(f" $SIM_SRC_DIR/{fname} \\") + if tb_name: + lines.append(" $TB_FILE \\") + return "\n".join(lines) + + +def run_command(command, power_dir): + report = os.path.join(power_dir, "report.txt") + ret = os.system(command + f" >> '{report}'") + return ret == 0 + + +################################################################ +# Main Function +################################################################ + +def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, + vivado_cmd="vivado", run_flow=True): + print("[INFO] Generating power evaluation script") + + # Normalize HDL selection + # For now, verilog-beta is treated as verilog + hdl = _normalize_hdl(hdl) + + # Resolve paths + output_dir = os.path.abspath(output_dir) + power_dir = os.path.join(output_dir, "power") + hdl_src_dir = os.path.join(output_dir, "hdl") + sim_src_dir = os.path.join(output_dir, "sim", "HDL_SRC") + verify_dir = os.path.join(output_dir, "sim", "HLS_VERIFY") + + if not os.path.exists(hdl_src_dir): + print(f"[ERROR] {hdl_src_dir} not found. Please run the 'write-hdl' command") + return + + if not os.path.exists(sim_src_dir): + print(f"[ERROR] {sim_src_dir} not found. Please run the 'simulate' command") + return + + check_else_create(power_dir) + + tb_file = _find_tb_file(sim_src_dir, kernel_name) + if not tb_file: + print(f"[ERROR] Testbench not found in {sim_src_dir}") + return + + # Generate the xdc file for synthesis and implementation in Vivado + xdc_dict = { + "tcp": clock_period, + "halftcp": clock_period / 2 + } + period_xdc_file = os.path.join(power_dir, "period.xdc") + target_file_generation( + template_file=base_xdc, + substitute_dict=xdc_dict, + target_path=period_xdc_file + ) + + # Collect RTL sources + rtl_extensions = [".v"] if hdl == "verilog" else [".vhd"] + rtl_files = _list_files(hdl_src_dir, rtl_extensions) + if not rtl_files: + print(f"[ERROR] No RTL sources found in {hdl_src_dir}") + return + + # Collect simulation sources + if hdl == "vhdl": + sim_files = [f for f in _list_files(sim_src_dir, [".vhd"]) if f not in rtl_files] + design_vhd = f"{kernel_name}.vhd" + sim_files = [f for f in sim_files if f != design_vhd] + else: + sim_files = _list_files(sim_src_dir, [".sv"]) + sim_files.append("tb_join.v") + sim_files = [f for f in sim_files if f != os.path.basename(tb_file)] + + # Build lists for TCL + rtl_sources = _build_rtl_list(rtl_files) + sim_sources = _build_sim_list(sim_files, tb_file) + + # Resolve output paths + pre_synth_saif = os.path.join(verify_dir, "pre_synth.saif") + + post_synth_fun_saif = os.path.join(verify_dir, "post_synth_fun.saif") + post_synth_fun_vcd = os.path.join(verify_dir, "post_synth_fun.vcd") + post_synth_time_saif = os.path.join(verify_dir, "post_synth_time.saif") + post_synth_time_vcd = os.path.join(verify_dir, "post_synth_time.vcd") + + post_impl_fun_saif = os.path.join(verify_dir, "post_impl_fun.saif") + post_impl_fun_vcd = os.path.join(verify_dir, "post_impl_fun.vcd") + post_impl_time_saif = os.path.join(verify_dir, "post_impl_time.saif") + post_impl_time_vcd = os.path.join(verify_dir, "post_impl_time.vcd") + + post_synth_fun_rpt = os.path.join(power_dir, "post_synth_fun_power.rpt") + post_synth_time_rpt = os.path.join(power_dir, "post_synth_time_power.rpt") + + post_impl_fun_rpt = os.path.join(power_dir, "post_impl_fun_power.rpt") + post_impl_time_rpt = os.path.join(power_dir, "post_impl_time_power.rpt") + + flatten_line = "" + if not flatten_hierarchy: + flatten_line = ( + "set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY none " + "[get_runs synth_1]" + ) + + synth_lines = [ + "# =============================================================", + "# 5. Post-synthesis simulations (functional & timing)", + "# =============================================================", + ] + if flatten_line: + synth_lines.append(flatten_line) + synth_lines += [ + "launch_runs synth_1 -jobs 8", + "wait_on_run synth_1", + "open_run synth_1", + "", + "# --- 5a. Functional netlist sim ---", + "launch_simulation -mode post-synthesis -type functional", + "current_scope /tb/duv_inst", + f"open_saif {post_synth_fun_saif}", + "log_saif [get_objects -r]", + f"open_vcd {post_synth_fun_vcd}", + "log_vcd", + "", + "run all", + "close_saif", + "close_vcd", + "close_sim", + "", + "# --- 5b. Timing netlist sim ---", + "launch_simulation -mode post-synthesis -type timing", + "current_scope /tb/duv_inst", + f"open_saif {post_synth_time_saif}", + "log_saif [get_objects -r]", + f"open_vcd {post_synth_time_vcd}", + "log_vcd", + "", + "run all", + "close_saif", + "close_vcd", + "close_sim", + "", + "# =============================================================", + "# 6. Power reports for post-synthesis simulations", + "# =============================================================", + f"read_saif -file {post_synth_fun_saif}", + f"report_power -file {post_synth_fun_rpt}", + "reset_switching_activity -all", + "", + f"read_saif -file {post_synth_time_saif}", + f"report_power -file {post_synth_time_rpt} -hierarchical_depth 0 -verbose", + "reset_switching_activity -all", + ] + synth_block = "\n".join(synth_lines) + "\n" + + impl_block = "" + if stage == DesignStage.IMPL: + impl_lines = [ + "# =============================================================", + "# 7. Post-implementation simulations (functional & timing)", + "# =============================================================", + "launch_runs impl_1 -to_step route_design -jobs 8", + "wait_on_run impl_1", + "", + "open_run impl_1", + "", + "# --- 7a. Functional routed sim ---", + "launch_simulation -mode post-implementation -type functional", + "current_scope /tb/duv_inst", + f"open_saif {post_impl_fun_saif}", + "log_saif [get_objects -r]", + f"open_vcd {post_impl_fun_vcd}", + "log_vcd", + "", + "run all", + "close_saif", + "close_vcd", + "close_sim", + "", + "# --- 7b. Timing routed sim ---", + "launch_simulation -mode post-implementation -type timing", + "current_scope /tb/duv_inst", + f"open_saif {post_impl_time_saif}", + "log_saif [get_objects -r]", + f"open_vcd {post_impl_time_vcd}", + "log_vcd", + "", + "# Only works for Verilog designs for now", + "run 8000ns", + "close_saif", + "close_vcd", + "close_sim", + "", + "# =============================================================", + "# 8. Power reports for post-implementation simulations", + "# =============================================================", + f"read_saif -file {post_impl_fun_saif}", + f"report_power -file {post_impl_fun_rpt}", + "reset_switching_activity -all", + "", + f"read_saif -file {post_impl_time_saif}", + f"report_power -file {post_impl_time_rpt}", + ] + impl_block = "\n".join(impl_lines) + "\n" + + target_language = "VHDL" if hdl == "vhdl" else "Verilog" + eval_dict = { + "date": get_date(), + "vivado_cmd": vivado_cmd, + "top_design": kernel_name, + "tb_top": "tb", + "v_src_dir": hdl_src_dir, + "sim_src_dir": sim_src_dir, + "tb_file": tb_file, + "xdc_file": period_xdc_file, + "saif_pre": pre_synth_saif, + "rtl_sources": rtl_sources, + "sim_sources": sim_sources, + "target_language": target_language, + "synth_block": synth_block, + "impl_block": impl_block, + } + + power_extraction_script = os.path.join(power_dir, "power_extraction.tcl") + target_file_generation( + template_file=vivado_power_evaluation_tcl, + substitute_dict=eval_dict, + target_path=power_extraction_script + ) + + print(f"[INFO] Power evaluation script generated at {power_extraction_script}") + + if run_flow: + print("[INFO] Launching Vivado power evaluation") + eval_command = ( + f"cd '{power_dir}'; " + f"{vivado_cmd} -mode batch -source '{power_extraction_script}'" + ) + if run_command(eval_command, power_dir): + print("[INFO] Vivado power evaluation finished") + else: + print("[ERROR] Vivado power evaluation failed") + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--output_dir", required=True, help="Output folder") + p.add_argument("--kernel_name", required=True, help="Name of kernel under test") + p.add_argument( + "--hdl", + required=True, + help="HDL type (verilog, verilog-beta or vhdl)", + choices=["verilog", "verilog-beta", "vhdl"], + default="vhdl" + ) + p.add_argument( + "--stage", + dest="stage", + choices=["synth", "impl"], + required=False, + help=( + "Stage to perform simulation with XSim and vector-based power " + "evaluation, synthesis or implementation (default: synth)." + ), + default="synth" + ) + p.add_argument( + "--flatten_hierarchy", + action="store_true", + help=( + "Control hierarchy flattening during synthesis. With 'false' to emit " + "the FLATTEN_HIERARCHY none property, or 'true' for the fully flattened flow." + ), + ) + p.add_argument("--vivado_cmd", type=str, required=False, help="Vivado command", default="vivado") + p.add_argument("--cp", type=float, required=True, help="Clock period for design implementation") + p.add_argument( + "--no-run", + action="store_true", + help="Only generate the TCL script without running Vivado." + ) + + args = p.parse_args() + + # Default to synthesis stage if not specified + stage = args.stage if args.stage is not None else "synth" + + # Exit when the hdl is VHDL + if args.hdl == "vhdl": + print("[ERROR] VHDL flow is not supported yet for power evaluation. Please use Verilog backend instead.") + exit(1) + + main( + args.output_dir, + args.kernel_name, + args.hdl, + args.cp, + DesignStage(stage), + args.flatten_hierarchy, + args.vivado_cmd, + run_flow=not args.no_run, + ) diff --git a/tools/dynamatic/estimate_power/script_templates/__init__.py b/tools/dynamatic/power/script_templates/__init__.py similarity index 78% rename from tools/dynamatic/estimate_power/script_templates/__init__.py rename to tools/dynamatic/power/script_templates/__init__.py index fe6a180575..f1aab40cd1 100644 --- a/tools/dynamatic/estimate_power/script_templates/__init__.py +++ b/tools/dynamatic/power/script_templates/__init__.py @@ -2,3 +2,4 @@ from .xdc_template import * from .report_power_template import * from .synthesis_template import * +from .eval_power_template import * diff --git a/tools/dynamatic/power/script_templates/eval_power_template.py b/tools/dynamatic/power/script_templates/eval_power_template.py new file mode 100644 index 0000000000..6d97cc8cc8 --- /dev/null +++ b/tools/dynamatic/power/script_templates/eval_power_template.py @@ -0,0 +1,85 @@ +vivado_power_evaluation_tcl = r"""# Date: %date +# Simulation with XSim + SAIF dump + power extraction +# Command %vivado_cmd -mode batch -source power_extraction.tcl +# ============================================================= + +# ============================================================= +# 0. Setup +# ============================================================= +set TOP_DESIGN %top_design +set TB_TOP %tb_top + +set V_SRC_DIR %v_src_dir +set SIM_SRC_DIR %sim_src_dir +set TB_FILE %tb_file + +set XDC_FILE %xdc_file + +# SAIF output +set SAIF_OUT %saif_pre + +# ============================================================= +# 1. Create an in-memory project +# ============================================================= +create_project -force -part xc7k160tfbg484-1 power_eval_proj + +# Optional but often helpful for clean compilation behavior +set_property target_language %target_language [current_project] +set_property simulator_language Mixed [current_project] + +# ============================================================= +# 2. Add RTL sources to sources_1 +# ============================================================= +add_files -fileset sources_1 [list \ +%{rtl_sources} +] + +# Constraints (not required for behavioral sim, but harmless if you want periods/IO constraints loaded) +if {[file exists $XDC_FILE]} { + add_files -fileset constrs_1 $XDC_FILE +} + +# Ensure Vivado knows the intended RTL top (helps elaboration) +set_property top $TOP_DESIGN [get_filesets sources_1] + +# ============================================================= +# 3. Add testbench to sim_1 and set simulation top +# ============================================================= +add_files -fileset sim_1 [list \ +%{sim_sources} +] +set_property top $TB_TOP [get_filesets sim_1] +set_property top_lib xil_defaultlib [get_filesets sim_1] + +update_compile_order -fileset sources_1 +update_compile_order -fileset sim_1 + +# Disable auto simulation limit, allow SAIF for all signals +set_property -name {xsim.simulate.runtime} -value {0ns} -objects [get_filesets sim_1] +set_property -name {xsim.simulate.saif_all_signals} -value {true} -objects [get_filesets sim_1] + +# ============================================================= +# 4. Launch pre-synthesis simulation and dump SAIF +# ============================================================= +launch_simulation + +# If your DUT instance path is tb/duv_inst: +current_scope /tb/duv_inst + +# SAIF dump +open_saif $SAIF_OUT +log_saif [get_objects -r] + +# Run the sim +run all + +close_saif +close_sim + +%{synth_block} +%{impl_block} +# ============================================================= +# Exit +# ============================================================= +exit +""" diff --git a/tools/dynamatic/estimate_power/script_templates/report_power_template.py b/tools/dynamatic/power/script_templates/report_power_template.py similarity index 84% rename from tools/dynamatic/estimate_power/script_templates/report_power_template.py rename to tools/dynamatic/power/script_templates/report_power_template.py index 225b7c3946..82ff8fc2b5 100644 --- a/tools/dynamatic/estimate_power/script_templates/report_power_template.py +++ b/tools/dynamatic/power/script_templates/report_power_template.py @@ -3,7 +3,7 @@ # Date: %date # This tcl script is used to synthesis the design and report the power set TOP_DESIGN %design -set VHDL_SRC %hdlsrc +set HDL_SRC %hdlsrc # Read all source files %inputs @@ -16,7 +16,7 @@ # Report power read_saif -file %saif -report_power -file %{report_folder}/power_analysis.rpt +report_power -file %{report_folder}/power_estimation.rpt # Ciao! exit diff --git a/tools/dynamatic/estimate_power/script_templates/sim_do_template.py b/tools/dynamatic/power/script_templates/sim_do_template.py similarity index 90% rename from tools/dynamatic/estimate_power/script_templates/sim_do_template.py rename to tools/dynamatic/power/script_templates/sim_do_template.py index f6988e4770..591eda9355 100644 --- a/tools/dynamatic/estimate_power/script_templates/sim_do_template.py +++ b/tools/dynamatic/power/script_templates/sim_do_template.py @@ -10,7 +10,7 @@ project addfile %{designsrc} project calculateorder project compileall -eval vsim tb +eval vsim -debugdb -voptargs=+acc -onfinish stop tb power add %powerflag /duv_inst/* log -r * run -all diff --git a/tools/dynamatic/estimate_power/script_templates/synthesis_template.py b/tools/dynamatic/power/script_templates/synthesis_template.py similarity index 96% rename from tools/dynamatic/estimate_power/script_templates/synthesis_template.py rename to tools/dynamatic/power/script_templates/synthesis_template.py index 353bddd6e2..e7de1f86a4 100644 --- a/tools/dynamatic/estimate_power/script_templates/synthesis_template.py +++ b/tools/dynamatic/power/script_templates/synthesis_template.py @@ -5,7 +5,7 @@ # Date: %date # Define global variable set TOP_DESIGN %design -set VHDL_SRC %hdlsrc +set HDL_SRC %hdlsrc # Read all source files %inputs diff --git a/tools/dynamatic/estimate_power/script_templates/xdc_template.py b/tools/dynamatic/power/script_templates/xdc_template.py similarity index 100% rename from tools/dynamatic/estimate_power/script_templates/xdc_template.py rename to tools/dynamatic/power/script_templates/xdc_template.py diff --git a/tools/dynamatic/estimate_power/utils.py b/tools/dynamatic/power/utils.py similarity index 100% rename from tools/dynamatic/estimate_power/utils.py rename to tools/dynamatic/power/utils.py diff --git a/tools/dynamatic/scripts/simulate.sh b/tools/dynamatic/scripts/simulate.sh index 41933cf541..c8d583937e 100755 --- a/tools/dynamatic/scripts/simulate.sh +++ b/tools/dynamatic/scripts/simulate.sh @@ -14,7 +14,12 @@ KERNEL_NAME=$4 VIVADO_PATH=$5 VIVADO_FPU=$6 SIMULATOR_NAME=$7 -HDL_TYPE=$8 +CLOCK_PERIOD=$8 + +if [ -z "$CLOCK_PERIOD" ]; then + CLOCK_PERIOD="4.0" +fi +HDL_TYPE=$9 # Generated directories/files SIM_DIR="$(realpath "$OUTPUT_DIR/sim")" @@ -98,6 +103,7 @@ if [ "$VIVADO_FPU" = "true" ]; then --kernel-name="$KERNEL_NAME" \ --handshake-mlir="$OUTPUT_DIR/comp/handshake_export.mlir" \ --simulator="$SIMULATOR_NAME" \ + --clock-period="$CLOCK_PERIOD" \ --hdl="$HDL_TYPE" \ --vivado-fpu \ > "../report.txt" 2>&1 @@ -107,6 +113,7 @@ else --kernel-name="$KERNEL_NAME" \ --handshake-mlir="$OUTPUT_DIR/comp/handshake_export.mlir" \ --simulator="$SIMULATOR_NAME" \ + --clock-period="$CLOCK_PERIOD" \ --hdl="$HDL_TYPE" \ > "../report.txt" 2>&1 fi diff --git a/tools/hls-verifier/hls-verifier.cpp b/tools/hls-verifier/hls-verifier.cpp index 60a493e542..558af95fc1 100644 --- a/tools/hls-verifier/hls-verifier.cpp +++ b/tools/hls-verifier/hls-verifier.cpp @@ -146,6 +146,9 @@ int main(int argc, char **argv) { cl::desc("Simulator of choice (options: xsim, ghdl, vsim, verilator)"), cl::value_desc("Simulator of choice"), cl::init("vsim")); + cl::opt clockPeriod("clock-period", cl::desc("Clock period in ns"), + cl::value_desc("clock period"), cl::init(4.0)); + cl::opt hdlType("hdl", cl::desc("HDL used for simulation. Can either " "be 'vhdl' (default) or 'verilog'"), @@ -189,7 +192,8 @@ int main(int argc, char **argv) { HdlType hdl = (hdlType == "verilog") ? VERILOG : VHDL; - VerificationContext ctx(simPathName, hlsKernelName, &funcOp, vivadoFPU, hdl); + VerificationContext ctx(simPathName, hlsKernelName, &funcOp, vivadoFPU, + clockPeriod, hdl); // Generate hls_verify_.vhd vhdlTbCodegen(ctx); diff --git a/tools/hls-verifier/include/VerificationContext.h b/tools/hls-verifier/include/VerificationContext.h index d0b242beae..1ac720d109 100644 --- a/tools/hls-verifier/include/VerificationContext.h +++ b/tools/hls-verifier/include/VerificationContext.h @@ -37,9 +37,10 @@ enum HdlType { VHDL, VERILOG }; struct VerificationContext { VerificationContext(const std::string &simPath, const std::string &cFuvFunctionName, - handshake::FuncOp *funcOp, bool vivadoFPU, HdlType hdl) + handshake::FuncOp *funcOp, bool vivadoFPU, + double clockPeriod, HdlType hdl) : simPath(simPath), funcOp(funcOp), kernelName(cFuvFunctionName), - vivadoFPU(vivadoFPU), simLanguage(hdl) {} + vivadoFPU(vivadoFPU), clockPeriod(clockPeriod), simLanguage(hdl) {} static const char SEP = std::filesystem::path::preferred_separator; @@ -55,10 +56,14 @@ struct VerificationContext { // Whether to use Vivado FPU for floating-point operations bool vivadoFPU; + // Clock period in nanoseconds + double clockPeriod; + // Wheter to use VHDL or VERILOG for the testbench HdlType simLanguage; bool useVivadoFPU() const { return vivadoFPU; } + double getclockPeriod() const { return clockPeriod; } std::string getVhdlTestbenchPath() const { return getHdlSrcDir() + SEP + "tb_" + kernelName + ".vhd"; diff --git a/tools/hls-verifier/lib/HlsTb.cpp b/tools/hls-verifier/lib/HlsTb.cpp index aaa8bbdeca..38a4dc0846 100644 --- a/tools/hls-verifier/lib/HlsTb.cpp +++ b/tools/hls-verifier/lib/HlsTb.cpp @@ -6,7 +6,9 @@ // //===----------------------------------------------------------------------===// +#include #include +#include #include #include @@ -22,6 +24,14 @@ using std::tuple; +namespace { +std::string formatTimeNs(double ns, HdlType simLanguage) { + std::ostringstream os; + os << std::fixed << std::setprecision(2) << ns; + return os.str(); +} +} // namespace + // A Helper struct for connecting dynamatic's MemRef argument to a two-port RAM. // This grouping makes codegen more consistent in their code style, and // centralize the specialize handling in this struct. @@ -445,7 +455,8 @@ void getConstantDeclaration(mlir::raw_indented_ostream &os, ChannelToEndConnector c(type, argName); c.declareConstants(os, ctx, inputVectorPath, outputFilePath); } - declareConstant(ctx, os, "HALF_CLK_PERIOD", TIME, "2.00"); + declareConstant(ctx, os, "HALF_CLK_PERIOD", TIME, + formatTimeNs(ctx.getclockPeriod() / 2.0, ctx.simLanguage)); declareConstant(ctx, os, "RESET_LATENCY", TIME, "8.00"); declareConstant(ctx, os, "TRANSACTION_NUM", INTEGER, to_string(1)); } diff --git a/tools/hls-verifier/resources/templates_vhdl/template_simpackage.vhd b/tools/hls-verifier/resources/templates_vhdl/template_simpackage.vhd index 2e96643958..7c03eb93f0 100644 --- a/tools/hls-verifier/resources/templates_vhdl/template_simpackage.vhd +++ b/tools/hls-verifier/resources/templates_vhdl/template_simpackage.vhd @@ -25,6 +25,32 @@ end package; -- Main body of the simulation package ------------------------------------ package body sim_package is +--------------------------------------------------------------------------- + -- Helper to convert a 4-bit nibble to a lowercase hex character + function nibble_to_hex_char(nibble : std_logic_vector(3 downto 0)) + return character is + begin + case nibble is + when "0000" => return '0'; + when "0001" => return '1'; + when "0010" => return '2'; + when "0011" => return '3'; + when "0100" => return '4'; + when "0101" => return '5'; + when "0110" => return '6'; + when "0111" => return '7'; + when "1000" => return '8'; + when "1001" => return '9'; + when "1010" => return 'a'; + when "1011" => return 'b'; + when "1100" => return 'c'; + when "1101" => return 'd'; + when "1110" => return 'e'; + when "1111" => return 'f'; + when others => return 'x'; + end case; + end function nibble_to_hex_char; + --------------------------------------------------------------------------- -- Procedure to read tokens from input textfile procedure read_token(file filename : text; @@ -157,25 +183,20 @@ package body sim_package is -- Internal signals constant inp_len : integer := logicVec'length; constant str_len : integer := (inp_len + 3) / 4; - variable conv : string(1 to str_len); - variable ret : string (conv'range); + variable ret : string(1 to str_len); + variable padded : std_logic_vector(str_len * 4 - 1 downto 0); + variable nibble : std_logic_vector(3 downto 0); begin - -- Use in-built function (won't report errors) - conv := to_hex_string(logicVec); - - -- Convert string to lower case (alternatively change token compare function) - for i in conv'range loop - case (conv(i)) is - when 'A' => ret(i) := 'a'; - when 'B' => ret(i) := 'b'; - when 'C' => ret(i) := 'c'; - when 'D' => ret(i) := 'd'; - when 'E' => ret(i) := 'e'; - when 'F' => ret(i) := 'f'; - when others => ret(i) := conv(i); - end case; - end loop; + -- Zero-pad on the MSB side to a full nibble width. + padded := (others => '0'); + padded(inp_len - 1 downto 0) := logicVec; + + -- Convert to lower-case hex, MSB nibble first. + for i in 0 to str_len - 1 loop + nibble := padded((str_len * 4 - 1) - i * 4 downto (str_len * 4 - 4) - i * 4); + ret(i + 1) := nibble_to_hex_char(nibble); + end loop; -- Return the converted string return ret; @@ -183,4 +204,4 @@ package body sim_package is --------------------------------------------------------------------------- end package body; --- End of code +-- End of code \ No newline at end of file diff --git a/tools/hls-verifier/resources/templates_vhdl/template_single_argument.vhd b/tools/hls-verifier/resources/templates_vhdl/template_single_argument.vhd index 38d6739fa4..8eb9b484e5 100644 --- a/tools/hls-verifier/resources/templates_vhdl/template_single_argument.vhd +++ b/tools/hls-verifier/resources/templates_vhdl/template_single_argument.vhd @@ -37,6 +37,7 @@ end single_argument; architecture behav of single_argument is -- Internal signals signal emitToken, tokenEmitted : std_logic; + signal dout0_valid_r : std_logic; shared variable mem : std_logic_vector(DATA_WIDTH - 1 downto 0) := (others => '0'); begin @@ -110,19 +111,21 @@ begin end process file_to_mem; -- Transfer: mem-array -> RTL ports --------------------------------------- + dout0_valid <= dout0_valid_r; + mem_to_port0 : process (clk, rst) begin if (rst = '1') then tokenEmitted <= '0'; dout0 <= (others => '0'); - dout0_valid <= '0'; + dout0_valid_r <= '0'; elsif rising_edge(clk) then - if (not tokenEmitted) then + if (tokenEmitted = '0') then tokenEmitted <= '1'; dout0 <= mem; - dout0_valid <= '1'; + dout0_valid_r <= '1'; else - dout0_valid <= dout0_valid and (not dout0_ready); + dout0_valid_r <= dout0_valid_r and (not dout0_ready); end if; end if; end process mem_to_port0; @@ -202,4 +205,4 @@ begin ---------------------------------------------------------------------------- end behav; --- End of code +-- End of code \ No newline at end of file