├── LICENSE ├── README.md └── USAGI.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ChunChih3310 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # USAGI: Ultra Synthesis Automation and Gate-Level Integration 2 | 3 | ![32cda790-5285-45f7-8026-ba646ca99b81](https://github.com/user-attachments/assets/2b183679-7abd-42a3-a03a-2cda48350568) 4 | 5 | USAGI is a Python script designed to automate the process of synthesizing and performing gate-level simulations for digital designs across a range of cycle times. It helps designers find the optimal balance between area, performance, and latency by automating the synthesis process for multiple configurations and selecting the best result based on a customizable performance function. 6 | 7 | 8 | ## Features 9 | 10 | - Automates synthesis for a range of cycle times. 11 | - Customizable performance function to suit specific design goals. 12 | - Parallel processing to speed up the synthesis process. 13 | - Automatic selection and retention of the best-performing design. 14 | - Perform gate-level simulation on the best result. 15 | - Generates a CSV file with synthesis results for easy analysis. 16 | 17 | ## Prerequisites 18 | 19 | - **Operating System**: Linux-based system 20 | - **Python**: Python 3.x 21 | - **External Tools**: 22 | - `dcnxt_shell` for synthesis (Design Compiler) 23 | - `vcs` for gate-level simulation (Synopsys VCS) 24 | - `08_check` script to check synthesis results 25 | - **Python Libraries**: Standard libraries (`os`, `re`, `csv`, `subprocess`, `time`, `shutil`, `threading`, `concurrent.futures`) 26 | 27 | ## Usage 28 | 29 | 1. **Clone the Repository**: 30 | 31 | ```bash 32 | git clone https://github.com/ChunChih3310/Ultra-Synthesis-Automation-and-Gate-Level-Integration.git 33 | cd Ultra-Synthesis-Automation-and-Gate-Level-Integration 34 | ``` 35 | 36 | 2. **Configuration**: 37 | 38 | Open the script `USAGI.py` in a text editor and modify the following variables in the Configuration section: 39 | 40 | ```python 41 | # ================================================== 42 | # Configuration 43 | # ================================================== 44 | latency = 1 # Latency of the design 45 | 46 | upper_bound = 5.0 # Upper bound of the cycle time range 47 | lower_bound = 3.0 # Lower bound of the cycle time range 48 | step = 0.1 # Step size for cycle time increments 49 | reverse = True # If True, cycle times will be in descending order 50 | 51 | dummy_dir = os.path.expanduser('~/dummy_dir') # Temporary directory to store files 52 | lab_source_dir = os.path.expanduser('~/Midterm_Project_bk/Exercise') # Path to your lab files 53 | max_workers = 100 # Number of parallel synthesis processes 54 | ``` 55 | 56 | - `latency`: Latency of the design. 57 | - `upper_bound`: Upper bound of the cycle time range. 58 | - `lower_bound`: Lower bound of the cycle time range. 59 | - `step`: Step size for cycle time increments. 60 | - `reverse`: If True, cycle times will be in descending order. 61 | - `dummy_dir`: Temporary directory to store files. 62 | - `lab_source_dir`: Path to your lab files. 63 | - `max_workers`: Maximum number of parallel synthesis processes. 64 | 65 | 3. **Performance Function**: 66 | 67 | The script uses a performance function to evaluate and compare different synthesis results. You can customize this function according to your optimization goals. 68 | 69 | Modify the performance_func function to suit your performance criteria. 70 | ```python 71 | # ================================================== 72 | # Performance Calculation 73 | # ================================================== 74 | def performance_func(area, cycle_time, latency): 75 | return (area) * (cycle_time ** 2) * (latency ** 2) 76 | ``` 77 | 78 | 4. **Run the Script**: 79 | 80 | Run the script using the following command: 81 | 82 | ```bash 83 | python3 USAGI.py 84 | ``` 85 | 86 | The script will perform the following steps: 87 | 1. Copy your lab files to a temporary directory for each cycle time. 88 | 2. Modify the syn.tcl file to set the cycle time for synthesis. 89 | 3. Run synthesis for each cycle time in parallel. 90 | 4. Collect synthesis results, including area and performance metrics. (Only the directory with the best performance will be retained.) 91 | 5. Identify the best-performing design based on the performance function. 92 | 6. Perform gate-level simulation on the best result. 93 | 7. Generate a CSV file with synthesis results. 94 | 95 | ## License 96 | This project is licensed under the MIT License 97 | 98 | ## Contact 99 | 100 | - **Author**: Chun-Chih, Yu 101 | - **Email**: [chunchih.ee13@nycu.edu.tw](mailto:chunchih.ee13@nycu.edu.tw) 102 | -------------------------------------------------------------------------------- /USAGI.py: -------------------------------------------------------------------------------- 1 | ''' 2 | USAGI: Ultra Synthesis Automation and Gate-Level Integration 3 | Author : Chun-Chih, Yu 4 | Email : chunchih.ee13@nycu.edu.tw 5 | Date : 2024-11-20 6 | Description: 7 | 8 | Usage : 9 | 1. Modify the "latency", "upper_bound", "lower_bound", "step", "reverse", "dummy_dir", and "lab_source_dir" variables. 10 | 2. Modify the "performance_func" function to the desired performance calculation. 11 | 3. Run the script using Python 3 12 | 13 | Dependencies: 14 | - Python 3 15 | - Standard Python libraries 16 | - External commands: dcnxt_shell(for synthesis), vcs(for gate-level simulation), 08_check(check the synthesis result) 17 | ''' 18 | 19 | import os 20 | import re 21 | import csv 22 | import subprocess 23 | import time 24 | import shutil 25 | import threading 26 | import concurrent.futures 27 | 28 | # ================================================== 29 | # Configuration 30 | # ================================================== 31 | latency = 1 # Latency of the design 32 | upper_bound = 5.0 33 | lower_bound = 3.0 34 | step = 0.1 35 | reverse = True # if True, the cycle times will be in descending order 36 | dummy_dir = os.path.expanduser('~/dummy_dir') # Dummy directory to store temporary files for different cycle times 37 | lab_source_dir = os.path.expanduser('~/Midterm_Project_bk/Exercise') # Path to the lab files, e.g., ~/Lab04/Exercise 38 | max_workers = 100 # Number of parallel synthesis processes 39 | 40 | # ================================================== 41 | # Performance Calculation 42 | # ================================================== 43 | def performance_func(area, cycle_time, latency): 44 | return (area) * (cycle_time ** 2) * (latency ** 2) 45 | 46 | 47 | # ================================================== 48 | GREEN = "\033[92m" 49 | RED = "\033[91m" 50 | RESET = "\033[0m" 51 | 52 | def print_img(): 53 | print(r""" 54 | """) 55 | 56 | def print_sep(text, total_length=100, end="\n"): 57 | text_length = len(re.sub(r'\033\[\d+m', '', text)) 58 | eq_length = (total_length - text_length - 2) // 2 59 | separator = "-" * eq_length + " " + text + " " + "-" * eq_length 60 | if len(separator) < total_length: 61 | separator += "-" 62 | print(separator, end=end) 63 | 64 | def run_synthesis(cycle, cycle_dir): 65 | syn_command = 'dcnxt_shell -f syn.tcl -x "set_host_options -max_cores 8" -output_log_file syn.log && ./08_check' 66 | syn_working_dir = os.path.join(cycle_dir, '02_SYN') 67 | 68 | process = subprocess.run( 69 | syn_command, 70 | shell=True, 71 | cwd=syn_working_dir, 72 | stdout=subprocess.PIPE, 73 | stderr=subprocess.PIPE, 74 | universal_newlines=True, 75 | encoding='utf-8', 76 | errors='replace' 77 | ) 78 | 79 | output = process.stdout + process.stderr 80 | 81 | success = False 82 | error_msg = "" 83 | area = None 84 | cycle_time = cycle 85 | 86 | if "--> V 02_SYN Success !!" in output: 87 | success = True 88 | elif "--> X 02_SYN Fail !!" in output: 89 | success = False 90 | else: 91 | success = False 92 | error_msg = "Unknown Synthesis Status" 93 | 94 | area_match = re.search(r"Area:\s*([\d\.]+)", output) 95 | if area_match: 96 | area = float(area_match.group(1)) 97 | else: 98 | area = None 99 | error_msg += " | Cannot find area" 100 | 101 | cycle_match = re.search(r"Cycle:\s*([\d\.]+)", output) 102 | if cycle_match: 103 | cycle_time_output = float(cycle_match.group(1)) 104 | if abs(cycle_time_output - cycle) > 0.01: 105 | error_msg += f" | Cycle time mismatch: set to {cycle}, output is {cycle_time_output}" 106 | else: 107 | error_msg += " | Cannot find cycle time" 108 | 109 | if area is not None and cycle_time is not None and latency is not None: 110 | performance = performance_func(area, cycle_time, latency) 111 | else: 112 | performance = None 113 | 114 | result = { 115 | "Cycle Time": cycle, 116 | "Area": area, 117 | "Performance": performance, 118 | "Latency": latency, 119 | "Success": "Yes" if success else "No", 120 | "Error": error_msg.strip(), 121 | "Directory": cycle_dir 122 | } 123 | 124 | return result 125 | 126 | def run_gatesim(cycle, cycle_dir): 127 | 128 | pattern_path = os.path.join(cycle_dir, '00_TESTBED', 'PATTERN.v') 129 | if not os.path.exists(pattern_path): 130 | print(f"Pattern file not found: {pattern_path}") 131 | return False 132 | else: 133 | with open(pattern_path, 'r', encoding='utf-8') as f: 134 | content = f.read() 135 | 136 | content = re.sub(r"`define CYCLE_TIME\s*[\d\.]+", f"`define CYCLE_TIME {cycle}", content) 137 | 138 | with open(pattern_path, 'w', encoding='utf-8') as f: 139 | f.write(content) 140 | 141 | gatesim_command = 'vcs -timescale=1ns/1fs -j8 -sverilog +v2k -full64 -Mupdate -R -debug_access+all -f "filelist.f" -o "simv" -l "vcs.log" -P /usr/cad/synopsys/verdi/2019.06//share/PLI/VCS/linux64/novas.tab /usr/cad/synopsys/verdi/2019.06//share/PLI/VCS/linux64/pli.a -v ~iclabTA01/UMC018_CBDK/CIC/Verilog/fsa0m_a_generic_core_30.lib.src +define+FUNC +define+GATE +neg_tchk +nowarnNTCDSN' 142 | gatesim_working_dir = os.path.join(cycle_dir, '03_GATE') 143 | 144 | error_ignore_list = ["-lerrorinf", "Total errors:"] 145 | 146 | process = subprocess.run( 147 | gatesim_command, 148 | shell=True, 149 | cwd=gatesim_working_dir, 150 | stdout=subprocess.PIPE, 151 | stderr=subprocess.PIPE, 152 | universal_newlines=True, 153 | encoding='utf-8', 154 | errors='replace' 155 | ) 156 | 157 | output = process.stdout + process.stderr 158 | 159 | success = False 160 | 161 | if "congratulations" in output.lower(): 162 | error_found = False 163 | for line in output.splitlines(): 164 | if "error" in line.lower(): 165 | if not any(ignore_item in line for ignore_item in error_ignore_list): 166 | error_found = True 167 | break 168 | 169 | if not error_found: 170 | success = True 171 | 172 | return success 173 | 174 | 175 | def main(): 176 | print_img() 177 | start_time = time.time() 178 | 179 | if not os.path.exists(dummy_dir): 180 | os.makedirs(dummy_dir) 181 | else: 182 | shutil.rmtree(dummy_dir) 183 | os.makedirs(dummy_dir) 184 | 185 | original_dir = os.path.join(dummy_dir, 'original') 186 | os.system(f"cp -r {lab_source_dir} {original_dir}") 187 | print_sep(f"Original directory copied, you can modify your design safely") 188 | 189 | 190 | if reverse: 191 | print_sep(f"Running synthesis with cycle times from {upper_bound} to {lower_bound} with step {step}") 192 | print_sep(f"Max workers: {max_workers}") 193 | else: 194 | print_sep(f"Running synthesis with cycle times from {lower_bound} to {upper_bound} with step {step}") 195 | print_sep(f"Max workers: {max_workers}") 196 | 197 | cycle_times = [round(x / 100.0, 2) for x in range(int(lower_bound * 100), int(upper_bound * 100) + 1, int(step * 100))] 198 | if reverse: 199 | cycle_times = list(reversed(cycle_times)) 200 | 201 | # Prepare directories 202 | cycle_dirs = {} 203 | for cycle in cycle_times: 204 | cycle_dir_name = f"cycle_{cycle}" 205 | cycle_dir = os.path.join(dummy_dir, cycle_dir_name) 206 | cycle_dirs[cycle] = cycle_dir 207 | if os.path.exists(cycle_dir): 208 | shutil.rmtree(cycle_dir) 209 | # Do not copy yet 210 | 211 | # Copy original to each cycle directory in parallel 212 | def copy_original(cycle, cycle_dir): 213 | shutil.copytree(original_dir, cycle_dir) 214 | 215 | with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: 216 | copy_futures = [] 217 | for cycle in cycle_times: 218 | cycle_dir = cycle_dirs[cycle] 219 | future = executor.submit(copy_original, cycle, cycle_dir) 220 | copy_futures.append(future) 221 | concurrent.futures.wait(copy_futures) 222 | 223 | 224 | # Modify syn.tcl for each cycle time 225 | for cycle in cycle_times: 226 | cycle_dir = cycle_dirs[cycle] 227 | syn_tcl_path = os.path.join(cycle_dir, '02_SYN', 'syn.tcl') 228 | with open(syn_tcl_path, 'r', encoding='utf-8') as f: 229 | lines = f.readlines() 230 | with open(syn_tcl_path, 'w', encoding='utf-8') as f: 231 | for line in lines: 232 | if line.strip().startswith("set CYCLE"): 233 | f.write(f"set CYCLE {cycle}\n") 234 | else: 235 | f.write(line) 236 | 237 | best_performance = [None] 238 | best_result = [None] 239 | best_directory = [None] 240 | lock = threading.Lock() 241 | 242 | results = [] 243 | 244 | with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: 245 | future_to_cycle = {} 246 | for cycle in cycle_times: 247 | cycle_dir = cycle_dirs[cycle] 248 | future = executor.submit(run_synthesis, cycle, cycle_dir) 249 | future_to_cycle[future] = (cycle, cycle_dir) 250 | 251 | for future in concurrent.futures.as_completed(future_to_cycle): 252 | cycle, cycle_dir = future_to_cycle[future] 253 | try: 254 | result = future.result() 255 | area_str = f"{result['Area']:7.2f}" if result['Area'] is not None else " N/A" 256 | performance_str = f"{result['Performance']:15.2f}" if result['Performance'] is not None else " N/A" 257 | 258 | if result["Success"] == "Yes": 259 | print(f"Cycle Time: {result['Cycle Time']:3.1f}, Area: {area_str}, Performance: {performance_str}, " 260 | f"Latency: {result['Latency']:6d}, Success: {GREEN}{result['Success']}{RESET}") 261 | else: 262 | print(f"Cycle Time: {result['Cycle Time']:3.1f}, Area: {area_str}, Performance: {performance_str}, " 263 | f"Latency: {result['Latency']:6d}, Success: {RED}{result['Success']}{RESET}") 264 | 265 | # Synchronize access to the best performance 266 | with lock: 267 | if result["Success"] == "Yes" and result["Performance"] is not None: 268 | if best_performance[0] is None or result["Performance"] < best_performance[0]: 269 | if best_directory[0] and os.path.exists(best_directory[0]): 270 | shutil.rmtree(best_directory[0]) 271 | 272 | best_performance[0] = result["Performance"] 273 | best_result[0] = result 274 | best_directory[0] = cycle_dir 275 | else: # Not the best result 276 | shutil.rmtree(cycle_dir) 277 | else: # Failure 278 | shutil.rmtree(cycle_dir) 279 | 280 | # Collect the result 281 | results.append(result) 282 | 283 | except Exception as exc: 284 | print(f"Cycle Time {cycle} generated an exception: {exc}") 285 | if os.path.exists(cycle_dir): 286 | shutil.rmtree(cycle_dir) 287 | 288 | results.sort(key=lambda x: x['Cycle Time']) 289 | 290 | with open("results.csv", "w", newline="", encoding="utf-8") as csvfile: 291 | fieldnames = ["Cycle Time", "Area", "Performance", "Latency", "Success", "Error"] 292 | writer = csv.DictWriter(csvfile, fieldnames=fieldnames) 293 | 294 | writer.writeheader() 295 | for result in results: 296 | writer.writerow({k: v for k, v in result.items() if k != "Directory"}) 297 | 298 | # Print the best result 299 | print_sep(f"Total time: {time.time() - start_time} seconds") 300 | print_sep("Synthesis results saved to results.csv") 301 | if best_result[0] is not None: 302 | print(f"The best synthesis directory is retained at: {best_directory[0]}") 303 | print(f"Best result: Cycle Time: {best_result[0]['Cycle Time']}, Area: {best_result[0]['Area']}, Performance: {best_result[0]['Performance']}, Latency: {best_result[0]['Latency']}") 304 | 305 | if best_result[0] is not None: 306 | print_sep("Running gate-level simulation") 307 | success = run_gatesim(best_result[0]['Cycle Time'], best_directory[0]) 308 | if success: 309 | print_sep(f"Gate-level simulation {GREEN}PASSED!!{RESET}") 310 | else: 311 | print_sep(f"Gate-level simulation {RED}FAILED!!{RESET}") 312 | else: 313 | print_sep("No best result found, skip gate-level simulation") 314 | 315 | if __name__ == "__main__": 316 | main() 317 | --------------------------------------------------------------------------------