From 0352503560f420c0792e15186ab8e2c60d05e380 Mon Sep 17 00:00:00 2001 From: Krystine Sherwin <93062060+KrystalDelusion@users.noreply.github.com> Date: Fri, 23 Aug 2024 09:05:44 +1200 Subject: [PATCH] smtr: Add rkt to functional tests --- tests/functional/.gitignore | 1 + tests/functional/rkt_vcd.py | 115 ++++++++++++++++++++++++++++ tests/functional/test_functional.py | 13 ++++ 3 files changed, 129 insertions(+) create mode 100644 tests/functional/rkt_vcd.py diff --git a/tests/functional/.gitignore b/tests/functional/.gitignore index 1ffe9926ef6..f0c4cf14e45 100644 --- a/tests/functional/.gitignore +++ b/tests/functional/.gitignore @@ -3,3 +3,4 @@ my_module_functional_cxx.cc vcd_harness *.vcd *.smt2 +*.rkt diff --git a/tests/functional/rkt_vcd.py b/tests/functional/rkt_vcd.py new file mode 100644 index 00000000000..1b2cf31e384 --- /dev/null +++ b/tests/functional/rkt_vcd.py @@ -0,0 +1,115 @@ +from __future__ import annotations +from pathlib import Path +import re +import subprocess +from typing import Dict, List, Tuple +from random import Random + +vcd_version = "Yosys/tests/functional/rkt_vcd.py" +StepList = List[Tuple[int, str]] +SignalStepMap = Dict[str, StepList] +SignalWidthMap = Dict[str, int] + +def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='today'): + with open(filename, 'w') as f: + # Write the header + f.write(f"$date\n {date}\n$end\n") + f.write(f"$timescale {timescale} $end\n") + + # Declare signals + f.write("$scope module gold $end\n") + for signal_name, changes in signals.items(): + signal_size = len(changes[0][1]) + f.write(f"$var wire {signal_size - 1} {signal_name} {signal_name} $end\n") + f.write("$upscope $end\n") + f.write("$enddefinitions $end\n") + + # Collect all unique timestamps + timestamps = sorted(set(time for changes in signals.values() for time, _ in changes)) + + # Write initial values + f.write("#0\n") + for signal_name, changes in signals.items(): + for time, value in changes: + if time == 0: + f.write(f"{value} {signal_name}\n") + + # Write value changes + for time in timestamps: + if time != 0: + f.write(f"#{time}\n") + for signal_name, changes in signals.items(): + for change_time, value in changes: + if change_time == time: + f.write(f"{value} {signal_name}\n") + +def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random): + signals: dict[str, list[str]] = {} + inputs: SignalWidthMap = {} + outputs: SignalWidthMap = {} + + current_struct_name: str = "" + with open(rkt_file_path, 'r') as rkt_file: + for line in rkt_file: + m = re.search(r'gold_(Inputs|Outputs|State)', line) + if m: + current_struct_name = m.group(1) + if current_struct_name == "State": break + elif not current_struct_name: continue # skip lines before structs + m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line) + if not m: continue # skip non matching lines (probably closing the struct) + signal = m.group(1) + width = int(m.group(2)) + if current_struct_name == "Inputs": + inputs[signal] = width + elif current_struct_name == "Outputs": + outputs[signal] = width + + for signal, width in inputs.items(): + step_list: list[int] = [] + for step in range(num_steps): + value = rnd.getrandbits(width) + binary_string = format(value, '0{}b'.format(width)) + step_list.append(binary_string) + signals[signal] = step_list + + test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt') + with open(test_rkt_file_path, 'w') as test_rkt_file: + test_rkt_file.writelines([ + '#lang rosette\n', + f'(require "{rkt_file_path.name}")\n', + ]) + + for step in range(num_steps): + this_step = f"step_{step}" + value_list: list[str] = [] + for signal, width in inputs.items(): + value = signals[signal][step] + value_list.append(f"(bv #b{value} {width})") + gold_Inputs = f"(gold_Inputs {' '.join(value_list)})" + gold_State = f"(cdr step_{step-1})" if step else "gold_initial" + test_rkt_file.write(f"(define {this_step} (gold {gold_Inputs} {gold_State})) (car {this_step})\n") + + cmd = ["racket", test_rkt_file_path] + status = subprocess.run(cmd, capture_output=True) + assert status.returncode == 0, f"{cmd[0]} failed" + + for signal in outputs.keys(): + signals[signal] = [] + + for line in status.stdout.decode().splitlines(): + m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line) + assert m, f"Incomplete output definition {line!r}" + for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)): + assert isinstance(value, str), f"Bad value {value!r}" + assert value.startswith(('#b', '#x')), f"Non-binary value {value!r}" + assert int(width) == outputs[output], f"Width mismatch for output {output!r} (got {width}, expected {outputs[output]})" + int_value = int(value[2:], 16 if value.startswith('#x') else 2) + binary_string = format(int_value, '0{}b'.format(width)) + signals[output].append(binary_string) + + vcd_signals: SignalStepMap = {} + for signal, steps in signals.items(): + vcd_signals[signal] = [(time, f"b{value}") for time, value in enumerate(steps)] + + write_vcd(vcd_path, vcd_signals) diff --git a/tests/functional/test_functional.py b/tests/functional/test_functional.py index 55e4acc3340..3b006676a14 100644 --- a/tests/functional/test_functional.py +++ b/tests/functional/test_functional.py @@ -72,6 +72,19 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd): smt_vcd.simulate_smt(smt_file, vcd_functional_file, num_steps, rnd(cell.name + "-smt")) yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) +def test_rkt(cell, parameters, tmp_path, num_steps, rnd): + import rkt_vcd + + rtlil_file = tmp_path / 'rtlil.il' + rkt_file = tmp_path / 'smtlib.rkt' + vcd_functional_file = tmp_path / 'functional.vcd' + vcd_yosys_sim_file = tmp_path / 'yosys.vcd' + + cell.write_rtlil_file(rtlil_file, parameters) + yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}") + rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt")) + yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', '')) + def test_print_graph(tmp_path): tb_file = base_path / 'tests/functional/picorv32_tb.v' cpu_file = base_path / 'tests/functional/picorv32.v'