mirror of
				https://github.com/YosysHQ/yosys
				synced 2025-11-03 21:09:12 +00:00 
			
		
		
		
	Docs: static opt macro list
Also adds `docs/tests/macro_commands.py` which checks all commands in `code_examples/macro_commands` against the current yosys build. Format similar to `run-test.sh` files: logging the file under test and reporting errors.
This commit is contained in:
		
							parent
							
								
									95849edbba
								
							
						
					
					
						commit
						6c8949cacc
					
				
					 3 changed files with 107 additions and 4 deletions
				
			
		
							
								
								
									
										91
									
								
								docs/tests/macro_commands.py
									
										
									
									
									
										Executable file
									
								
							
							
						
						
									
										91
									
								
								docs/tests/macro_commands.py
									
										
									
									
									
										Executable file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,91 @@
 | 
			
		|||
#!/usr/bin/env python3
 | 
			
		||||
 | 
			
		||||
import logging
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import re
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
# basic logging setup
 | 
			
		||||
logging.basicConfig(level=logging.INFO)
 | 
			
		||||
 | 
			
		||||
# expects __file__ = yosys/docs/tests/macro_commands.py
 | 
			
		||||
TESTS_DIR = Path(__file__).parent
 | 
			
		||||
ROOT_DIR = TESTS_DIR.parent.parent
 | 
			
		||||
THIS_FILE = (TESTS_DIR / "macro_commands.py").relative_to(ROOT_DIR)
 | 
			
		||||
MACRO_SOURCE = TESTS_DIR.parent / "source" / "code_examples" / "macro_commands"
 | 
			
		||||
assert MACRO_SOURCE.exists(), f"can't find macro_commands in {MACRO_SOURCE}"
 | 
			
		||||
 | 
			
		||||
YOSYS = TESTS_DIR.parent.parent / "yosys"
 | 
			
		||||
assert YOSYS.exists(), f"can't find yosys executable in {YOSYS}"
 | 
			
		||||
 | 
			
		||||
raise_error = False
 | 
			
		||||
# get all macro commands being used
 | 
			
		||||
for macro in MACRO_SOURCE.glob("*.ys"):
 | 
			
		||||
    # log current test
 | 
			
		||||
    relative_path = macro.relative_to(ROOT_DIR)
 | 
			
		||||
    logging.log(logging.INFO, f"Checking {relative_path}")
 | 
			
		||||
    # files in macros_commands should be named {command}.ys
 | 
			
		||||
    command = macro.stem
 | 
			
		||||
    # run `yosys -h {command}` to capture current sub-commands
 | 
			
		||||
    proc = subprocess.run([YOSYS, "-QTh", command], capture_output=True, text=True)
 | 
			
		||||
    with open(macro) as f:
 | 
			
		||||
        # {command.ys} starts with two commented lines, the first is the text
 | 
			
		||||
        # immediately prior to the macro body.  The second is the text
 | 
			
		||||
        # immediately after.
 | 
			
		||||
        start = f.readline()
 | 
			
		||||
        end = f.readline()
 | 
			
		||||
        expected_content = f.readlines()
 | 
			
		||||
    # parse {command.ys}
 | 
			
		||||
    if "#start:" not in start or "#end:" not in end:
 | 
			
		||||
        logging.error(f"Missing start and/or end string in {relative_path}, see {THIS_FILE}")
 | 
			
		||||
        raise_error = True
 | 
			
		||||
        continue
 | 
			
		||||
    start = start.replace("#start:", "").strip()
 | 
			
		||||
    end = end.replace("#end:", "").strip()
 | 
			
		||||
    # attempt to find macro body in help output
 | 
			
		||||
    match = re.fullmatch(f".*{start}(.*){end}.*", proc.stdout, re.DOTALL)
 | 
			
		||||
    if not match:
 | 
			
		||||
        logging.error(f"Couldn't find {start!r} and/or {end!r} in `yosys -h {command}` output")
 | 
			
		||||
        raise_error = True
 | 
			
		||||
        continue
 | 
			
		||||
    actual_content = match.group(1).strip().splitlines()
 | 
			
		||||
    # iterate over and compare expected v actual
 | 
			
		||||
    for (expected, actual) in zip(expected_content, actual_content):
 | 
			
		||||
        expected = expected.strip()
 | 
			
		||||
        actual = actual.strip()
 | 
			
		||||
        # raw match
 | 
			
		||||
        if expected == actual:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        # rip apart formatting to match line parts
 | 
			
		||||
        pattern = r"(?P<cmd>\S+)(?P<pass> \[.*\])?(?P<opt>.*?)(?P<cond>  \(.*\))?(?P<comment>\s+#.*)?"
 | 
			
		||||
        try:
 | 
			
		||||
            expected_dict = re.fullmatch(pattern, expected).groupdict()
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            logging.error(f"Bad formatting in {relative_path}: {expected!r}")
 | 
			
		||||
            raise_error = True
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            actual_dict = re.fullmatch(pattern, actual).groupdict()
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            logging.error(f"Unexpected formatting in `yosys -h {command}` output, {actual!r}")
 | 
			
		||||
            raise_error = True
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        does_match = expected_dict["cmd"] == actual_dict["cmd"]
 | 
			
		||||
 | 
			
		||||
        #todo: check expected_dict["pass"] is a subset of actual_dict["pass"]
 | 
			
		||||
        # only check opt and cond match if they're in {command}.ys
 | 
			
		||||
        match_keys = ["opt", "cond"]
 | 
			
		||||
        for key in match_keys:
 | 
			
		||||
            if expected_dict[key] and expected_dict[key] != actual_dict[key]:
 | 
			
		||||
                does_match = False
 | 
			
		||||
 | 
			
		||||
        # raise error on mismatch        
 | 
			
		||||
        if not does_match:
 | 
			
		||||
            logging.error(f"Expected {expected!r}, got {actual!r}")
 | 
			
		||||
            raise_error = True
 | 
			
		||||
 | 
			
		||||
sys.exit(raise_error)
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue