diff options
Diffstat (limited to 'tests/xprop/test.py')
-rw-r--r-- | tests/xprop/test.py | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/tests/xprop/test.py b/tests/xprop/test.py new file mode 100644 index 000000000..84ad0a1f4 --- /dev/null +++ b/tests/xprop/test.py @@ -0,0 +1,516 @@ +import os +import re +import subprocess +import itertools +import random +import argparse +from pathlib import Path + +parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) +parser.add_argument('-S', '--seed', type=int, help='seed for PRNG') +parser.add_argument('-c', '--count', type=int, default=32, help='number of random patterns to test') +parser.add_argument('-s', '--seq', action='store_true', help='run a sequential test') +parser.add_argument('steps', nargs='*', help='steps to run') +args = parser.parse_args() + +if args.seed is None: + args.seed = random.randrange(1 << 32) + +print(f"PRNG seed: {args.seed}") +random.seed(args.seed) + +steps = args.steps + +all_outputs = [ + "sim", + "sim_xprop", + "vsim_expr", + "vsim_expr_xprop", + "vsim_noexpr", + "vsim_noexpr_xprop", + "sat", + "sat_xprop", +] + +if not args.seq: + all_outputs += ["opt_expr", "opt_expr_xprop"] + +if not steps: + steps = ["clean", "prepare", "verify", *all_outputs, "compare"] + +if "clean" in steps: + for output in all_outputs: + try: + os.unlink(f"{output}.out") + except FileNotFoundError: + pass + + +def yosys(command): + subprocess.check_call(["yosys", "-Qp", command]) + +def remove(file): + try: + os.unlink(file) + except FileNotFoundError: + pass + + +def vcdextract(signals, on_change, file, output, limit=None): + scope = [] + ids = {} + prefix = [] + + for line in file: + line = prefix + line.split() + if line[-1] != "$end": + prefix = line + continue + prefix = [] + + if not line: + continue + if line[0] == "$scope": + scope.append(line[2].lstrip("\\")) + elif line[0] == "$upscope": + scope.pop() + elif line[0] == "$var": + ids[".".join(scope + [line[4].lstrip("\\")])] = line[3] + elif line[0] == "$enddefinitions": + break + elif line[0] in ["$version", "$date", "$comment"]: + continue + else: + raise RuntimeError(f"unexpected input in vcd {line}") + + dump_pos = {} + + for i, sig in enumerate(signals + on_change): + dump_pos[ids[sig]] = i + + values = [None] * len(signals + on_change) + + last_values = [] + + counter = 0 + + for line in file: + if line.startswith("#"): + if None in values: + continue + + if values != last_values: + last_values = list(values) + if limit is None or counter < limit: + print(*values[:len(signals)], file=output) + counter += 1 + continue + + if line.startswith("b"): + value, id = line[1:].split() + else: + value, id = line[:1], line[1:] + + pos = dump_pos.get(id) + if pos is None: + continue + + values[pos] = value + + if values != last_values: + if limit is None or counter < limit: + print(*values[:len(signals)], file=output) + + +share = Path(__file__).parent / ".." / ".." / "share" + +simlibs = [str(share / "simlib.v"), str(share / "simcells.v")] + +if "prepare" in steps: + yosys( + """ + read_verilog -icells uut.v + hierarchy -top uut; proc -noopt + write_rtlil uut.il + dump -o ports.list uut/x:* + """ + ) + +inputs = [] +outputs = [] + +with open("ports.list") as ports_file: + for line in ports_file: + line = line.split() + if not line or line[0] != "wire": + continue + line = line[1:] + width = 1 + if line[0] == "width": + width = int(line[1]) + line = line[2:] + direction, seq, name = line + assert name.startswith("\\") + name = name[1:] + seq = int(seq) + + if direction == "input": + inputs.append((seq, name, width)) + else: + outputs.append((seq, name, width)) + +inputs.sort() +outputs.sort() + +input_width = sum(width for seq, name, width in inputs) +output_width = sum(width for seq, name, width in outputs) + +if "prepare" in steps: + if args.seq: + patterns = [''.join(random.choices('01x', k=input_width)) for i in range(args.count)] + else: + if 3**input_width <= args.count * 2: + patterns = ["".join(bits) for bits in itertools.product(*["01x"] * input_width)] + else: + patterns = set() + + for bit in '01x': + patterns.add(bit * input_width) + + for bits in itertools.combinations('01x', 2): + for bit1, bit2 in itertools.permutations(bits): + for i in range(input_width): + pattern = [bit1] * input_width + pattern[i] = bit2 + patterns.add(''.join(pattern)) + + for i, j in itertools.combinations(range(input_width), 2): + pattern = [bit1] * input_width + pattern[i] = bit2 + pattern[j] = bit2 + patterns.add(''.join(pattern)) + + for bit1, bit2, bit3 in itertools.permutations('01x'): + for i, j in itertools.combinations(range(input_width), 2): + pattern = [bit1] * input_width + pattern[i] = bit2 + pattern[j] = bit3 + patterns.add(''.join(pattern)) + + if len(patterns) > args.count // 2: + patterns = sorted(patterns) + random.shuffle(patterns) + patterns = set(patterns[:args.count // 2]) + + while len(patterns) < args.count: + pattern = ''.join(random.choices('01x', k=input_width)) + patterns.add(pattern) + + patterns = sorted(patterns) + with open("patterns.list", "w") as f: + for pattern in patterns: + print(pattern, file=f) +else: + with open("patterns.list") as f: + patterns = [pattern.strip() for pattern in f] + + +if "prepare" in steps: + with open("wrapper.v", "w") as wrapper_file: + print( + "module wrapper(" + f"input [{input_width-1}:0] A, " + f"output [{output_width-1}:0] Y);", + file=wrapper_file, + ) + + connections = [] + pos = 0 + for seq, name, width in inputs: + connections.append(f".{name}(A[{pos+width-1}:{pos}])") + pos += width + pos = 0 + for seq, name, width in outputs: + connections.append(f".{name}(Y[{pos+width-1}:{pos}])") + pos += width + + print(f"uut uut({', '.join(connections)});", file=wrapper_file) + print("endmodule", file=wrapper_file) + + yosys( + """ + read_rtlil uut.il + read_verilog wrapper.v + hierarchy -top wrapper; proc -noopt + flatten; clean; + rename wrapper uut + write_rtlil wrapped.il + """ + ) + + try: + yosys( + """ + read_rtlil wrapped.il + dffunmap + xprop -required + check -assert + write_rtlil wrapped_xprop.il + """ + ) + except subprocess.CalledProcessError: + remove("wrapped_xprop.il") + + with open("verilog_sim_tb.v", "w") as tb_file: + print("module top();", file=tb_file) + print(f"reg gclk;", file=tb_file) + print(f"reg [{input_width-1}:0] A;", file=tb_file) + print(f"wire [{output_width-1}:0] Y;", file=tb_file) + print(f"uut uut(.A(A), .Y(Y));", file=tb_file) + print("initial begin", file=tb_file) + + for pattern in patterns: + print( + f' gclk = 1; #0; A[0] = 1\'b{pattern[-1]}; #0; A = {input_width}\'b{pattern}; #5; gclk = 0; $display("%b %b", A, Y); #5', + file=tb_file, + ) + + print(" $finish;", file=tb_file) + print("end", file=tb_file) + print("endmodule", file=tb_file) + + with open("synth_tb.v", "w") as tb_file: + print("module top(input clk);", file=tb_file) + print(f"reg [{len(patterns).bit_length() - 1}:0] counter = 0;", file=tb_file) + print(f"reg [{input_width-1}:0] A;", file=tb_file) + print(f"(* gclk *) reg gclk;", file=tb_file) + print(f"wire [{output_width-1}:0] Y;", file=tb_file) + print(f"uut uut(.A(A), .Y(Y));", file=tb_file) + print(f"always @(posedge gclk) counter <= counter + 1;", file=tb_file) + print(f"always @* case (counter)", file=tb_file) + for i, pattern in enumerate(patterns): + print(f" {i:7} : A = {input_width}'b{pattern};", file=tb_file) + print(f" default : A = {input_width}'b{'x' * input_width};", file=tb_file) + print(f"endcase", file=tb_file) + print("endmodule", file=tb_file) + + with open("const_tb.v", "w") as tb_file: + print("module top();", file=tb_file) + for i, pattern in enumerate(patterns): + print( + f"(* keep *) wire [{output_width-1}:0] Y_{i}; " + f"uut uut_{i}(.A({input_width}'b{pattern}), .Y(Y_{i}));", + file=tb_file, + ) + print("endmodule", file=tb_file) + +if "verify" in steps: + try: + seq_args = " -tempinduct -set-init-undef" if args.seq else "" + yosys( + f""" + read_rtlil wrapped.il + copy uut gold + rename uut gate + design -push-copy + dffunmap + xprop -formal -split-inputs -required -debug-asserts gate + clk2fflogic + sat{seq_args} -enable_undef -set-def-inputs -prove-asserts -verify -show-all gate + design -pop + + dffunmap + xprop -required -assume-encoding gate + miter -equiv -make_assert -flatten gold gate miter + clk2fflogic + sat{seq_args} -enable_undef -set-assumes -prove-asserts -verify -show-all miter + """ + ) + with open("verified", "w") as f: + pass + except subprocess.CalledProcessError: + remove("verified") + + +for mode in ["", "_xprop"]: + if not Path(f"wrapped{mode}.il").is_file(): + for expr in [f"expr{mode}", f"noexpr{mode}"]: + remove(f"vsim_{expr}.out") + continue + + if "prepare" in steps: + yosys( + f""" + read_rtlil wrapped{mode}.il + chformal -remove + dffunmap + write_verilog -noparallelcase vsim_expr{mode}.v + write_verilog -noexpr vsim_noexpr{mode}.v + """ + ) + + for expr in [f"expr{mode}", f"noexpr{mode}"]: + if f"vsim_{expr}" in steps: + subprocess.check_call( + [ + "iverilog", + "-DSIMLIB_FF", + "-DSIMLIB_GLOBAL_CLOCK=top.gclk", + f"-DDUMPFILE=\"vsim_{expr}.vcd\"", + "verilog_sim_tb.v", + f"vsim_{expr}.v", + *simlibs, + "-o", + f"vsim_{expr}", + ] + ) + with open(f"vsim_{expr}.out", "w") as f: + subprocess.check_call([f"./vsim_{expr}"], stdout=f) + +for mode in ["", "_xprop"]: + if f"sim{mode}" not in steps: + continue + if not Path(f"wrapped{mode}.il").is_file(): + remove(f"sim{mode}.out") + continue + yosys( + f""" + read_verilog synth_tb.v + read_rtlil wrapped{mode}.il + hierarchy -top top; proc -noopt + dffunmap + sim -clock clk -n {len(patterns) // 2} -vcd sim{mode}.vcd top + """ + ) + + with open(f"sim{mode}.vcd") as fin, open(f"sim{mode}.out", "w") as fout: + vcdextract(["top.A", "top.Y"], ["top.counter"], fin, fout, len(patterns)) + +for mode in ["", "_xprop"]: + if f"sat{mode}" not in steps: + continue + if not Path(f"wrapped{mode}.il").is_file(): + remove(f"sat{mode}.out") + continue + chunk_size = len(patterns) if args.seq else 32 + last_progress = 0 + with open(f"sat{mode}.ys", "w") as ys: + for chunk_start in range(0, len(patterns), chunk_size): + chunk = patterns[chunk_start : chunk_start + chunk_size] + progress = (10 * chunk_start) // len(patterns) + if progress > last_progress: + print(f"log sat {progress}0%", file=ys) + last_progress = progress + + append = "-a" if chunk_start else "-o" + print( + end=f"tee -q {append} sat{mode}.log sat -set-init-undef -seq {len(chunk)}" + " -show A -show Y -dump_vcd sat.vcd -enable_undef", + file=ys, + ) + for i, pattern in enumerate(chunk, 1): + ad = pattern.replace("x", "0") + ax = pattern.replace("1", "0").replace("x", "1") + print(end=f" -set-at {i} A {input_width}'b{pattern}", file=ys) + print(file=ys) + print(f"log sat 100%", file=ys) + + try: + yosys( + f""" + read_rtlil wrapped{mode}.il + clk2fflogic + script sat{mode}.ys + """ + ) + except subprocess.CalledProcessError: + remove(f"sat{mode}.out") + else: + sig_re = re.compile( + r" *[0-9]+ +\\([AY]) +(?:--|[0-9]+) +(?:--|[0-9a-f]+) +([01x]+)" + ) + current_input = None + with open(f"sat{mode}.log") as logfile, open(f"sat{mode}.out", "w") as outfile: + for line in logfile: + match = sig_re.match(line) + if match: + if match[1] == "A": + current_input = match[2] + else: + print(current_input, match[2], file=outfile) + +for mode in ["", "_xprop"]: + if f"opt_expr{mode}" not in steps: + continue + if not Path(f"wrapped{mode}.il").is_file(): + remove(f"opt_expr{mode}.out") + continue + yosys( + f""" + read_verilog const_tb.v + read_rtlil wrapped{mode}.il + hierarchy -top top; proc -noopt + flatten + opt_expr -keepdc; clean + dump -o opt_expr{mode}.list */\Y_* + """ + ) + + values = [] + + connect_re = re.compile(r" *connect +\\Y_([0-9]+) +[0-9]+'([01x]+)$") + with open(f"opt_expr{mode}.list") as connections: + for line in connections: + match = connect_re.match(line) + if match: + seq = int(match[1]) + data = match[2] + if len(data) < output_width: + data = data * output_width + values.append((seq, data)) + + values.sort() + + with open(f"opt_expr{mode}.out", "w") as outfile: + for seq, data in values: + print(patterns[seq], data, file=outfile) + + +if "compare" in steps: + groups = {} + missing = [] + + for output in all_outputs: + try: + with open(f"{output}.out") as f: + groups.setdefault(f.read(), []).append(output) + except FileNotFoundError: + missing.append(output) + + verified = Path(f"verified").is_file() + + with open("status", "w") as status: + name = Path('.').absolute().name + + status_list = [] + + if len(groups) > 1: + status_list.append("mismatch") + if not verified: + status_list.append("failed-verify") + if missing: + status_list.append("missing") + if not status_list: + status_list.append("ok") + print(f"{name}: {', '.join(status_list)}", file=status) + + if len(groups) > 1: + print("output differences:", file=status) + for group in groups.values(): + print(" -", *group, file=status) + if missing: + print("missing:", file=status) + print(" -", *missing, file=status) + + with open("status") as status: + print(end=status.read()) |