From b9d1973116c556542e98fe61ddec82e144cb3744 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Mon, 19 Jan 2026 20:05:13 +0100 Subject: [PATCH 01/17] [Power] Add support for Verilog backend for power estimation flow --- .../estimate_power/estimate_power.py | 153 ++++++++++++------ .../script_templates/report_power_template.py | 2 +- 2 files changed, 107 insertions(+), 48 deletions(-) diff --git a/tools/dynamatic/estimate_power/estimate_power.py b/tools/dynamatic/estimate_power/estimate_power.py index a7604abdf3..b8a15428f0 100755 --- a/tools/dynamatic/estimate_power/estimate_power.py +++ b/tools/dynamatic/estimate_power/estimate_power.py @@ -29,7 +29,7 @@ class DesignFlag(Enum): class InputFlag(Enum): - PI = "pi" + PI = "pi" # Primary Inputs only ALL = "all" @@ -43,28 +43,35 @@ 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") + # + # 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, the testbench is in tb_.vhd 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 - 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) - + # + # Step 2: Generate the xdc file for synthesis in Vivado + # xdc_dict = { 'tcp': clock_period, 'halftcp': clock_period / 2 @@ -77,28 +84,50 @@ 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 + if hdl == "verilog": + sim_hdl_inputs.extend([ + f"project addfile {hdl_src_folder}/{f}" + for f in sorted(os.listdir(hdl_src_folder)) + if f.endswith(".v") + ]) # 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 {hdl_src_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 $VHDL_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 $VHDL_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-2019.1.1-bt 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/estimate_power/script_templates/report_power_template.py b/tools/dynamatic/estimate_power/script_templates/report_power_template.py index 225b7c3946..0bf1c29750 100644 --- a/tools/dynamatic/estimate_power/script_templates/report_power_template.py +++ b/tools/dynamatic/estimate_power/script_templates/report_power_template.py @@ -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 From 72208dce7e5d5758ac91a14143ff0fa728767bb1 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Mon, 19 Jan 2026 20:30:54 +0100 Subject: [PATCH 02/17] [Power] Change the name of the top folder containing power_estimate scripts to 'power' --- tools/dynamatic/{estimate_power => power}/estimate_power.py | 2 +- .../{estimate_power => power}/script_templates/__init__.py | 0 .../script_templates/report_power_template.py | 0 .../script_templates/sim_do_template.py | 0 .../script_templates/synthesis_template.py | 0 .../{estimate_power => power}/script_templates/xdc_template.py | 0 tools/dynamatic/{estimate_power => power}/utils.py | 0 7 files changed, 1 insertion(+), 1 deletion(-) rename tools/dynamatic/{estimate_power => power}/estimate_power.py (99%) rename tools/dynamatic/{estimate_power => power}/script_templates/__init__.py (100%) rename tools/dynamatic/{estimate_power => power}/script_templates/report_power_template.py (100%) rename tools/dynamatic/{estimate_power => power}/script_templates/sim_do_template.py (100%) rename tools/dynamatic/{estimate_power => power}/script_templates/synthesis_template.py (100%) rename tools/dynamatic/{estimate_power => power}/script_templates/xdc_template.py (100%) rename tools/dynamatic/{estimate_power => power}/utils.py (100%) diff --git a/tools/dynamatic/estimate_power/estimate_power.py b/tools/dynamatic/power/estimate_power.py similarity index 99% rename from tools/dynamatic/estimate_power/estimate_power.py rename to tools/dynamatic/power/estimate_power.py index b8a15428f0..94b27c8145 100755 --- a/tools/dynamatic/estimate_power/estimate_power.py +++ b/tools/dynamatic/power/estimate_power.py @@ -271,7 +271,7 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s ), default="pre" ) - p.add_argument("--vivado_cmd", type=str, required=False, help="Vivado command", default="vivado-2019.1.1-bt vivado") + 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() diff --git a/tools/dynamatic/estimate_power/script_templates/__init__.py b/tools/dynamatic/power/script_templates/__init__.py similarity index 100% rename from tools/dynamatic/estimate_power/script_templates/__init__.py rename to tools/dynamatic/power/script_templates/__init__.py 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 100% rename from tools/dynamatic/estimate_power/script_templates/report_power_template.py rename to tools/dynamatic/power/script_templates/report_power_template.py 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 100% rename from tools/dynamatic/estimate_power/script_templates/sim_do_template.py rename to tools/dynamatic/power/script_templates/sim_do_template.py diff --git a/tools/dynamatic/estimate_power/script_templates/synthesis_template.py b/tools/dynamatic/power/script_templates/synthesis_template.py similarity index 100% rename from tools/dynamatic/estimate_power/script_templates/synthesis_template.py rename to tools/dynamatic/power/script_templates/synthesis_template.py 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 From 3e93de0a605fc4d62915b50314e7d23f326f3eaf Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Mon, 19 Jan 2026 20:55:25 +0100 Subject: [PATCH 03/17] [Power] Update flags for estimate-power --- tools/dynamatic/dynamatic.cpp | 54 +++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index b87eeca11e..662d316b87 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -97,7 +97,6 @@ struct FrontendState { std::optional sourcePath = std::nullopt; std::string outputDir = "out"; - FrontendState(StringRef cwd) : cwd(cwd), dynamaticPath(cwd) {}; bool sourcePathIsSet(StringRef keyword); @@ -268,14 +267,16 @@ class SetCP : public Command { class SetOutputDir : public Command { public: SetOutputDir(FrontendState &state) - : Command("set-output-dir", "Sets the name of the dir to perform HLS in. If not set, defaults to 'out'", state) { + : Command("set-output-dir", + "Sets the name of the dir to perform HLS in. If not set, " + "defaults to 'out'", + state) { addPositionalArg({"out_dir", "out dir name"}); } CommandResult execute(CommandArguments &args) override; }; - class Compile : public Command { public: static constexpr llvm::StringLiteral FAST_TOKEN_DELIVERY = @@ -375,11 +376,20 @@ 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 to use for design's top-level"}); + 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; }; @@ -660,7 +670,8 @@ CommandResult SetOutputDir::execute(CommandArguments &args) { llvm::StringRef outputDir = args.positionals.front(); // reject trivial bad cases - if (outputDir.empty() || outputDir == "." || outputDir == ".." || outputDir.endswith("/")) + if (outputDir.empty() || outputDir == "." || outputDir == ".." || + outputDir.endswith("/")) return CommandResult::FAIL; // reject illegal chars @@ -825,14 +836,45 @@ CommandResult EstimatePower::execute(CommandArguments &args) { if (!state.sourcePathIsSet(keyword)) return CommandResult::FAIL; + // Get the HDL configuration + std::string hdl = "vhdl"; + + if (auto it = args.options.find(HDL); it != args.options.end()) { + if (it->second == "verilog") { + hdl = "verilog"; + } else if (it->second == "verilog-beta") { + hdl = "verilog-beta"; + } else if (it->second != "vhdl") { + llvm::errs() << "Unknow HDL '" << it->second + << "', possible options are 'vhdl'," + " and 'verilog'.\n"; + 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/estimate_power/estimate_power.py"; + state.dynamaticPath + "/tools/dynamatic/power/estimate_power.py"; // clang-format off return execCmd( "python", script, "--output_dir", state.getOutputDir(), "--kernel_name", state.getKernelName(), + "--hdl", hdl, + "--synth", stage, "--cp", floatToString(state.targetCP, 3) ); // clang-format on From 1f271b8b1f92c6886e2813db66ec66d36969bac1 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Mon, 19 Jan 2026 22:26:51 +0100 Subject: [PATCH 04/17] [Sim] Make simpackage.vhd and single_argument.vhd compatible and runnable with xsim in Vivado --- .../templates_vhdl/template_simpackage.vhd | 57 +++++++++++++------ .../template_single_argument.vhd | 13 +++-- 2 files changed, 47 insertions(+), 23 deletions(-) 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 From c2fc69ee2353bc041b378d096eeb8e957e89ba38 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Sun, 25 Jan 2026 19:40:42 +0100 Subject: [PATCH 05/17] [HLS-verf] pass clock period ot hls-verifier to change the HALF_CLOCK_PERIOD in the generated testbench to fix the timing issue in timing simulation. Add initial implementation for power-eval flow as well --- tools/dynamatic/dynamatic.cpp | 76 ++++++++++++++++++- tools/hls-verifier/hls-verifier.cpp | 7 +- .../include/VerificationContext.h | 9 ++- tools/hls-verifier/lib/HlsVhdlTb.cpp | 13 +++- 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index 662d316b87..9cf8dfdc16 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -384,7 +384,7 @@ class EstimatePower : public Command { "Estimate the power consumption of the design using switching " "activity from simulation.", state) { - addOption({HDL, "HDL to use for design's top-level"}); + 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 " @@ -394,6 +394,28 @@ class EstimatePower : public Command { CommandResult execute(CommandArguments &args) override; }; +class PowerEval : public Command { +public: + static constexpr llvm::StringLiteral HDL = "hdl"; + static constexpr llvm::StringLiteral STAGE = "stage"; + + PowerEval(FrontendState &state) + : Command( + "power-eval", + "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 simuulation with xsim and " + "vector-based power " + "evaluation, synthesis or implementation, defaul : synth"}); + } + + CommandResult execute(CommandArguments &args) override; +}; + class FrontendCommands { public: StringMap> cmds; @@ -799,7 +821,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); + simulator, floatToString(state.targetCP, 2)); } CommandResult Visualize::execute(CommandArguments &args) { @@ -880,6 +902,55 @@ CommandResult EstimatePower::execute(CommandArguments &args) { // clang-format on } +CommandResult PowerEval::execute(CommandArguments &args) { + // We need the source path to be set + if (!state.sourcePathIsSet(keyword)) + return CommandResult::FAIL; + + // Get the HDL configuration + std::string hdl = "vhdl"; + + if (auto it = args.options.find(HDL); it != args.options.end()) { + if (it->second == "verilog") { + hdl = "verilog"; + } else if (it->second == "verilog-beta") { + hdl = "verilog-beta"; + } else if (it->second != "vhdl") { + llvm::errs() << "Unknow HDL '" << it->second + << "', possible options are 'vhdl'," + " and 'verilog'.\n"; + 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; + } + } + + std::string script = + state.dynamaticPath + "/tools/dynamatic/power/power_eval.py"; + + // clang-format off + return execCmd( + "python", script, + "--output_dir", state.getOutputDir(), + "--kernel_name", state.getKernelName(), + "--hdl", hdl, + "--synth", stage, + "--cp", floatToString(state.targetCP, 3) + ); + // clang-format on +} + static StringRef removeComment(StringRef input) { if (size_t cutAt = input.find('#'); cutAt != std::string::npos) return input.take_front(cutAt); @@ -953,6 +1024,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/hls-verifier/hls-verifier.cpp b/tools/hls-verifier/hls-verifier.cpp index a289b3c1fa..2844c6cb1a 100644 --- a/tools/hls-verifier/hls-verifier.cpp +++ b/tools/hls-verifier/hls-verifier.cpp @@ -143,6 +143,10 @@ int main(int argc, char **argv) { "simulator", cl::desc("Simulator of choice (options: xsim, ghdl, vsim)"), 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::ParseCommandLineOptions(argc, argv, R"PREFIX( This is the hls-verifier tool for comparing C and VHDL/Verilog outputs. @@ -178,7 +182,8 @@ int main(int argc, char **argv) { handshake::FuncOp funcOp = dyn_cast(modOp->lookupSymbol(hlsKernelName)); - VerificationContext ctx(simPathName, hlsKernelName, &funcOp, vivadoFPU); + VerificationContext ctx(simPathName, hlsKernelName, &funcOp, vivadoFPU, + clockPeriod); // Generate hls_verify_.vhd vhdlTbCodegen(ctx); diff --git a/tools/hls-verifier/include/VerificationContext.h b/tools/hls-verifier/include/VerificationContext.h index 28a59a138d..e7ac5552c0 100644 --- a/tools/hls-verifier/include/VerificationContext.h +++ b/tools/hls-verifier/include/VerificationContext.h @@ -34,9 +34,10 @@ static const std::string HLS_VERIFY_DIR = "HLS_VERIFY"; struct VerificationContext { VerificationContext(const std::string &simPath, const std::string &cFuvFunctionName, - handshake::FuncOp *funcOp, bool vivadoFPU) + handshake::FuncOp *funcOp, bool vivadoFPU, + double clockPeriod) : simPath(simPath), funcOp(funcOp), kernelName(cFuvFunctionName), - vivadoFPU(vivadoFPU) {} + vivadoFPU(vivadoFPU), clockPeriod(clockPeriod) {} static const char SEP = std::filesystem::path::preferred_separator; @@ -52,7 +53,11 @@ struct VerificationContext { // Whether to use Vivado FPU for floating-point operations bool vivadoFPU; + // Clock period in nanoseconds + double clockPeriod; + 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/HlsVhdlTb.cpp b/tools/hls-verifier/lib/HlsVhdlTb.cpp index a35f5b5279..12d1988cb1 100644 --- a/tools/hls-verifier/lib/HlsVhdlTb.cpp +++ b/tools/hls-verifier/lib/HlsVhdlTb.cpp @@ -6,7 +6,9 @@ // //===----------------------------------------------------------------------===// +#include #include +#include #include #include "HlsVhdlTb.h" @@ -20,6 +22,14 @@ using std::tuple; +namespace { +std::string formatTimeNs(double ns) { + std::ostringstream os; + os << std::fixed << std::setprecision(2) << ns << " 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. @@ -353,7 +363,8 @@ void getConstantDeclaration(mlir::raw_indented_ostream &os, ChannelToEndConnector c(type, argName); c.declareConstants(os, inputVectorPath, outputFilePath); } - declareConstant(os, "HALF_CLK_PERIOD", "TIME", "2.00 ns"); + declareConstant(os, "HALF_CLK_PERIOD", "TIME", + formatTimeNs(ctx.getclockPeriod() / 2.0)); declareConstant(os, "RESET_LATENCY", "TIME", "10.00 ns"); declareConstant(os, "TRANSACTION_NUM", "INTEGER", to_string(1)); } From 05e17c6852c74c69fe2e99c862a3a0a4e239474d Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Tue, 27 Jan 2026 16:11:39 +0100 Subject: [PATCH 06/17] Initial commit for the power_evaluation tcl generation script --- tools/dynamatic/power/estimate_power.py | 4 +- tools/dynamatic/power/power_eval.py | 360 ++++++++++++++++++ .../power/script_templates/__init__.py | 1 + .../script_templates/eval_power_template.py | 88 +++++ 4 files changed, 450 insertions(+), 3 deletions(-) create mode 100644 tools/dynamatic/power/power_eval.py create mode 100644 tools/dynamatic/power/script_templates/eval_power_template.py diff --git a/tools/dynamatic/power/estimate_power.py b/tools/dynamatic/power/estimate_power.py index 94b27c8145..b0317e05f5 100755 --- a/tools/dynamatic/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 * @@ -271,7 +269,7 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s ), default="pre" ) - p.add_argument("--vivado_cmd", type=str, required=False, help="Vivado command", default="vivado") + p.add_argument("--vivado_cmd", type=str, required=False, help="Vivado command", default="vivado-2019.1.1 vivado") p.add_argument("--cp", type=float, required=True, help="Clock period for synthesis") args = p.parse_args() diff --git a/tools/dynamatic/power/power_eval.py b/tools/dynamatic/power/power_eval.py new file mode 100644 index 0000000000..2549081fcb --- /dev/null +++ b/tools/dynamatic/power/power_eval.py @@ -0,0 +1,360 @@ +#!/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 + 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) + check_else_create(verify_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 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 (VHDL support files and testbench) + sim_files = _list_files(sim_src_dir, [".vhd"]) + if hdl == "vhdl": + design_vhd = f"{kernel_name}.vhd" + sim_files = [f for f in sim_files if f != design_vhd] + 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_fun_xml = os.path.join(power_dir, "post_synth_fun_power.xml") + 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_fun_xml = os.path.join(power_dir, "post_impl_fun_power.xml") + post_impl_time_rpt = os.path.join(power_dir, "post_impl_time_power.rpt") + + flatten_line = "" + if flatten_hierarchy == "none": + 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}", + f"report_power -file {post_synth_fun_xml} -format xml", + "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", + "", + "# For now, we force the simulation to stop after 8000ns", + "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}", + f"report_power -file {post_impl_fun_xml} -format xml", + "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", + choices=["none", "default"], + required=False, + default="none", + help=( + "Control hierarchy flattening during synthesis. Use 'none' to emit " + "the FLATTEN_HIERARCHY none property, or 'default' 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() + + stage = args.stage if args.stage is not None else "synth" + + 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/power/script_templates/__init__.py b/tools/dynamatic/power/script_templates/__init__.py index fe6a180575..f1aab40cd1 100644 --- a/tools/dynamatic/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..79aa2858fd --- /dev/null +++ b/tools/dynamatic/power/script_templates/eval_power_template.py @@ -0,0 +1,88 @@ +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 pre_synth_sim + +# 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 +""" From 4bb0548db19a442559cbf225e9a3779095841932 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Tue, 27 Jan 2026 16:12:00 +0100 Subject: [PATCH 07/17] Add clock period variable in simulate.sh --- tools/dynamatic/scripts/simulate.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/dynamatic/scripts/simulate.sh b/tools/dynamatic/scripts/simulate.sh index 2de89856c2..54ae1d5ca7 100755 --- a/tools/dynamatic/scripts/simulate.sh +++ b/tools/dynamatic/scripts/simulate.sh @@ -14,6 +14,11 @@ KERNEL_NAME=$4 VIVADO_PATH=$5 VIVADO_FPU=$6 SIMULATOR_NAME=$7 +CLOCK_PERIOD=$8 + +if [ -z "$CLOCK_PERIOD" ]; then + CLOCK_PERIOD="4.0" +fi # Generated directories/files SIM_DIR="$(realpath "$OUTPUT_DIR/sim")" @@ -89,6 +94,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" \ --vivado-fpu \ > "../report.txt" 2>&1 else @@ -97,6 +103,7 @@ else --kernel-name="$KERNEL_NAME" \ --handshake-mlir="$OUTPUT_DIR/comp/handshake_export.mlir" \ --simulator="$SIMULATOR_NAME" \ + --clock-period="$CLOCK_PERIOD" \ > "../report.txt" 2>&1 fi exit_on_fail "Simulation failed" "Simulation succeeded" From d1528a0095b6986cff8b40d92312040b3ac572bf Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Tue, 27 Jan 2026 18:53:31 +0100 Subject: [PATCH 08/17] Fix bug in the added evaluate-power flag --- tools/dynamatic/dynamatic.cpp | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index 9cf8dfdc16..3a97061a68 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -398,19 +398,25 @@ 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( - "power-eval", + "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 simuulation with xsim and " + "Stage (synth or impl) to perform simulation with xsim and " "vector-based power " - "evaluation, synthesis or implementation, defaul : synth"}); + "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; @@ -936,6 +942,20 @@ CommandResult PowerEval::execute(CommandArguments &args) { } } + // 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/power/power_eval.py"; @@ -945,7 +965,8 @@ CommandResult PowerEval::execute(CommandArguments &args) { "--output_dir", state.getOutputDir(), "--kernel_name", state.getKernelName(), "--hdl", hdl, - "--synth", stage, + "--stage", stage, + (flattenHierarchy == "1" ? "--flatten_hierarchy" : ""), "--cp", floatToString(state.targetCP, 3) ); // clang-format on From 865e6717713a8ee4cc1916ee9d73b23360cecfa2 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Tue, 27 Jan 2026 23:34:19 +0100 Subject: [PATCH 09/17] Change the default vivado command in estimate_power.py script --- tools/dynamatic/power/estimate_power.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dynamatic/power/estimate_power.py b/tools/dynamatic/power/estimate_power.py index b0317e05f5..99310e4a08 100755 --- a/tools/dynamatic/power/estimate_power.py +++ b/tools/dynamatic/power/estimate_power.py @@ -269,7 +269,7 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s ), default="pre" ) - p.add_argument("--vivado_cmd", type=str, required=False, help="Vivado command", default="vivado-2019.1.1 vivado") + 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() From 02b45347b8cee39e37d0475b89e4053d881116fb Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Tue, 27 Jan 2026 23:53:35 +0100 Subject: [PATCH 10/17] Finalize power_extraction.tcl generation script --- tools/dynamatic/power/power_eval.py | 21 +++++++------------ .../script_templates/eval_power_template.py | 7 ++----- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/tools/dynamatic/power/power_eval.py b/tools/dynamatic/power/power_eval.py index 2549081fcb..f7ce566184 100644 --- a/tools/dynamatic/power/power_eval.py +++ b/tools/dynamatic/power/power_eval.py @@ -79,6 +79,7 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, print("[INFO] Generating power evaluation script") # Normalize HDL selection + # For now, verilog-beta is treated as verilog hdl = _normalize_hdl(hdl) # Resolve paths @@ -97,14 +98,13 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, return check_else_create(power_dir) - check_else_create(verify_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 in Vivado + # Generate the xdc file for synthesis and implementation in Vivado xdc_dict = { "tcp": clock_period, "halftcp": clock_period / 2 @@ -148,15 +148,13 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, 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_fun_xml = os.path.join(power_dir, "post_synth_fun_power.xml") 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_fun_xml = os.path.join(power_dir, "post_impl_fun_power.xml") post_impl_time_rpt = os.path.join(power_dir, "post_impl_time_power.rpt") flatten_line = "" - if flatten_hierarchy == "none": + if not flatten_hierarchy: flatten_line = ( "set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY none " "[get_runs synth_1]" @@ -205,7 +203,6 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, "# =============================================================", f"read_saif -file {post_synth_fun_saif}", f"report_power -file {post_synth_fun_rpt}", - f"report_power -file {post_synth_fun_xml} -format xml", "reset_switching_activity -all", "", f"read_saif -file {post_synth_time_saif}", @@ -257,7 +254,6 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, "# =============================================================", f"read_saif -file {post_impl_fun_saif}", f"report_power -file {post_impl_fun_rpt}", - f"report_power -file {post_impl_fun_xml} -format xml", "reset_switching_activity -all", "", f"read_saif -file {post_impl_time_saif}", @@ -327,13 +323,11 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, default="synth" ) p.add_argument( - "--flatten-hierarchy", - choices=["none", "default"], - required=False, - default="none", + "--flatten_hierarchy", + action="store_true", help=( - "Control hierarchy flattening during synthesis. Use 'none' to emit " - "the FLATTEN_HIERARCHY none property, or 'default' for the fully flattened flow." + "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") @@ -346,6 +340,7 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, args = p.parse_args() + # Default to synthesis stage if not specified stage = args.stage if args.stage is not None else "synth" main( diff --git a/tools/dynamatic/power/script_templates/eval_power_template.py b/tools/dynamatic/power/script_templates/eval_power_template.py index 79aa2858fd..6d97cc8cc8 100644 --- a/tools/dynamatic/power/script_templates/eval_power_template.py +++ b/tools/dynamatic/power/script_templates/eval_power_template.py @@ -1,5 +1,4 @@ -vivado_power_evaluation_tcl = r""" -# Date: %date +vivado_power_evaluation_tcl = r"""# Date: %date # Simulation with XSim + SAIF dump + power extraction # Command %vivado_cmd -mode batch -source power_extraction.tcl # ============================================================= @@ -19,11 +18,10 @@ # SAIF output set SAIF_OUT %saif_pre - # ============================================================= # 1. Create an in-memory project # ============================================================= -create_project -force -part xc7k160tfbg484-1 pre_synth_sim +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] @@ -80,7 +78,6 @@ %{synth_block} %{impl_block} - # ============================================================= # Exit # ============================================================= From 74f367250529d86d3b9d7e1aab09f3354194451d Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Fri, 30 Jan 2026 02:22:14 +0100 Subject: [PATCH 11/17] Fix the problem during testbench generation --- tools/hls-verifier/lib/HlsTb.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/hls-verifier/lib/HlsTb.cpp b/tools/hls-verifier/lib/HlsTb.cpp index 98c34b7bf4..9451677d8d 100644 --- a/tools/hls-verifier/lib/HlsTb.cpp +++ b/tools/hls-verifier/lib/HlsTb.cpp @@ -25,9 +25,11 @@ using std::tuple; namespace { -std::string formatTimeNs(double ns) { +std::string formatTimeNs(double ns, HdlType simLanguage) { std::ostringstream os; - os << std::fixed << std::setprecision(2) << ns << " ns"; + os << std::fixed << std::setprecision(2) << ns; + if (simLanguage == VHDL) + os << " ns"; return os.str(); } } // namespace @@ -456,7 +458,7 @@ void getConstantDeclaration(mlir::raw_indented_ostream &os, c.declareConstants(os, ctx, inputVectorPath, outputFilePath); } declareConstant(ctx, os, "HALF_CLK_PERIOD", TIME, - formatTimeNs(ctx.getclockPeriod() / 2.0)); + formatTimeNs(ctx.getclockPeriod() / 2.0, ctx.simLanguage)); declareConstant(ctx, os, "RESET_LATENCY", TIME, "8.00"); declareConstant(ctx, os, "TRANSACTION_NUM", INTEGER, to_string(1)); } From 006e79a60c98ddfa34d7255aa11be7e924f4af3b Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Fri, 30 Jan 2026 17:25:53 +0100 Subject: [PATCH 12/17] Fix bug when generating clock period in the testbench --- tools/hls-verifier/lib/HlsTb.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/hls-verifier/lib/HlsTb.cpp b/tools/hls-verifier/lib/HlsTb.cpp index 9451677d8d..38a4dc0846 100644 --- a/tools/hls-verifier/lib/HlsTb.cpp +++ b/tools/hls-verifier/lib/HlsTb.cpp @@ -28,8 +28,6 @@ namespace { std::string formatTimeNs(double ns, HdlType simLanguage) { std::ostringstream os; os << std::fixed << std::setprecision(2) << ns; - if (simLanguage == VHDL) - os << " ns"; return os.str(); } } // namespace From 9af7d7c125e4297fd3220b39563caa64e5c83d33 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Fri, 30 Jan 2026 20:50:35 +0100 Subject: [PATCH 13/17] Ensure verilog simulation will not exit early in Modelsim --- tools/dynamatic/power/script_templates/sim_do_template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/dynamatic/power/script_templates/sim_do_template.py b/tools/dynamatic/power/script_templates/sim_do_template.py index f6988e4770..591eda9355 100644 --- a/tools/dynamatic/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 From c763b8bc3c86a718298b01d2609200088d88c8fc Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Fri, 30 Jan 2026 21:51:27 +0100 Subject: [PATCH 14/17] Update estimate_power script for the new hls_verifier --- tools/dynamatic/power/estimate_power.py | 18 ++++++++++-------- .../script_templates/report_power_template.py | 2 +- .../script_templates/synthesis_template.py | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/tools/dynamatic/power/estimate_power.py b/tools/dynamatic/power/estimate_power.py index 99310e4a08..e1d7223cb9 100755 --- a/tools/dynamatic/power/estimate_power.py +++ b/tools/dynamatic/power/estimate_power.py @@ -61,8 +61,9 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s print(f"[ERROR] {hdl_src_folder} not found. Please run the 'write-hdl' command") return - # For both VHDL and Verilog, the testbench is in tb_.vhd - tb_file = os.path.join(output_dir, "sim", "HDL_SRC", f"tb_{kernel_name}.vhd") + # 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 @@ -95,11 +96,12 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s 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 {hdl_src_folder}/{f}" - for f in sorted(os.listdir(hdl_src_folder)) - if f.endswith(".v") + 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 @@ -107,7 +109,7 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s if hdl != "verilog": sim_hdl_inputs.remove(f"project addfile {hdl_src_folder}/{kernel_name}.vhd") else: - sim_hdl_inputs.remove(f"project addfile {hdl_src_folder}/{kernel_name}.v") + sim_hdl_inputs.remove(f"project addfile {sim_folder}/{kernel_name}.v") sim_hdl_inputs = "\n".join(sim_hdl_inputs) @@ -118,12 +120,12 @@ def main(output_dir, kernel_name, hdl, clock_period, vivado_cmd="vivado", post_s # As a list of imports for report_power.tcl if hdl == "verilog": synth_hdl_inputs = "\n" + "\n".join( - f"read_verilog $VHDL_SRC/{f}" + 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 $VHDL_SRC/{f}" + f"read_vhdl -vhdl2008 $HDL_SRC/{f}" for f in sorted(os.listdir(hdl_src_folder)) if f.endswith(".vhd")) diff --git a/tools/dynamatic/power/script_templates/report_power_template.py b/tools/dynamatic/power/script_templates/report_power_template.py index 0bf1c29750..82ff8fc2b5 100644 --- a/tools/dynamatic/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 diff --git a/tools/dynamatic/power/script_templates/synthesis_template.py b/tools/dynamatic/power/script_templates/synthesis_template.py index 353bddd6e2..e7de1f86a4 100644 --- a/tools/dynamatic/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 From dc1003b922a0932f074a57517113086ffe09c91f Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Fri, 30 Jan 2026 22:10:05 +0100 Subject: [PATCH 15/17] remove redudant hdl parameter parsing --- tools/dynamatic/dynamatic.cpp | 36 ++--------------------------------- 1 file changed, 2 insertions(+), 34 deletions(-) diff --git a/tools/dynamatic/dynamatic.cpp b/tools/dynamatic/dynamatic.cpp index a24cf18646..ab09068133 100644 --- a/tools/dynamatic/dynamatic.cpp +++ b/tools/dynamatic/dynamatic.cpp @@ -882,22 +882,6 @@ CommandResult EstimatePower::execute(CommandArguments &args) { if (!state.sourcePathIsSet(keyword)) return CommandResult::FAIL; - // Get the HDL configuration - std::string hdl = "vhdl"; - - if (auto it = args.options.find(HDL); it != args.options.end()) { - if (it->second == "verilog") { - hdl = "verilog"; - } else if (it->second == "verilog-beta") { - hdl = "verilog-beta"; - } else if (it->second != "vhdl") { - llvm::errs() << "Unknow HDL '" << it->second - << "', possible options are 'vhdl'," - " and 'verilog'.\n"; - return CommandResult::FAIL; - } - } - // Get simulation stage configuration std::string stage = "pre"; @@ -919,7 +903,7 @@ CommandResult EstimatePower::execute(CommandArguments &args) { "python", script, "--output_dir", state.getOutputDir(), "--kernel_name", state.getKernelName(), - "--hdl", hdl, + "--hdl", state.hdl, "--synth", stage, "--cp", floatToString(state.targetCP, 3) ); @@ -931,22 +915,6 @@ CommandResult PowerEval::execute(CommandArguments &args) { if (!state.sourcePathIsSet(keyword)) return CommandResult::FAIL; - // Get the HDL configuration - std::string hdl = "vhdl"; - - if (auto it = args.options.find(HDL); it != args.options.end()) { - if (it->second == "verilog") { - hdl = "verilog"; - } else if (it->second == "verilog-beta") { - hdl = "verilog-beta"; - } else if (it->second != "vhdl") { - llvm::errs() << "Unknow HDL '" << it->second - << "', possible options are 'vhdl'," - " and 'verilog'.\n"; - return CommandResult::FAIL; - } - } - // Get simulation stage configuration std::string stage = "synth"; @@ -982,7 +950,7 @@ CommandResult PowerEval::execute(CommandArguments &args) { "python", script, "--output_dir", state.getOutputDir(), "--kernel_name", state.getKernelName(), - "--hdl", hdl, + "--hdl", state.hdl, "--stage", stage, (flattenHierarchy == "1" ? "--flatten_hierarchy" : ""), "--cp", floatToString(state.targetCP, 3) From 5cf95f9d1dd60587c0d7bb695312199e86593184 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Sat, 31 Jan 2026 01:31:52 +0100 Subject: [PATCH 16/17] Adapt script to generate power extraction script --- tools/dynamatic/power/power_eval.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/dynamatic/power/power_eval.py b/tools/dynamatic/power/power_eval.py index f7ce566184..2357282ba4 100644 --- a/tools/dynamatic/power/power_eval.py +++ b/tools/dynamatic/power/power_eval.py @@ -123,11 +123,14 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, print(f"[ERROR] No RTL sources found in {hdl_src_dir}") return - # Collect simulation sources (VHDL support files and testbench) - sim_files = _list_files(sim_src_dir, [".vhd"]) + # Collect simulation sources if hdl == "vhdl": + sim_files = _list_files(sim_src_dir, [".vhd"]) 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 @@ -243,8 +246,8 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, f"open_vcd {post_impl_time_vcd}", "log_vcd", "", - "# For now, we force the simulation to stop after 8000ns", - "run 8000ns", + "# Only works for Verilog designs for now", + "run all", "close_saif", "close_vcd", "close_sim", From 03f76174a2349ea50538e04ddd1f12c9762c6789 Mon Sep 17 00:00:00 2001 From: Jiantao Liu Date: Sat, 31 Jan 2026 02:20:26 +0100 Subject: [PATCH 17/17] Early exit when hdl is VHDL for power analysis --- tools/dynamatic/power/power_eval.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/dynamatic/power/power_eval.py b/tools/dynamatic/power/power_eval.py index 2357282ba4..c30d176286 100644 --- a/tools/dynamatic/power/power_eval.py +++ b/tools/dynamatic/power/power_eval.py @@ -125,7 +125,7 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, # Collect simulation sources if hdl == "vhdl": - sim_files = _list_files(sim_src_dir, [".vhd"]) + 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: @@ -247,7 +247,7 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, "log_vcd", "", "# Only works for Verilog designs for now", - "run all", + "run 8000ns", "close_saif", "close_vcd", "close_sim", @@ -345,6 +345,11 @@ def main(output_dir, kernel_name, hdl, clock_period, stage, flatten_hierarchy, # 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,