import json import subprocess import os import re # --- CONFIGURATION --- # If your environment variable isn't set, this default path will be used. DEFAULT_VIVADO_PATH = "/tools/Xilinx/Vivado/2023.1/bin" def extract_module_name(verilog_code): """ Extract the module name from the Verilog code. Assumes the module declaration is of the form: module ( Returns the module name as a string, or None if not found. """ match = re.search(r'\bmodule\s+(\w+)', verilog_code) if match: return match.group(1) return None def parse_optimized(lines): """ Extract resource usage numbers from the main (optimized) report sections. Returns a dictionary with keys: LUT, FF, DSP, BRAM, IO. """ optimized = {"LUT": None, "FF": None, "DSP": None, "BRAM": None, "IO": None} for line in lines: m = re.search(r'\|\s*Slice LUTs\*?\s*\|\s*(\d+)', line) if m: optimized["LUT"] = int(m.group(1)) m = re.search(r'\|\s*Slice Registers\s*\|\s*(\d+)', line) if m: optimized["FF"] = int(m.group(1)) m = re.search(r'\|\s*DSPs\s*\|\s*(\d+)', line) if m: optimized["DSP"] = int(m.group(1)) m = re.search(r'\|\s*Block RAM Tile\s*\|\s*(\d+)', line) if m: optimized["BRAM"] = int(m.group(1)) m = re.search(r'\|\s*Bonded IOB\s*\|\s*(\d+)', line) if m: optimized["IO"] = int(m.group(1)) return optimized def extract_primitives_section(lines): """ Extracts all lines between the "7. Primitives" header and the "8. Black Boxes" header. """ start_marker = "7. Primitives" end_marker = "8. Black Boxes" start_idx = None end_idx = None for idx, line in enumerate(lines): if start_idx is None and start_marker in line and (idx + 1 < len(lines) and "------" in lines[idx + 1]): start_idx = idx elif start_idx is not None and end_marker in line and (idx + 1 < len(lines) and "------" in lines[idx + 1]): end_idx = idx break if start_idx is None or end_idx is None: return [] return lines[start_idx:end_idx] def parse_primitives_section(lines): """ Parses the primitives section lines to accumulate resource usage. """ resources = {"LUT": 0, "FF": 0, "DSP": 0, "BRAM": 0, "IO": 0} for line in lines: stripped_line = line.strip() if not stripped_line.startswith("|"): continue parts = stripped_line.split("|") if len(parts) < 4: continue ref_name = parts[1].strip() used_str = parts[2].strip() try: used = int(used_str) except ValueError: continue if ref_name.startswith("LUT"): resources["LUT"] += used if ref_name in ("IBUF", "OBUF"): resources["IO"] += used return resources def run_synthesis(solution_code): """ Runs Vivado synthesis on the provided code. """ # 1. Write the Verilog code to a temporary file. verilog_file = "temp.v" with open(verilog_file, "w") as f: f.write(solution_code) # 2. Extract the module name top_module = extract_module_name(solution_code) if top_module is None: print("Could not extract module name; using 'temp_top' as a default.") top_module = "temp_top" # 3. Resolve Vivado Path (Linux Fix) vivado_path_env = os.environ.get("vivado") # If env var is missing, try the default path, or search system path if vivado_path_env: vivado_bin = os.path.join(vivado_path_env, "vivado") # NO .bat elif os.path.exists(os.path.join(DEFAULT_VIVADO_PATH, "vivado")): vivado_bin = os.path.join(DEFAULT_VIVADO_PATH, "vivado") else: # Last resort: assume it's in the system PATH vivado_bin = "vivado" tcl_script = "synthesis_script.tcl" vivado_project = "temp_project" # 4. Create the Vivado Tcl script. tcl_commands = f""" create_project {vivado_project} -force -part xc7z020clg400-1 add_files {verilog_file} set_property top {top_module} [current_fileset] # Run synthesis only (no simulation) synth_design -top {top_module} -part xc7z020clg400-1 # Generate resource utilization report report_utilization -file resource_usage.rpt exit """ with open(tcl_script, "w") as file: file.write(tcl_commands) # 5. Run Vivado print(f"Starting Synthesis for {top_module} using {vivado_bin}...") try: # Added stdout=PIPE so we can see the output if it crashes result = subprocess.run( [vivado_bin, "-mode", "batch", "-source", tcl_script], capture_output=True, text=True, check=True ) except subprocess.CalledProcessError as e: print(f"❌ Synthesis CRASHED for {top_module}") print("--- Error Log ---") print(e.stderr) # Print the actual error from Vivado print("-----------------") return None except FileNotFoundError: print(f"❌ CRITICAL ERROR: Could not find Vivado executable at: {vivado_bin}") return None # 6. Check results if "Finished Writing Synthesis Report" in result.stdout: print(f"✅ Synthesis Success: {top_module}") if os.path.exists("resource_usage.rpt"): with open("resource_usage.rpt", "r") as f: report_lines = f.readlines() optimized = parse_optimized(report_lines) primitives_sec = extract_primitives_section(report_lines) primitives = parse_primitives_section(primitives_sec) if primitives_sec else {} return {"optimized": optimized, "primitives": primitives} else: print("⚠️ Report file missing despite success message.") return None else: print(f"❌ Synthesis Failed (Logic Error) for {top_module}") # Print the last 10 lines of the log to help debug print("\n".join(result.stdout.splitlines()[-10:])) return None def run_resource_usage(): input_json_file = "solutions.json" if not os.path.exists(input_json_file): print(f"Error: {input_json_file} not found.") return with open(input_json_file, "r") as f: data = json.load(f) print(f"Loaded {input_json_file}. Scanning for passing solutions...") found_any = False for top_key, top_value in data.items(): # e.g. "gpt-4" for category, module_list in top_value.items(): # e.g. "Combinational Logic" for module in module_list: for idx, sol in enumerate(module["solutions"]): # DEBUG PRINT: Show us the status status = sol.get("pass", "MISSING").strip().lower() if status == "true": found_any = True print(f"🚀 MATCH: Synthesizing {module['module']} (Sol #{idx})...") solution_code = sol["solution"] resource_usage = run_synthesis(solution_code) if resource_usage: sol["resource usage"] = resource_usage print(f" ✅ Result: {resource_usage['optimized']['LUT']} LUTs") else: sol["resource usage"] = {"optimized": {}, "primitives": {}} print(f" ❌ Synthesis Failed internally.") # Save immediately with open("solutions.json", "w") as f: json.dump(data, f, indent=4) else: # Tell the user we are skipping print(f"⏭️ Skipping {module['module']} (Status: '{status}')") if not found_any: print("\n⚠️ WARNING: No passing solutions found! Synthesis only runs on code that passed simulation.") print("To force a test, edit 'solutions.json' and change one 'pass' value to 'true'.") else: print("\n✅ All Resource Usage checks completed.") if __name__ == "__main__": run_resource_usage()