verilator/nodist/verilator_bisect

106 lines
3.6 KiB
Python
Executable File

#!/usr/bin/env python3
# pylint: disable=C0103,C0114,C0116,W0613
#
# This program is free software; you can redistribute it and/or modify the
# Verilator internals under the terms of either the GNU Lesser General
# Public License Version 3 or the Perl Artistic License Version 2.0.
#
# SPDX-FileCopyrightText: 2003-2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
######################################################################
import argparse
import os
import subprocess
import sys
try:
from termcolor import colored
except ModuleNotFoundError:
def colored(msg, **kwargs):
return msg
def cprint(msg="", *, color=None, attrs=None, **kwargs):
print(colored(msg, color=color, attrs=attrs), **kwargs)
parser = argparse.ArgumentParser(
description='Binary search utility for debugging Verilator with V3DebugBisect',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog='''
example:
%(prog)s DfgPeephole 0 1000 test_regress/t/t_foo.py --no-skip-identical
''')
parser.add_argument("--pre", type=str, help='Command to run before each execution')
parser.add_argument('name', help='Name of V3DebugBisect instance')
parser.add_argument('low', type=int, help='Bisection range low value, use 0 by default')
parser.add_argument('high',
type=int,
help='Bisection range high value, use a sufficiently high number')
parser.add_argument('cmd',
nargs=argparse.REMAINDER,
help='Discriminator command that should exit non-zero on failure')
args = parser.parse_args()
var = f"VERILATOR_DEBUG_BISECT_{args.name}"
passing = args.low - 1
failing = args.high + 1
cprint()
cprint(f"Starting bisection serach for {var} in interval [{args.low}, {args.high}]",
attrs=["bold"])
while True:
cprint()
passStr = str(passing) if passing >= args.low else '?'
failStr = str(failing) if failing < args.high else '?'
cprint(f"Current step Pass: {passStr} Fail: {failStr}", attrs=["bold"])
# Stop if found, or exhausted interval without finding both a pass and a fail
if failing == args.low:
cprint(f"The low endpoint of the search interval ({args.low}) fails. Suggest rerun with:",
color="yellow")
cprint(f" {sys.argv[0]} {args.name} 0 {args.low} ...", color="yellow")
sys.exit(1)
if passing == args.high:
cprint(
f"The high endpoint of the search interval ({args.high}) passes. Suggest rerun with:",
color="yellow")
cprint(f" {sys.argv[0]} {args.name} {args.high} {10*args.high} ...", color="yellow")
sys.exit(1)
if failing == passing + 1:
cprint(f"First faling value: {var}={failing}", attrs=["bold"])
sys.exit(0)
# Compute middle of interval to evaluate
mid = (failing + passing) // 2
# Run pre command if given:
if args.pre:
cprint("Running --pre command", attrs=["bold"])
preResult = subprocess.run(args.pre, shell=True, check=False)
if preResult.returncode != 0:
cprint("Pre command failed", color="red")
sys.exit(2)
# Set up environment variable
env = os.environ.copy()
env[var] = str(mid)
# Run the discriminator command
cprint(f"Running with {var}={mid}", attrs=["bold"])
result = subprocess.run(args.cmd, env=env, check=False)
# Check status, update interval
if result.returncode != 0:
cprint(f"Run with {var}={mid}: Fail", color="red")
failing = mid
else:
cprint(f"Run with {var}={mid}: Pass", color="green")
passing = mid