mirror of
https://github.com/YosysHQ/yosys
synced 2025-08-04 10:20:24 +00:00
Add option for using assoc list helpers in tests
This commit is contained in:
parent
8a9d724873
commit
a1d68fe3bc
3 changed files with 91 additions and 187 deletions
|
@ -43,21 +43,37 @@ def write_vcd(filename: Path, signals: SignalStepMap, timescale='1 ns', date='to
|
||||||
if change_time == time:
|
if change_time == time:
|
||||||
f.write(f"{value} {signal_name}\n")
|
f.write(f"{value} {signal_name}\n")
|
||||||
|
|
||||||
def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: Random):
|
|
||||||
|
def simulate_rosette(
|
||||||
|
rkt_file_path: Path,
|
||||||
|
vcd_path: Path,
|
||||||
|
num_steps: int,
|
||||||
|
rnd: Random,
|
||||||
|
use_assoc_list_helpers: bool = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
- use_assoc_list_helpers: If True, will use the association list helpers
|
||||||
|
in the Racket file. The file should have been generated with the
|
||||||
|
-assoc-list-helpers flag in the yosys command.
|
||||||
|
"""
|
||||||
signals: dict[str, list[str]] = {}
|
signals: dict[str, list[str]] = {}
|
||||||
inputs: SignalWidthMap = {}
|
inputs: SignalWidthMap = {}
|
||||||
outputs: SignalWidthMap = {}
|
outputs: SignalWidthMap = {}
|
||||||
|
|
||||||
current_struct_name: str = ""
|
current_struct_name: str = ""
|
||||||
with open(rkt_file_path, 'r') as rkt_file:
|
with open(rkt_file_path, "r") as rkt_file:
|
||||||
for line in rkt_file:
|
for line in rkt_file:
|
||||||
m = re.search(r'gold_(Inputs|Outputs|State)', line)
|
m = re.search(r"gold_(Inputs|Outputs|State)", line)
|
||||||
if m:
|
if m:
|
||||||
current_struct_name = m.group(1)
|
current_struct_name = m.group(1)
|
||||||
if current_struct_name == "State": break
|
if current_struct_name == "State":
|
||||||
elif not current_struct_name: continue # skip lines before structs
|
break
|
||||||
m = re.search(r'; (.+?)\b \(bitvector (\d+)\)', line)
|
elif not current_struct_name:
|
||||||
if not m: continue # skip non matching lines (probably closing the struct)
|
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)
|
signal = m.group(1)
|
||||||
width = int(m.group(2))
|
width = int(m.group(2))
|
||||||
if current_struct_name == "Inputs":
|
if current_struct_name == "Inputs":
|
||||||
|
@ -69,43 +85,86 @@ def simulate_rosette(rkt_file_path: Path, vcd_path: Path, num_steps: int, rnd: R
|
||||||
step_list: list[int] = []
|
step_list: list[int] = []
|
||||||
for step in range(num_steps):
|
for step in range(num_steps):
|
||||||
value = rnd.getrandbits(width)
|
value = rnd.getrandbits(width)
|
||||||
binary_string = format(value, '0{}b'.format(width))
|
binary_string = format(value, "0{}b".format(width))
|
||||||
step_list.append(binary_string)
|
step_list.append(binary_string)
|
||||||
signals[signal] = step_list
|
signals[signal] = step_list
|
||||||
|
|
||||||
test_rkt_file_path = rkt_file_path.with_suffix('.tst.rkt')
|
test_rkt_file_path = rkt_file_path.with_suffix(".tst.rkt")
|
||||||
with open(test_rkt_file_path, 'w') as test_rkt_file:
|
with open(test_rkt_file_path, "w") as test_rkt_file:
|
||||||
test_rkt_file.writelines([
|
test_rkt_file.writelines(
|
||||||
'#lang rosette\n',
|
[
|
||||||
f'(require "{rkt_file_path.name}")\n',
|
"#lang rosette\n",
|
||||||
])
|
f'(require "{rkt_file_path.name}")\n',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
for step in range(num_steps):
|
for step in range(num_steps):
|
||||||
this_step = f"step_{step}"
|
this_step = f"step_{step}"
|
||||||
value_list: list[str] = []
|
value_list: list[str] = []
|
||||||
for signal, width in inputs.items():
|
if use_assoc_list_helpers:
|
||||||
value = signals[signal][step]
|
# Generate inputs as a list of cons pairs making up the
|
||||||
value_list.append(f"(bv #b{value} {width})")
|
# association list.
|
||||||
gold_Inputs = f"(gold_Inputs {' '.join(value_list)})"
|
for signal, width in inputs.items():
|
||||||
|
value = signals[signal][step]
|
||||||
|
value_list.append(f'(cons "{signal}" (bv #b{value} {width}))')
|
||||||
|
else:
|
||||||
|
# Otherwise, we generate the inputs as a list of bitvectors.
|
||||||
|
for signal, width in inputs.items():
|
||||||
|
value = signals[signal][step]
|
||||||
|
value_list.append(f"(bv #b{value} {width})")
|
||||||
|
gold_Inputs = (
|
||||||
|
f"(gold_inputs_helper (list {' '.join(value_list)}))"
|
||||||
|
if use_assoc_list_helpers
|
||||||
|
else f"(gold_Inputs {' '.join(value_list)})"
|
||||||
|
)
|
||||||
gold_State = f"(cdr step_{step-1})" if step else "gold_initial"
|
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")
|
get_value_expr = (
|
||||||
|
f"(gold_outputs_helper (car {this_step}))"
|
||||||
|
if use_assoc_list_helpers
|
||||||
|
else f"(car {this_step})"
|
||||||
|
)
|
||||||
|
test_rkt_file.write(
|
||||||
|
f"(define {this_step} (gold {gold_Inputs} {gold_State})) {get_value_expr}\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
cmd = ["racket", test_rkt_file_path]
|
cmd = ["racket", test_rkt_file_path]
|
||||||
status = subprocess.run(cmd, capture_output=True)
|
try:
|
||||||
assert status.returncode == 0, f"{cmd[0]} failed"
|
status = subprocess.run(cmd, capture_output=True, check=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Racket simulation failed with command: {cmd}\n"
|
||||||
|
f"Error: {e.stderr.decode()}"
|
||||||
|
) from e
|
||||||
|
|
||||||
for signal in outputs.keys():
|
for signal in outputs.keys():
|
||||||
signals[signal] = []
|
signals[signal] = []
|
||||||
|
|
||||||
for line in status.stdout.decode().splitlines():
|
for line in status.stdout.decode().splitlines():
|
||||||
m = re.match(r'\(gold_Outputs( \(bv \S+ \d+\))+\)', line)
|
m = (
|
||||||
|
re.match(r"\(list( \(cons \"\S+\" \(bv \S+ \d+\)\))+\)", line)
|
||||||
|
if use_assoc_list_helpers
|
||||||
|
else re.match(r"\(gold_Outputs( \(bv \S+ \d+\))+\)", line)
|
||||||
|
)
|
||||||
assert m, f"Incomplete output definition {line!r}"
|
assert m, f"Incomplete output definition {line!r}"
|
||||||
for output, (value, width) in zip(outputs.keys(), re.findall(r'\(bv (\S+) (\d+)\)', line)):
|
outputs_values_and_widths = (
|
||||||
|
{
|
||||||
|
output: re.findall(
|
||||||
|
r"\(cons \"" + output + r"\" \(bv (\S+) (\d+)\)\)", line
|
||||||
|
)[0]
|
||||||
|
for output in outputs.keys()
|
||||||
|
}.items()
|
||||||
|
if use_assoc_list_helpers
|
||||||
|
else zip(outputs.keys(), re.findall(r"\(bv (\S+) (\d+)\)", line))
|
||||||
|
)
|
||||||
|
for output, (value, width) in outputs_values_and_widths:
|
||||||
assert isinstance(value, str), f"Bad value {value!r}"
|
assert isinstance(value, str), f"Bad value {value!r}"
|
||||||
assert value.startswith(('#b', '#x')), f"Non-binary 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]})"
|
assert (
|
||||||
int_value = int(value[2:], 16 if value.startswith('#x') else 2)
|
int(width) == outputs[output]
|
||||||
binary_string = format(int_value, '0{}b'.format(width))
|
), 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)
|
signals[output].append(binary_string)
|
||||||
|
|
||||||
vcd_signals: SignalStepMap = {}
|
vcd_signals: SignalStepMap = {}
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
; Utilities for simulating Rosette programs.
|
|
||||||
;
|
|
||||||
; Tests can be run with `raco test <this file>`.
|
|
||||||
#lang racket/base
|
|
||||||
|
|
||||||
(provide simulate-rosette)
|
|
||||||
|
|
||||||
(require (only-in rosette bv)
|
|
||||||
racket/list)
|
|
||||||
|
|
||||||
; Inputs:
|
|
||||||
; - function: The function for the module to simulate. This should be a Rosette function generated by
|
|
||||||
; Yosys's `write_fuctional_rosette` backend.
|
|
||||||
; - initial-state: The initial state of the module, as generated by Yosys's `write_fuctional_rosette`
|
|
||||||
; backend.
|
|
||||||
; - inputs: A list of association lists. The function will be called with each association list as
|
|
||||||
; inputs, and the state will be threaded through each call.
|
|
||||||
;
|
|
||||||
; Outputs:
|
|
||||||
; - A list of outputs, one for each cycle. The outputs are a list of the output objects generated by
|
|
||||||
; `function`.
|
|
||||||
(define (simulate-rosette #:function function #:initial-state initial-state #:inputs inputs)
|
|
||||||
|
|
||||||
(define outputs-and-states
|
|
||||||
(drop (reverse (foldl (lambda (input acc)
|
|
||||||
(let* ([outputs (function input (cdr (car acc)))]) (cons outputs acc)))
|
|
||||||
(list (cons 'unused initial-state))
|
|
||||||
inputs))
|
|
||||||
1))
|
|
||||||
|
|
||||||
(define outputs (map car outputs-and-states))
|
|
||||||
|
|
||||||
outputs)
|
|
||||||
|
|
||||||
; Inputs:
|
|
||||||
; - inputs: association list mapping string name to bitwidth.
|
|
||||||
; - num-inputs: number of inputs to generate.
|
|
||||||
; TODO(@gussmith23): If `num-inputs` is more than the number of possible values, just enumerate.
|
|
||||||
(define (generate-inputs #:inputs inputs #:num-inputs num-inputs)
|
|
||||||
(define (generate-random-input inputs)
|
|
||||||
(map (lambda (pair) (cons (car pair) (bv (random (expt 2 (cdr pair))) (cdr pair)))) inputs))
|
|
||||||
(for/list ([_ (range num-inputs)])
|
|
||||||
(generate-random-input inputs)))
|
|
||||||
|
|
||||||
; Generates a clock signal for the given inputs.
|
|
||||||
;
|
|
||||||
; Given a string of inputs, one per clock cycle, this function generates a clock signal alongside the
|
|
||||||
; inputs. It does so by alternating the clock signal between 0 and 1 for each cycle, starting with 0.
|
|
||||||
; For example, if the inputs are (list inputs1 inputs2 inputs3), the output will be (list (cons (cons
|
|
||||||
; "clk" (bv 0 1)) inputs1) (cons (cons "clk" (bv 1 1)) inputs1) (cons (cons "clk" (bv 0 1)) inputs2)
|
|
||||||
; (cons (cons "clk" (bv 1 1)) inputs2) ... ).
|
|
||||||
;
|
|
||||||
; Inputs:
|
|
||||||
; - clock-name: The name of the clock signal.
|
|
||||||
; - inputs: A list of inputs in association list form, as output by `generate-inputs`.
|
|
||||||
;
|
|
||||||
; Outputs:
|
|
||||||
; - A list of association lists, each containing a new clock signal. Will be twice the length of the
|
|
||||||
; inputs list.
|
|
||||||
(define (generate-clock #:clock-name clock-name #:inputs inputs)
|
|
||||||
(apply append
|
|
||||||
(for/list ([this-cycle-inputs inputs])
|
|
||||||
(list (cons (cons clock-name (bv 0 1)) this-cycle-inputs)
|
|
||||||
(cons (cons clock-name (bv 1 1)) this-cycle-inputs)))))
|
|
||||||
|
|
||||||
; This is what gets executed when the script is run.
|
|
||||||
(module main racket/base
|
|
||||||
(require racket/cmdline)
|
|
||||||
|
|
||||||
; - input-helper, output-helper: association-list-based helpers for input and output struct, generated
|
|
||||||
; by Yosys's `write_fuctional_rosette` backend with `-assoc-list-helpers` enabled.
|
|
||||||
)
|
|
||||||
|
|
||||||
(module+ test
|
|
||||||
(require rackunit
|
|
||||||
(only-in rosette bv bvadd))
|
|
||||||
(test-case "generate-inputs"
|
|
||||||
(check-equal? (length (generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10)) 10)
|
|
||||||
; Check that this call generates a list of one-length lists, each containing a single association
|
|
||||||
; list with the key "input1" and a random value.
|
|
||||||
(check-true (foldl (lambda (input acc)
|
|
||||||
(and acc (equal? (length input) 1) (equal? (car (first input)) "input1")))
|
|
||||||
#t
|
|
||||||
(generate-inputs #:inputs (list (cons "input1" 4)) #:num-inputs 10))))
|
|
||||||
|
|
||||||
(test-case "generate-clock"
|
|
||||||
(define inputs
|
|
||||||
(list (list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2)))
|
|
||||||
(list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2)))
|
|
||||||
(list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2)))))
|
|
||||||
|
|
||||||
(check-equal? (length (generate-clock #:clock-name "clk" #:inputs inputs)) 6)
|
|
||||||
|
|
||||||
(check-equal?
|
|
||||||
(generate-clock #:clock-name "clk" #:inputs inputs)
|
|
||||||
(list (cons (cons "clk" (bv 0 1))
|
|
||||||
(list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2))))
|
|
||||||
(cons (cons "clk" (bv 1 1))
|
|
||||||
(list (cons "input1" (bv 4 4)) (cons "input2" (bv 3 3)) (cons "input3" (bv 2 2))))
|
|
||||||
(cons (cons "clk" (bv 0 1))
|
|
||||||
(list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2))))
|
|
||||||
(cons (cons "clk" (bv 1 1))
|
|
||||||
(list (cons "input1" (bv 3 4)) (cons "input2" (bv 4 3)) (cons "input3" (bv 1 2))))
|
|
||||||
(cons (cons "clk" (bv 0 1))
|
|
||||||
(list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2))))
|
|
||||||
(cons (cons "clk" (bv 1 1))
|
|
||||||
(list (cons "input1" (bv 2 4)) (cons "input2" (bv 5 3)) (cons "input3" (bv 0 2)))))))
|
|
||||||
(test-case "simulate-rosette"
|
|
||||||
|
|
||||||
; This function will take association lists as inputs, so the helper function is simply identity.
|
|
||||||
; This is not generally true of Yosys-generated code. Similarly, this function uses an association
|
|
||||||
; list for state, which is not what Yosys generates, but it's easier for testing.
|
|
||||||
;
|
|
||||||
; A one-stage adder. Inputs are registered in one clock cycle, and the output is the sum of the
|
|
||||||
; two registered inputs.
|
|
||||||
(define (module-function inputs state)
|
|
||||||
(let* ([a (cdr (assoc "a" inputs))]
|
|
||||||
[b (cdr (assoc "b" inputs))]
|
|
||||||
[clk (cdr (assoc "clk" inputs))]
|
|
||||||
[old-clk (cdr (assoc "clk" state))]
|
|
||||||
[prev-a (cdr (assoc "prev-a" state))]
|
|
||||||
[prev-b (cdr (assoc "prev-b" state))]
|
|
||||||
[a-reg (cdr (assoc "a-reg" state))]
|
|
||||||
[b-reg (cdr (assoc "b-reg" state))]
|
|
||||||
[clk-ticked (and (equal? clk (bv 1 1)) (equal? old-clk (bv 0 1)))]
|
|
||||||
[new-a-reg (if clk-ticked prev-a a-reg)]
|
|
||||||
[new-b-reg (if clk-ticked prev-b b-reg)]
|
|
||||||
[out (list (cons "o" (bvadd new-a-reg new-b-reg)))]
|
|
||||||
[new-state (list (cons "prev-a" a)
|
|
||||||
(cons "a-reg" new-a-reg)
|
|
||||||
(cons "prev-b" b)
|
|
||||||
(cons "b-reg" new-b-reg)
|
|
||||||
(cons "clk" clk))])
|
|
||||||
(cons out new-state)))
|
|
||||||
|
|
||||||
(define outputs
|
|
||||||
(simulate-rosette #:function module-function
|
|
||||||
#:initial-state (list (cons "a-reg" (bv 0 4))
|
|
||||||
(cons "b-reg" (bv 0 4))
|
|
||||||
(cons "prev-a" (bv 0 4))
|
|
||||||
(cons "prev-b" (bv 0 4))
|
|
||||||
(cons "clk" (bv 0 1)))
|
|
||||||
#:inputs
|
|
||||||
(list (list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv 4 4)))
|
|
||||||
(list (cons "clk" (bv 1 1)) (cons "a" (bv 3 4)) (cons "b" (bv 0 4)))
|
|
||||||
(list (cons "clk" (bv 0 1)) (cons "a" (bv 10 4)) (cons "b" (bv 9 4)))
|
|
||||||
(list (cons "clk" (bv 1 1)) (cons "a" (bv 2 4)) (cons "b" (bv -1 4)))
|
|
||||||
(list (cons "clk" (bv 0 1)) (cons "a" (bv 4 4)) (cons "b" (bv -15 4)))
|
|
||||||
(list (cons "clk" (bv 1 1)) (cons "a" (bv 0 4)) (cons "b" (bv 0 4))))))
|
|
||||||
|
|
||||||
(check-equal? outputs
|
|
||||||
(list (list (cons "o" (bv 0 4)))
|
|
||||||
(list (cons "o" (bv 8 4)))
|
|
||||||
(list (cons "o" (bv 8 4)))
|
|
||||||
(list (cons "o" (bv 3 4)))
|
|
||||||
(list (cons "o" (bv 3 4)))
|
|
||||||
(list (cons "o" (bv -11 4)))))))
|
|
|
@ -74,7 +74,8 @@ def test_smt(cell, parameters, tmp_path, num_steps, rnd):
|
||||||
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
||||||
|
|
||||||
@pytest.mark.rkt
|
@pytest.mark.rkt
|
||||||
def test_rkt(cell, parameters, tmp_path, num_steps, rnd):
|
@pytest.mark.parametrize("use_assoc_list_helpers", [True, False])
|
||||||
|
def test_rkt(cell, parameters, tmp_path, num_steps, rnd, use_assoc_list_helpers):
|
||||||
import rkt_vcd
|
import rkt_vcd
|
||||||
|
|
||||||
rtlil_file = tmp_path / 'rtlil.il'
|
rtlil_file = tmp_path / 'rtlil.il'
|
||||||
|
@ -83,8 +84,9 @@ def test_rkt(cell, parameters, tmp_path, num_steps, rnd):
|
||||||
vcd_yosys_sim_file = tmp_path / 'yosys.vcd'
|
vcd_yosys_sim_file = tmp_path / 'yosys.vcd'
|
||||||
|
|
||||||
cell.write_rtlil_file(rtlil_file, parameters)
|
cell.write_rtlil_file(rtlil_file, parameters)
|
||||||
yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {quote(rkt_file)}")
|
use_assoc_helpers_flag = '-assoc-list-helpers' if use_assoc_list_helpers else ''
|
||||||
rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt"))
|
yosys(f"read_rtlil {quote(rtlil_file)} ; clk2fflogic ; write_functional_rosette -provides {use_assoc_helpers_flag} {quote(rkt_file)}")
|
||||||
|
rkt_vcd.simulate_rosette(rkt_file, vcd_functional_file, num_steps, rnd(cell.name + "-rkt"), use_assoc_list_helpers=use_assoc_list_helpers)
|
||||||
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
yosys_sim(rtlil_file, vcd_functional_file, vcd_yosys_sim_file, getattr(cell, 'sim_preprocessing', ''))
|
||||||
|
|
||||||
def test_print_graph(tmp_path):
|
def test_print_graph(tmp_path):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue