tgt-flow: add dataflow exporter target (-tflow)
Add a new compile-time code-generator target that exports the elaborated
design as a JSON dataflow database (schema flowtracer1.merged.v0), shared
with the GHDL --flow exporter so a single consumer works across Verilog
and VHDL designs.
The exporter walks the elaborated netlist via ivl_target.h and emits:
- modules[] keyed by module type, with ports/generics/signals and
name-based processes[] and assignments[] (reads/drives/sensitivity
as signal names);
- hierarchy[] with port_map associations resolved through the shared
nexus, and generate blocks as transparent scope:true frames;
- a secondary integer-id nets[]/cells[] graph (drivers/loads,
clocked/clock_net).
Process reads/drives come from walking the statement/expression tree;
continuous assigns are recovered from logic/LPM devices with transitive
backward-cone resolution through synthetic nets. Generate scopes are
transparent to dataflow attribution and to port-map actual resolution.
Wired into the build (SUBDIRS, configure.ac), documented in
Documentation/targets/tgt-flow.rst, and covered by ivtest/flow_reg.py
(flow_basic, flow_comb, flow_generate, flow_genpath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:00:38 +02:00
|
|
|
''' Regression runner for the flow (-tflow) dataflow exporter target.
|
|
|
|
|
|
|
|
|
|
For each test named in flow.list, this compiles flow/<name>.v with
|
2026-06-19 22:24:51 +02:00
|
|
|
`iverilog -tflow` and validates the resulting flowtracer1.verilog.v0
|
tgt-flow: add dataflow exporter target (-tflow)
Add a new compile-time code-generator target that exports the elaborated
design as a JSON dataflow database (schema flowtracer1.merged.v0), shared
with the GHDL --flow exporter so a single consumer works across Verilog
and VHDL designs.
The exporter walks the elaborated netlist via ivl_target.h and emits:
- modules[] keyed by module type, with ports/generics/signals and
name-based processes[] and assignments[] (reads/drives/sensitivity
as signal names);
- hierarchy[] with port_map associations resolved through the shared
nexus, and generate blocks as transparent scope:true frames;
- a secondary integer-id nets[]/cells[] graph (drivers/loads,
clocked/clock_net).
Process reads/drives come from walking the statement/expression tree;
continuous assigns are recovered from logic/LPM devices with transitive
backward-cone resolution through synthetic nets. Generate scopes are
transparent to dataflow attribution and to port-map actual resolution.
Wired into the build (SUBDIRS, configure.ac), documented in
Documentation/targets/tgt-flow.rst, and covered by ivtest/flow_reg.py
(flow_basic, flow_comb, flow_generate, flow_genpath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:00:38 +02:00
|
|
|
JSON: every output must be well-formed and carry the expected schema,
|
|
|
|
|
a top, and a non-empty module/hierarchy set; per-test checks then
|
|
|
|
|
assert the dataflow facts the exporter is meant to capture (clocked
|
|
|
|
|
processes, port-map continuity, continuous-assign cone reads, and
|
|
|
|
|
generate frames).
|
|
|
|
|
|
|
|
|
|
Run with: python3 flow_reg.py '''
|
|
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import json
|
|
|
|
|
import subprocess
|
|
|
|
|
|
|
|
|
|
# Name of the iverilog command. May vary in different installations.
|
|
|
|
|
IVERILOG = "iverilog"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tests() -> list:
|
|
|
|
|
'''Read the test names from flow.list (first word of each line).'''
|
|
|
|
|
match_prog = re.compile(r"^([a-zA-Z0-9_.]+).*$")
|
|
|
|
|
tests = []
|
|
|
|
|
with open("flow.list", encoding='ascii') as fd:
|
|
|
|
|
for line in fd:
|
|
|
|
|
if line[0] == "#":
|
|
|
|
|
continue
|
|
|
|
|
match = match_prog.search(line)
|
|
|
|
|
if match:
|
|
|
|
|
tests.append(match.group(1))
|
|
|
|
|
return tests
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ---- per-test structural checks ----------------------------------------
|
|
|
|
|
# Each returns (ok, message). `d` is the parsed .flow document.
|
|
|
|
|
|
|
|
|
|
def _module(d, name):
|
|
|
|
|
for m in d["modules"]:
|
|
|
|
|
if m["name"] == name:
|
|
|
|
|
return m
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _find_in_hier(d, pred):
|
|
|
|
|
'''Depth-first search of hierarchy[] for a node matching pred.'''
|
|
|
|
|
stack = list(d["hierarchy"])
|
|
|
|
|
while stack:
|
|
|
|
|
n = stack.pop()
|
|
|
|
|
if pred(n):
|
|
|
|
|
return n
|
|
|
|
|
stack.extend(n.get("children", []))
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_basic(d):
|
|
|
|
|
if d["top"] != "top":
|
|
|
|
|
return False, "top != 'top'"
|
|
|
|
|
cnt = _module(d, "counter")
|
|
|
|
|
if not cnt:
|
|
|
|
|
return False, "no 'counter' module"
|
|
|
|
|
proc = [p for p in cnt["processes"]
|
|
|
|
|
if "count" in p["drives"] and p.get("sensitivity")]
|
|
|
|
|
if not proc:
|
|
|
|
|
return False, "no clocked process driving count"
|
|
|
|
|
# port_map continuity: u_cnt.count maps to parent net q
|
|
|
|
|
node = _find_in_hier(d, lambda n: n.get("module") == "counter")
|
|
|
|
|
if not node:
|
|
|
|
|
return False, "counter instance not in hierarchy"
|
|
|
|
|
pm = {a["formal"]: a["actuals"] for a in node["port_map"]}
|
|
|
|
|
if "q" not in pm.get("count", []):
|
|
|
|
|
return False, "port_map count->q missing"
|
|
|
|
|
return True, "clocked process + port_map ok"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_comb(d):
|
|
|
|
|
if d["top"] != "chip":
|
|
|
|
|
return False, "top != 'chip'"
|
|
|
|
|
dp = _module(d, "datapath")
|
|
|
|
|
if not dp:
|
|
|
|
|
return False, "no 'datapath' module"
|
|
|
|
|
res = [a for a in dp["assignments"]
|
|
|
|
|
if a["target"] == "result" and "alu_out" in a["reads"]]
|
|
|
|
|
if not res:
|
|
|
|
|
return False, "assign result<-alu_out (cone) missing"
|
|
|
|
|
return True, "continuous-assign cone read ok"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_generate(d):
|
|
|
|
|
frame = _find_in_hier(d, lambda n: n.get("scope") is True
|
|
|
|
|
and "generate" in n)
|
|
|
|
|
if not frame:
|
|
|
|
|
return False, "no generate frame in hierarchy"
|
|
|
|
|
kind = frame["generate"].get("kind")
|
|
|
|
|
if kind not in ("for", "if"):
|
|
|
|
|
return False, "generate.kind not for/if"
|
|
|
|
|
return True, "generate frame ({}) ok".format(kind)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def check_genpath(d):
|
|
|
|
|
# if-generate with whole-signal connection: the instance under the
|
|
|
|
|
# frame must resolve its actuals to the parent nets.
|
|
|
|
|
inst = _find_in_hier(d, lambda n: n.get("module") == "inv8")
|
|
|
|
|
if not inst:
|
|
|
|
|
return False, "inv8 instance not found under frame"
|
|
|
|
|
pm = {a["formal"]: a["actuals"] for a in inst["port_map"]}
|
|
|
|
|
if "din" not in pm.get("d", []) or "dout" not in pm.get("q", []):
|
|
|
|
|
return False, "port_map through frame not resolved"
|
|
|
|
|
return True, "generate-frame port_map ok"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CHECKS = {
|
|
|
|
|
"flow_basic": check_basic,
|
|
|
|
|
"flow_comb": check_comb,
|
|
|
|
|
"flow_generate": check_generate,
|
|
|
|
|
"flow_genpath": check_genpath,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_test(test_name: str) -> bool:
|
|
|
|
|
'''Compile one test with -tflow and validate its JSON.'''
|
|
|
|
|
src = "flow/" + test_name + ".v"
|
|
|
|
|
out = "flow/" + test_name + ".flow"
|
|
|
|
|
|
|
|
|
|
cmd = IVERILOG + " -g2012 -tflow -o" + out + " " + src
|
|
|
|
|
rc = subprocess.call(cmd + " > log/" + test_name + ".log 2>&1", shell=True)
|
|
|
|
|
if rc != 0:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
with open(out, encoding='ascii') as fd:
|
|
|
|
|
doc = json.load(fd)
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# Invariants common to every well-formed output.
|
2026-06-19 22:24:51 +02:00
|
|
|
if doc.get("schema") != "flowtracer1.verilog.v0":
|
tgt-flow: add dataflow exporter target (-tflow)
Add a new compile-time code-generator target that exports the elaborated
design as a JSON dataflow database (schema flowtracer1.merged.v0), shared
with the GHDL --flow exporter so a single consumer works across Verilog
and VHDL designs.
The exporter walks the elaborated netlist via ivl_target.h and emits:
- modules[] keyed by module type, with ports/generics/signals and
name-based processes[] and assignments[] (reads/drives/sensitivity
as signal names);
- hierarchy[] with port_map associations resolved through the shared
nexus, and generate blocks as transparent scope:true frames;
- a secondary integer-id nets[]/cells[] graph (drivers/loads,
clocked/clock_net).
Process reads/drives come from walking the statement/expression tree;
continuous assigns are recovered from logic/LPM devices with transitive
backward-cone resolution through synthetic nets. Generate scopes are
transparent to dataflow attribution and to port-map actual resolution.
Wired into the build (SUBDIRS, configure.ac), documented in
Documentation/targets/tgt-flow.rst, and covered by ivtest/flow_reg.py
(flow_basic, flow_comb, flow_generate, flow_genpath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:00:38 +02:00
|
|
|
return False
|
|
|
|
|
if not doc.get("top") or not doc.get("modules") or not doc.get("hierarchy"):
|
|
|
|
|
return False
|
2026-06-19 22:24:51 +02:00
|
|
|
# Every position is the compact "start:begin:end" byte-offset string.
|
|
|
|
|
if not _positions_well_formed(doc):
|
|
|
|
|
return False
|
tgt-flow: add dataflow exporter target (-tflow)
Add a new compile-time code-generator target that exports the elaborated
design as a JSON dataflow database (schema flowtracer1.merged.v0), shared
with the GHDL --flow exporter so a single consumer works across Verilog
and VHDL designs.
The exporter walks the elaborated netlist via ivl_target.h and emits:
- modules[] keyed by module type, with ports/generics/signals and
name-based processes[] and assignments[] (reads/drives/sensitivity
as signal names);
- hierarchy[] with port_map associations resolved through the shared
nexus, and generate blocks as transparent scope:true frames;
- a secondary integer-id nets[]/cells[] graph (drivers/loads,
clocked/clock_net).
Process reads/drives come from walking the statement/expression tree;
continuous assigns are recovered from logic/LPM devices with transitive
backward-cone resolution through synthetic nets. Generate scopes are
transparent to dataflow attribution and to port-map actual resolution.
Wired into the build (SUBDIRS, configure.ac), documented in
Documentation/targets/tgt-flow.rst, and covered by ivtest/flow_reg.py
(flow_basic, flow_comb, flow_generate, flow_genpath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:00:38 +02:00
|
|
|
|
|
|
|
|
check = CHECKS.get(test_name)
|
|
|
|
|
if check:
|
|
|
|
|
ok, _msg = check(doc)
|
|
|
|
|
return ok
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
2026-06-19 22:24:51 +02:00
|
|
|
_POS_RE = re.compile(r"^[0-9]*:[0-9]*:[0-9]*$")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _positions_well_formed(node) -> bool:
|
|
|
|
|
'''Every "pos"/"inst_pos" value, anywhere in the document, must be the
|
|
|
|
|
compact "start:begin:end" byte-offset string (parts may be empty).'''
|
|
|
|
|
if isinstance(node, dict):
|
|
|
|
|
for key, val in node.items():
|
|
|
|
|
if key in ("pos", "inst_pos"):
|
|
|
|
|
if not (isinstance(val, str) and _POS_RE.match(val)):
|
|
|
|
|
return False
|
|
|
|
|
elif not _positions_well_formed(val):
|
|
|
|
|
return False
|
|
|
|
|
return True
|
|
|
|
|
if isinstance(node, list):
|
|
|
|
|
return all(_positions_well_formed(x) for x in node)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
tgt-flow: add dataflow exporter target (-tflow)
Add a new compile-time code-generator target that exports the elaborated
design as a JSON dataflow database (schema flowtracer1.merged.v0), shared
with the GHDL --flow exporter so a single consumer works across Verilog
and VHDL designs.
The exporter walks the elaborated netlist via ivl_target.h and emits:
- modules[] keyed by module type, with ports/generics/signals and
name-based processes[] and assignments[] (reads/drives/sensitivity
as signal names);
- hierarchy[] with port_map associations resolved through the shared
nexus, and generate blocks as transparent scope:true frames;
- a secondary integer-id nets[]/cells[] graph (drivers/loads,
clocked/clock_net).
Process reads/drives come from walking the statement/expression tree;
continuous assigns are recovered from logic/LPM devices with transitive
backward-cone resolution through synthetic nets. Generate scopes are
transparent to dataflow attribution and to port-map actual resolution.
Wired into the build (SUBDIRS, configure.ac), documented in
Documentation/targets/tgt-flow.rst, and covered by ivtest/flow_reg.py
(flow_basic, flow_comb, flow_generate, flow_genpath).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 22:00:38 +02:00
|
|
|
def get_ivl_version() -> list:
|
|
|
|
|
'''Get the iverilog version.'''
|
|
|
|
|
text = subprocess.check_output([IVERILOG, "-V"])
|
|
|
|
|
match = re.search(b'Icarus Verilog version ([0-9]+)\\.([0-9]+)', text)
|
|
|
|
|
if not match:
|
|
|
|
|
return None
|
|
|
|
|
items = match.groups()
|
|
|
|
|
return [str(items[0], 'ascii'), str(items[1], 'ascii')]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run_tests(tests: list):
|
|
|
|
|
'''Run all tests in the list.'''
|
|
|
|
|
ivl_ver = get_ivl_version()
|
|
|
|
|
print("Running flow tests for Icarus Verilog version: {}.{}".format(
|
|
|
|
|
ivl_ver[0], ivl_ver[1]))
|
|
|
|
|
print("=" * 50)
|
|
|
|
|
|
|
|
|
|
if not os.path.exists("log"):
|
|
|
|
|
os.mkdir("log")
|
|
|
|
|
|
|
|
|
|
count_passed = 0
|
|
|
|
|
count_failed = 0
|
|
|
|
|
width = max(len(name) for name in tests)
|
|
|
|
|
for test in tests:
|
|
|
|
|
passed = run_test(test)
|
|
|
|
|
if passed:
|
|
|
|
|
count_passed += 1
|
|
|
|
|
res = "Passed"
|
|
|
|
|
else:
|
|
|
|
|
count_failed += 1
|
|
|
|
|
res = "Failed"
|
|
|
|
|
print("{name:>{width}}: {res}.".format(name=test, width=width, res=res))
|
|
|
|
|
|
|
|
|
|
print("=" * 50)
|
|
|
|
|
print("Tests results: Passed {}, Failed {}".format(count_passed, count_failed))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
run_tests(get_tests())
|