mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-03 22:16:09 +00:00
Merge pull request #5592 from YosysHQ/gus/5503-yw-load-error-msg
More helpful error messages when loading Yosys Witness files with `yosys-smtbmc`
This commit is contained in:
commit
b332279baf
2 changed files with 220 additions and 2 deletions
|
|
@ -735,6 +735,12 @@ def ywfile_signal(sig, step, mask=None):
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
|
|
||||||
|
def ywfile_signal_error(reason, detail=None):
|
||||||
|
msg = f"Yosys witness signal mismatch for {sig.pretty()}: {reason}"
|
||||||
|
if detail:
|
||||||
|
msg += f" ({detail})"
|
||||||
|
raise ValueError(msg)
|
||||||
|
|
||||||
if sig.path in smt_wires:
|
if sig.path in smt_wires:
|
||||||
for wire in smt_wires[sig.path]:
|
for wire in smt_wires[sig.path]:
|
||||||
width, offset = wire["width"], wire["offset"]
|
width, offset = wire["width"], wire["offset"]
|
||||||
|
|
@ -765,6 +771,12 @@ def ywfile_signal(sig, step, mask=None):
|
||||||
for mem in smt_mems[sig.memory_path]:
|
for mem in smt_mems[sig.memory_path]:
|
||||||
width, size, bv = mem["width"], mem["size"], mem["statebv"]
|
width, size, bv = mem["width"], mem["size"], mem["statebv"]
|
||||||
|
|
||||||
|
if sig.memory_addr is not None and sig.memory_addr >= size:
|
||||||
|
ywfile_signal_error(
|
||||||
|
"memory address out of bounds",
|
||||||
|
f"address={sig.memory_addr} size={size}",
|
||||||
|
)
|
||||||
|
|
||||||
smt_expr = smt.net_expr(topmod, f"s{step}", mem["smtpath"])
|
smt_expr = smt.net_expr(topmod, f"s{step}", mem["smtpath"])
|
||||||
|
|
||||||
if bv:
|
if bv:
|
||||||
|
|
@ -781,18 +793,34 @@ def ywfile_signal(sig, step, mask=None):
|
||||||
smt_expr = "((_ extract %d %d) %s)" % (slice_high, sig.offset, smt_expr)
|
smt_expr = "((_ extract %d %d) %s)" % (slice_high, sig.offset, smt_expr)
|
||||||
|
|
||||||
output.append((0, sig.width, smt_expr))
|
output.append((0, sig.width, smt_expr))
|
||||||
|
else:
|
||||||
|
ywfile_signal_error("memory not found in design")
|
||||||
|
|
||||||
output.sort()
|
output.sort()
|
||||||
|
|
||||||
output = [chunk for chunk in output if chunk[0] != chunk[1]]
|
output = [chunk for chunk in output if chunk[0] != chunk[1]]
|
||||||
|
|
||||||
|
if not output:
|
||||||
|
if sig.memory_path:
|
||||||
|
ywfile_signal_error("memory signal has no matching bits in design")
|
||||||
|
else:
|
||||||
|
ywfile_signal_error("signal not found in design")
|
||||||
|
|
||||||
pos = 0
|
pos = 0
|
||||||
|
|
||||||
for start, end, smt_expr in output:
|
for start, end, smt_expr in output:
|
||||||
assert start == pos
|
if start != pos:
|
||||||
|
ywfile_signal_error(
|
||||||
|
"signal width/offset mismatch",
|
||||||
|
f"expected coverage at bit {pos}",
|
||||||
|
)
|
||||||
pos = end
|
pos = end
|
||||||
|
|
||||||
assert pos == sig.width
|
if pos != sig.width:
|
||||||
|
ywfile_signal_error(
|
||||||
|
"signal width/offset mismatch",
|
||||||
|
f"covered {pos} of {sig.width} bits",
|
||||||
|
)
|
||||||
|
|
||||||
if len(output) == 1:
|
if len(output) == 1:
|
||||||
return output[0][-1]
|
return output[0][-1]
|
||||||
|
|
|
||||||
190
tests/functional/test_smtbmc_witness_mismatch.py
Normal file
190
tests/functional/test_smtbmc_witness_mismatch.py
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
import json
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
base_path = Path(__file__).resolve().parent.parent.parent
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(shutil.which("z3") is None, reason="z3 not available")
|
||||||
|
|
||||||
|
def run(cmd, **kwargs):
|
||||||
|
"""Run a command and assert it succeeds."""
|
||||||
|
status = subprocess.run(cmd, **kwargs)
|
||||||
|
assert status.returncode == 0, f"{cmd[0]} failed"
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
def write_smt2(tmp_path, verilog_text):
|
||||||
|
"""Write Verilog to temp and emit SMT2 via yosys."""
|
||||||
|
vfile = tmp_path / "design.v"
|
||||||
|
smt2 = tmp_path / "design.smt2"
|
||||||
|
vfile.write_text(verilog_text)
|
||||||
|
run([base_path / "yosys", "-Q", "-p",
|
||||||
|
f"read_verilog {vfile}; prep -top top; write_smt2 {smt2}"])
|
||||||
|
return smt2
|
||||||
|
|
||||||
|
|
||||||
|
def witness_entries(smt2_path):
|
||||||
|
"""Parse yosys-smt2-witness JSON records from an SMT2 file."""
|
||||||
|
entries = []
|
||||||
|
marker = "yosys-smt2-witness"
|
||||||
|
with open(smt2_path, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
if marker not in line:
|
||||||
|
continue
|
||||||
|
payload = line.split(marker, 1)[1].strip()
|
||||||
|
entries.append(json.loads(payload))
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def find_entry(entries, entry_type):
|
||||||
|
"""Return the first witness entry of the given type."""
|
||||||
|
for entry in entries:
|
||||||
|
if entry.get("type") == entry_type:
|
||||||
|
return entry
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def write_yw(yw_path, signals, bits):
|
||||||
|
"""Write a minimal Yosys witness file with one step of bits."""
|
||||||
|
data = {
|
||||||
|
"format": "Yosys Witness Trace",
|
||||||
|
"clocks": [],
|
||||||
|
"signals": signals,
|
||||||
|
"steps": [{"bits": bits}],
|
||||||
|
}
|
||||||
|
yw_path.write_text(json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
def run_smtbmc(smt2_path, yw_path):
|
||||||
|
"""Run yosys-smtbmc on the SMT2 file with a witness trace."""
|
||||||
|
cmd = [
|
||||||
|
base_path / "yosys-smtbmc",
|
||||||
|
"-s", "z3",
|
||||||
|
"-m", "top",
|
||||||
|
"--check-witness",
|
||||||
|
"--yw", yw_path,
|
||||||
|
"-t", "1",
|
||||||
|
smt2_path,
|
||||||
|
]
|
||||||
|
return subprocess.run(cmd, capture_output=True, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
def assert_no_mismatch_message(result):
|
||||||
|
"""Assert the mismatch error prefix is absent from outputs."""
|
||||||
|
combined = (result.stderr or "") + (result.stdout or "")
|
||||||
|
assert "Yosys witness signal mismatch" not in combined
|
||||||
|
|
||||||
|
|
||||||
|
def assert_has_mismatch_message(result, msg):
|
||||||
|
"""Assert the mismatch error prefix and substring are present."""
|
||||||
|
combined = (result.stderr or "") + (result.stdout or "")
|
||||||
|
assert "Yosys witness signal mismatch" in combined
|
||||||
|
assert msg in combined
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_signal_path(tmp_path):
|
||||||
|
smt2 = write_smt2(tmp_path, "module top(input ok, output out); assign out = ok; endmodule")
|
||||||
|
yw_path = tmp_path / "trace.yw"
|
||||||
|
signals = [{"path": ["\\missing"], "offset": 0, "width": 1, "init_only": False}]
|
||||||
|
write_yw(yw_path, signals, "1")
|
||||||
|
result = run_smtbmc(smt2, yw_path)
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert_has_mismatch_message(result, "signal not found in design")
|
||||||
|
|
||||||
|
|
||||||
|
def test_width_mismatch(tmp_path):
|
||||||
|
smt2 = write_smt2(tmp_path, "module top(input ok, output out); assign out = ok; endmodule")
|
||||||
|
entries = witness_entries(smt2)
|
||||||
|
input_entry = find_entry(entries, "input")
|
||||||
|
assert input_entry is not None
|
||||||
|
yw_path = tmp_path / "trace.yw"
|
||||||
|
signals = [{
|
||||||
|
"path": input_entry["path"],
|
||||||
|
"offset": 0,
|
||||||
|
"width": 2,
|
||||||
|
"init_only": False,
|
||||||
|
}]
|
||||||
|
write_yw(yw_path, signals, "10")
|
||||||
|
result = run_smtbmc(smt2, yw_path)
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert_has_mismatch_message(result, "signal width/offset mismatch")
|
||||||
|
|
||||||
|
|
||||||
|
def test_memory_address_oob(tmp_path):
|
||||||
|
verilog = """
|
||||||
|
module top(input ok, output [7:0] mem_out);
|
||||||
|
reg [7:0] mem [0:1];
|
||||||
|
assign mem_out = mem[0] ^ {8{ok}};
|
||||||
|
endmodule
|
||||||
|
"""
|
||||||
|
smt2 = write_smt2(tmp_path, verilog)
|
||||||
|
entries = witness_entries(smt2)
|
||||||
|
mem_entry = find_entry(entries, "mem")
|
||||||
|
assert mem_entry is not None
|
||||||
|
addr = mem_entry["size"]
|
||||||
|
yw_path = tmp_path / "trace.yw"
|
||||||
|
signals = [{
|
||||||
|
"path": mem_entry["path"] + [f"\\[{addr}]"],
|
||||||
|
"offset": 0,
|
||||||
|
"width": mem_entry["width"],
|
||||||
|
"init_only": False,
|
||||||
|
}]
|
||||||
|
bits = "0" * mem_entry["width"]
|
||||||
|
write_yw(yw_path, signals, bits)
|
||||||
|
result = run_smtbmc(smt2, yw_path)
|
||||||
|
assert result.returncode != 0
|
||||||
|
assert_has_mismatch_message(result, "memory address out of bounds")
|
||||||
|
|
||||||
|
|
||||||
|
def test_allowed_extra_signal_in_design(tmp_path):
|
||||||
|
verilog = """
|
||||||
|
module top(input ok, input extra, output [1:0] out);
|
||||||
|
assign out = {ok, extra};
|
||||||
|
endmodule
|
||||||
|
"""
|
||||||
|
smt2 = write_smt2(tmp_path, verilog)
|
||||||
|
entries = witness_entries(smt2)
|
||||||
|
input_entry = find_entry(entries, "input")
|
||||||
|
assert input_entry is not None
|
||||||
|
yw_path = tmp_path / "trace.yw"
|
||||||
|
signals = [{
|
||||||
|
"path": input_entry["path"],
|
||||||
|
"offset": 0,
|
||||||
|
"width": input_entry["width"],
|
||||||
|
"init_only": False,
|
||||||
|
}]
|
||||||
|
bits = "0" * input_entry["width"]
|
||||||
|
write_yw(yw_path, signals, bits)
|
||||||
|
result = run_smtbmc(smt2, yw_path)
|
||||||
|
# With --check-witness and no assertions, smtbmc can still exit non-zero.
|
||||||
|
# Thus we don't check the result.returncode here and in the other success
|
||||||
|
# cases.
|
||||||
|
assert_no_mismatch_message(result)
|
||||||
|
|
||||||
|
|
||||||
|
def test_allowed_extra_memory_in_design(tmp_path):
|
||||||
|
verilog = """
|
||||||
|
module top(input ok, output [7:0] out);
|
||||||
|
reg [7:0] mem0 [0:1];
|
||||||
|
reg [7:0] mem1 [0:3];
|
||||||
|
assign out = mem0[0] ^ mem1[0];
|
||||||
|
endmodule
|
||||||
|
"""
|
||||||
|
smt2 = write_smt2(tmp_path, verilog)
|
||||||
|
entries = witness_entries(smt2)
|
||||||
|
input_entry = find_entry(entries, "input")
|
||||||
|
assert input_entry is not None
|
||||||
|
yw_path = tmp_path / "trace.yw"
|
||||||
|
signals = [{
|
||||||
|
"path": input_entry["path"],
|
||||||
|
"offset": 0,
|
||||||
|
"width": input_entry["width"],
|
||||||
|
"init_only": False,
|
||||||
|
}]
|
||||||
|
bits = "1" * input_entry["width"]
|
||||||
|
write_yw(yw_path, signals, bits)
|
||||||
|
result = run_smtbmc(smt2, yw_path)
|
||||||
|
assert_no_mismatch_message(result)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue