diff --git a/cocotb/caravel_cocotb/CI/setup_env.py b/cocotb/caravel_cocotb/CI/setup_env.py index 11b45ba..1262194 100644 --- a/cocotb/caravel_cocotb/CI/setup_env.py +++ b/cocotb/caravel_cocotb/CI/setup_env.py @@ -29,7 +29,7 @@ def clone_needed_repos(self): depth=1, branch="main", ) - self.download_sky130_pdk("e3b630d9b7c0e23615367d52c4f78b2d2ede58ac") + self.download_sky130_pdk("a918dc7c8e474a99b68c85eb3546b4ed91fe9e7b") def pull_cocotb_docker(self): image_name = "efabless/dv" diff --git a/cocotb/caravel_cocotb/caravel_interfaces.py b/cocotb/caravel_cocotb/caravel_interfaces.py index 3a476a3..f6c1731 100644 --- a/cocotb/caravel_cocotb/caravel_interfaces.py +++ b/cocotb/caravel_cocotb/caravel_interfaces.py @@ -1,10 +1,6 @@ from caravel_cocotb.interfaces.caravel import Caravel_env # noqa: F401 from caravel_cocotb.interfaces.SPI import SPI # noqa: F401 from caravel_cocotb.interfaces.UART import UART # noqa: F401 -from caravel_cocotb.interfaces.common_functions.test_functions import ( - test_configure, -) # noqa: F401 -from caravel_cocotb.interfaces.common_functions.test_functions import ( - report_test, -) # noqa: F401 +from caravel_cocotb.interfaces.common_functions.test_functions import (test_configure) # noqa: F401 +from caravel_cocotb.interfaces.common_functions.test_functions import (report_test) # noqa: F401 from caravel_cocotb.interfaces.common import GPIO_MODE # noqa: F401 diff --git a/cocotb/caravel_cocotb/scripts/verify_cocotb/RunRegression.py b/cocotb/caravel_cocotb/scripts/verify_cocotb/RunRegression.py index 821852e..150ddc0 100644 --- a/cocotb/caravel_cocotb/scripts/verify_cocotb/RunRegression.py +++ b/cocotb/caravel_cocotb/scripts/verify_cocotb/RunRegression.py @@ -16,7 +16,6 @@ import smtplib import socket import yaml -import time from caravel_cocotb.scripts.merge_coverage import merge_fun_cov from caravel_cocotb.scripts.test_defaults.test_defaults import TestDefaults from rich.live import Live @@ -490,12 +489,6 @@ def unzip_sdf_files(self): return elif self.args.sim != "GL_SDF": return - # make corners list in case in is n't - if not isinstance(self.args.corner, list): - corners = [self.args.corner] - else: - corners = self.args.corner - # keep caravel sdf dir sdf_dir = f"{self.paths.CARAVEL_ROOT}/signoff/{'caravan' if self.args.caravan else 'caravel'}/primetime/sdf" if self.args.sdfs_dir is None: @@ -512,4 +505,3 @@ def unzip_sdf_files(self): for gz_file in gz_files: subprocess.run(f"gzip {gz_file} -d".split()) self.args.macros.append(f'SDF_PATH=\\"{sdf_dir}\\"') - diff --git a/cocotb/caravel_cocotb/scripts/verify_cocotb/RunTest.py b/cocotb/caravel_cocotb/scripts/verify_cocotb/RunTest.py index eb3afa2..91a8fe5 100644 --- a/cocotb/caravel_cocotb/scripts/verify_cocotb/RunTest.py +++ b/cocotb/caravel_cocotb/scripts/verify_cocotb/RunTest.py @@ -5,9 +5,12 @@ import re import logging import caravel_cocotb +import hashlib class RunTest: + COMPILE_LOCK = set() + def __init__(self, args, paths, test, logger) -> None: self.args = args self.paths = paths @@ -139,15 +142,23 @@ def runTest(self): def runTest_iverilog(self): if self.test.sim == "GL_SDF": raise RuntimeError( - f"iverilog can't run SDF for test {self.test.name} Please use anothor simulator like cvc" + f"{bcolors.FAIL}iverilog can't run SDF for test {self.test.name} Please use anothor simulator like cvc{bcolors.ENDC}" ) return self.write_iverilog_includes_file() - if ( - not os.path.isfile(f"{self.test.compilation_dir}/sim.vvp") - or self.args.compile - ): + if not os.path.isfile(f"{self.test.compilation_dir}/sim.vvp"): + print(f"{bcolors.OKCYAN}Compiling as sim.vvp not found{bcolors.ENDC}") + self.iverilog_compile() + elif self.args.compile: + print(f"{bcolors.OKCYAN}Compiling as compile flag is set{bcolors.ENDC}") + self.iverilog_compile() + elif not self.is_same_hash(self.test.netlist) and f"{self.test.compilation_dir}/sim.vvp" not in RunTest.COMPILE_LOCK: + print(f"{bcolors.OKCYAN}Compiling since netlist has has changed{bcolors.ENDC}") self.iverilog_compile() + else: + if f"{self.test.compilation_dir}/sim.vvp" not in RunTest.COMPILE_LOCK: + print(f"{bcolors.OKCYAN}Skipping compilation as netlist has not changed{bcolors.ENDC}") + RunTest.COMPILE_LOCK.add(f"{self.test.compilation_dir}/sim.vvp") # locked means if it is copiled for the first time then it will not be compiled again even if netlist changes if not self.args.compile_only: self.iverilog_run() @@ -216,8 +227,19 @@ def runTest_vcs(self): self.vcs_coverage_command = "-cm line+tgl+cond+fsm+branch+assert " os.environ["TESTCASE"] = f"{self.test.name}" os.environ["MODULE"] = "module_trail" - if not os.path.isfile(f"{self.test.compilation_dir}/simv") or self.args.compile: + if not os.path.isfile(f"{self.test.compilation_dir}/simv"): + print(f"{bcolors.OKCYAN}Compiling as simv not found{bcolors.ENDC}") + self.vcs_compile() + elif self.args.compile: + print(f"{bcolors.OKCYAN}Compiling as compile flag is set{bcolors.ENDC}") + self.vcs_compile() + elif not self.is_same_hash(self.test.netlist) and f"{self.test.compilation_dir}/simv" not in RunTest.COMPILE_LOCK: + print(f"{bcolors.OKCYAN}Compiling since netlist has has changed{bcolors.ENDC}") self.vcs_compile() + else: + if f"{self.test.compilation_dir}/simv" not in RunTest.COMPILE_LOCK: + print(f"{bcolors.OKCYAN}Skipping compilation as netlist has not changed{bcolors.ENDC}") + RunTest.COMPILE_LOCK.add(f"{self.test.compilation_dir}/simv") # locked means if it is copiled for the first time then it will not be compiled again even if netlist changes if not self.args.compile_only: self.vcs_run() @@ -241,7 +263,7 @@ def vcs_compile(self): ) lint = "+lint=all" if self.args.lint else "" ignored_errors = " -error=noZMMCM " - vcs_cmd = f"cd {self.test.compilation_dir}; vcs {lint} -negdelay {self.vcs_coverage_command} {ignored_errors}-debug_access+all +error+50 +vcs+loopreport+1000000 -diag=sdf:verbose +sdfverbose +neg_tchk -debug_access -full64 -l {self.test.compilation_dir}/test_compilation.log caravel_top -Mdir={self.test.compilation_dir}/csrc -o {self.test.compilation_dir}/simv +vpi -P pli.tab -load $(cocotb-config --lib-name-path vpi vcs)" + vcs_cmd = f"cd {self.test.compilation_dir}; vcs {lint} -negdelay {self.vcs_coverage_command} {ignored_errors} -debug_access+all +error+50 +vcs+loopreport+1000000 -diag=sdf:verbose +sdfverbose +neg_tchk -full64 -l {self.test.compilation_dir}/test_compilation.log caravel_top -Mdir={self.test.compilation_dir}/csrc -o {self.test.compilation_dir}/simv +vpi -P pli.tab -load $(cocotb-config --lib-name-path vpi vcs)" self.run_command_write_to_file( vcs_cmd, self.test.compilation_log, @@ -309,6 +331,35 @@ def run_command_write_to_file(self, cmd, file, logger, quiet=True): return process.returncode + @staticmethod + def calculate_netlist_hash(netlist, hash_algorithm="sha256"): + """Calculate a combined hash of multiple files ignoring the order.""" + hash_func = getattr(hashlib, hash_algorithm)() + try: + for file_path in sorted(netlist): # Ensure consistent order + with open(file_path, "rb") as f: + while chunk := f.read(8192): + hash_func.update(chunk) + return hash_func.hexdigest() + except FileNotFoundError as e: + return f"File not found: {e.filename}" + except PermissionError as e: + return f"Permission denied: {e.filename}" + + def is_same_hash(self, netlist): + # read old hash if exists + try: + with open(self.test.hash_log, "r") as f: + old_hash = f.read().strip() + except FileNotFoundError: + old_hash = 0 + # calculate new hash + new_hash = self.calculate_netlist_hash(netlist) + # write new hash + with open(self.test.hash_log, "w") as f: + f.write(new_hash) + return new_hash == old_hash + class bcolors: HEADER = "\033[95m" diff --git a/cocotb/caravel_cocotb/scripts/verify_cocotb/Test.py b/cocotb/caravel_cocotb/scripts/verify_cocotb/Test.py index 26ea54d..bb4f269 100644 --- a/cocotb/caravel_cocotb/scripts/verify_cocotb/Test.py +++ b/cocotb/caravel_cocotb/scripts/verify_cocotb/Test.py @@ -31,6 +31,7 @@ def __init__(self, name, sim, corner, args, paths, logger, local_macros=None): ) self.include_dirs = set() self.init_test() + self.netlist = set() def init_test(self): self.start_time = "-" @@ -148,6 +149,7 @@ def create_logs(self): # self.test_log=open(test_log, "w") self.compilation_log = f"{self.compilation_dir}/compilation.log" self.hex_log = f"{self.test_dir}/firmware.log" + self.hash_log = f"{self.compilation_dir}/hash.txt" # self.full_terminal = open(self.compilation_log, "w") def create_lint_log(self): @@ -337,8 +339,10 @@ def convert_list_to_include(self, file): if "*" in file_path: for wild_match in glob.glob(file_path): paths += f'`include "{wild_match}"\n' + self.netlist.add(wild_match) else: paths += f'`include "{file_path}"\n' + self.netlist.add(file_path) # Add Includes to Set include_indices = [ i for i, flag in enumerate(split_line) if flag == "-I"