This commit is contained in:
wm2015email 2026-07-20 21:55:11 -07:00 committed by GitHub
commit 4f8323f27c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 2341 additions and 1 deletions

View File

@ -21,3 +21,4 @@ Icarus Verilog. The code generator is selected by the "-t" command line flag.
tgt-sizer
tgt-verilog
tgt-blif
tgt-flow

View File

@ -0,0 +1,99 @@
The Flow Dataflow Exporter (-tflow)
===================================
The flow code generator exports the elaborated design as a JSON
*dataflow database* rather than a simulation or netlist artifact. The
output describes the module hierarchy, the ports/parameters/signals of
each module, and the dataflow graph (which processes and continuous
assignments read and drive which nets), so that IDEs and analysis tools
can render dataflow panels, browse the elaborated hierarchy, and trace
input-to-output continuity across the design.
The emitted schema (``flowtracer1.verilog.v0``) is the Verilog companion
to the GHDL ``--flow`` exporter (``flowtracer1.vhdl.v0``), so a single
consumer works across Verilog (via this target) and VHDL (via GHDL)
designs.
USAGE
-----
::
% iverilog -tflow -o<path>.flow <source files>...
The flow target runs at elaboration time; no simulation is performed.
The root module of the elaborated design becomes the ``top`` of the
exported database. Because the exporter reads the fully elaborated
netlist, parameter values, generate blocks, and instance bindings are
all resolved.
OUTPUT
------
The output is a single JSON object with these top-level fields:
``schema``
Always ``"flowtracer1.verilog.v0"``.
``top``
The basename of the root module instance.
``modules``
One entry per module *type*, each with ``ports`` (name/direction/
type), ``generics`` (parameters), ``signals``, the name-based
``processes`` and ``assignments`` (with ``reads``/``drives``/
``sensitivity`` given as signal names), and the ``instances`` it
contains (with ``port_map`` and resolved ``generic_map``).
``hierarchy``
The elaborated instance tree. Each node carries its ``port_map``
(formal-to-actual associations resolved through the shared net) and
``children``. Generate blocks appear as transparent frames marked
``"scope": true`` with a ``generate`` object describing the kind
(``for``/``if``), label, and index.
``nets`` and ``cells``
A secondary, integer-id dataflow graph: ``nets`` are the canonical
nexuses (with ``drivers``/``loads`` referring to cell ids) and
``cells`` are the processes, gates, LPM devices, and constants (with
``drives``/``reads`` referring to net ids, plus ``clocked`` and
``clock_net``).
POSITIONS
---------
Every ``pos`` / ``inst_pos`` is a compact string of byte offsets,
``"start:begin:end"``, to help source-scanning consumers (such as Perl
lexers) locate constructs:
* *start* -- byte offset of the construct (first non-blank character of
its line);
* *begin* -- byte offset of the ``begin`` keyword that opens a
procedural or generate block -- empty when there is none;
* *end* -- byte offset of the closing ``end``/``endmodule`` keyword or
the terminating ``;`` -- empty for a plain point.
So a port or signal is ``"start::"``, a module is ``"start::end"``
(``module`` .. ``endmodule``), an instance is ``"start::end"`` (.. ``;``),
and an ``always``/``initial`` block or generate frame is
``"start:begin:end"``. Because the ``ivl_target`` API exposes only line
numbers, the exporter reads the source files and scans them (comment- and
string-aware) to recover these byte offsets and spans.
LIMITATIONS
-----------
Continuity through bit- and part-selects (for example a generate that
connects ``.d(bus[i])``) is not yet represented at the individual-bit
level; whole-signal connections are tracked.
For a process sensitive to several edges (for example an
asynchronous-reset flip-flop ``@(posedge clk or posedge rst)``), the
``clock_net`` field reports the first edge signal.
Constant tie-offs and synthetic compiler temporaries are summarized
rather than reported individually.

View File

@ -40,7 +40,7 @@ VERSION_MAJOR = @VERSION_MAJOR@
VERSION_MINOR = @VERSION_MINOR@
SUBDIRS = ivlpp vhdlpp vvp vpi tgt-null tgt-stub tgt-vvp \
tgt-vhdl tgt-vlog95 tgt-pcb tgt-blif tgt-sizer driver \
tgt-vhdl tgt-vlog95 tgt-pcb tgt-blif tgt-sizer tgt-flow driver \
ivtest
# Only run distclean for these directories.
NOTUSED = tgt-fpga tgt-pal tgt-verilog

View File

@ -453,6 +453,7 @@ AC_CONFIG_FILES([
ivtest/Makefile
libveriuser/Makefile
tgt-blif/Makefile
tgt-flow/Makefile
tgt-fpga/Makefile
tgt-null/Makefile
tgt-pal/Makefile

11
ivtest/flow.list Normal file
View File

@ -0,0 +1,11 @@
# Test list for the flow (-tflow) dataflow exporter target.
#
# Each entry names a Verilog source under flow/<name>.v. The runner
# flow_reg.py compiles it with `iverilog -tflow`, parses the resulting
# .flow JSON, and checks structural expectations (schema, hierarchy,
# processes/assignments, generate frames). Run with: python3 flow_reg.py
#
flow_basic
flow_comb
flow_generate
flow_genpath

3
ivtest/flow/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*.flow
*.log
log/

30
ivtest/flow/flow_basic.v Normal file
View File

@ -0,0 +1,30 @@
// Phase 0 test design: a counter instantiated inside a top module,
// to exercise scope hierarchy + nexus reachability in tgt-flow.
module counter #(parameter WIDTH = 4) (
input wire clk,
input wire rst,
input wire en,
output reg [WIDTH-1:0] count
);
always @(posedge clk or posedge rst) begin
if (rst)
count <= {WIDTH{1'b0}};
else if (en)
count <= count + 1'b1;
end
endmodule
module top (
input wire clk,
input wire rst,
output wire [3:0] q
);
wire enable = 1'b1;
counter #(.WIDTH(4)) u_cnt (
.clk (clk),
.rst (rst),
.en (enable),
.count (q)
);
endmodule

47
ivtest/flow/flow_comb.v Normal file
View File

@ -0,0 +1,47 @@
// Richer Phase 2 test: comb always with case, ternary, continuous
// assign, and two levels of hierarchy.
module alu (
input wire [1:0] op,
input wire [7:0] a,
input wire [7:0] b,
output reg [7:0] y
);
always @(*) begin
case (op)
2'b00: y = a + b;
2'b01: y = a - b;
2'b10: y = a & b;
default: y = a ^ b;
endcase
end
endmodule
module datapath (
input wire [1:0] sel,
input wire [7:0] x,
input wire [7:0] z,
output wire [7:0] result,
output wire is_zero
);
wire [7:0] alu_out;
alu u_alu (.op(sel), .a(x), .b(z), .y(alu_out));
assign result = alu_out;
assign is_zero = (alu_out == 8'b0) ? 1'b1 : 1'b0;
endmodule
module chip (
input wire [1:0] mode,
input wire [7:0] in0,
input wire [7:0] in1,
output wire [7:0] out,
output wire zero
);
datapath u_dp (
.sel (mode),
.x (in0),
.z (in1),
.result (out),
.is_zero(zero)
);
endmodule

View File

@ -0,0 +1,28 @@
// Phase 3 test: for-generate replicating a sub-module, plus an
// if-generate, to exercise generate-frame emission.
module inv #(parameter ID = 0) (
input wire d,
output wire q
);
assign q = ~d;
endmodule
module gen_top #(parameter N = 3) (
input wire [2:0] din,
output wire [2:0] dout
);
genvar i;
generate
for (i = 0; i < 3; i = i + 1) begin : row
inv #(.ID(i)) u_cell (.d(din[i]), .q(dout[i]));
end
endgenerate
generate
if (N > 0) begin : opt
wire tap;
assign tap = din[0];
end
endgenerate
endmodule

View File

@ -0,0 +1,13 @@
// Generate frame with WHOLE-signal port connections (isolates frame
// transparency from bit-select handling).
module inv8 (input wire [7:0] d, output wire [7:0] q);
assign q = ~d;
endmodule
module wrap (input wire [7:0] din, output wire [7:0] dout);
generate
if (1) begin : blk
inv8 u_inv (.d(din), .q(dout));
end
endgenerate
endmodule

210
ivtest/flow_reg.py Normal file
View File

@ -0,0 +1,210 @@
''' Regression runner for the flow (-tflow) dataflow exporter target.
For each test named in flow.list, this compiles flow/<name>.v with
`iverilog -tflow` and validates the resulting flowtracer1.verilog.v0
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.
if doc.get("schema") != "flowtracer1.verilog.v0":
return False
if not doc.get("top") or not doc.get("modules") or not doc.get("hierarchy"):
return False
# Every position is the compact "start:begin:end" byte-offset string.
if not _positions_well_formed(doc):
return False
check = CHECKS.get(test_name)
if check:
ok, _msg = check(doc)
return ok
return True
_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
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())

106
tgt-flow/Makefile.in Normal file
View File

@ -0,0 +1,106 @@
#
# This source code is free software; you can redistribute it
# and/or modify it in source code form under the terms of the GNU
# Library General Public License as published by the Free Software
# Foundation; either version 2 of the License, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this program; if not, write to the Free
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
# Boston, MA 02110-1301, USA.
#
SHELL = /bin/sh
suffix = @install_suffix@
prefix = @prefix@
exec_prefix = @exec_prefix@
srcdir = @srcdir@
VPATH = $(srcdir)
bindir = @bindir@
libdir = @libdir@
includedir = $(prefix)/include
CC = @CC@
INSTALL = @INSTALL@
INSTALL_PROGRAM = @INSTALL_PROGRAM@
INSTALL_DATA = @INSTALL_DATA@
ifeq (@srcdir@,.)
INCLUDE_PATH = -I. -I..
else
INCLUDE_PATH = -I. -I.. -I$(srcdir) -I$(srcdir)/..
endif
CPPFLAGS = $(INCLUDE_PATH) @CPPFLAGS@ @DEFS@ @PICFLAG@
CFLAGS = @WARNING_FLAGS@ @WARNING_FLAGS_CC@ @CFLAGS@
LDFLAGS = @LDFLAGS@
O = flow.o
all: dep flow.tgt
check: all
clean:
rm -rf *.o dep flow.tgt
distclean: clean
rm -f Makefile config.log
cppcheck: $(O:.o=.c)
cppcheck --enable=all --std=c99 --std=c++11 -f \
--check-level=exhaustive \
--suppressions-list=$(srcdir)/../cppcheck-global.sup \
--relative-paths=$(srcdir) $(INCLUDE_PATH) $^
Makefile: $(srcdir)/Makefile.in ../config.status
cd ..; ./config.status --file=tgt-flow/$@
dep:
mkdir dep
%.o: %.c | dep
$(CC) $(CPPFLAGS) $(CFLAGS) @DEPENDENCY_FLAG@ -c $< -o $*.o
mv $*.d dep
ifeq (@WIN32@,yes)
TGTLDFLAGS=-L.. -livl
TGTDEPLIBS=../libivl.a
else
TGTLDFLAGS=
TGTDEPLIBS=
endif
flow.tgt: $O $(TGTDEPLIBS)
$(CC) @shared@ $(LDFLAGS) -o $@ $O $(TGTLDFLAGS)
install: all installdirs installfiles
F = ./flow.tgt \
$(srcdir)/flow.conf \
$(srcdir)/flow-s.conf
installfiles: $(F) | installdirs
$(INSTALL_PROGRAM) ./flow.tgt "$(DESTDIR)$(libdir)/ivl$(suffix)/flow.tgt"
$(INSTALL_DATA) $(srcdir)/flow.conf "$(DESTDIR)$(libdir)/ivl$(suffix)/flow.conf"
$(INSTALL_DATA) $(srcdir)/flow-s.conf "$(DESTDIR)$(libdir)/ivl$(suffix)/flow-s.conf"
installdirs: $(srcdir)/../mkinstalldirs
$(srcdir)/../mkinstalldirs "$(DESTDIR)$(libdir)/ivl$(suffix)"
uninstall:
rm -f "$(DESTDIR)$(libdir)/ivl$(suffix)/flow.tgt"
rm -f "$(DESTDIR)$(libdir)/ivl$(suffix)/flow.conf"
rm -f "$(DESTDIR)$(libdir)/ivl$(suffix)/flow-s.conf"
-include $(patsubst %.o, dep/%.d, $O)

6
tgt-flow/flow-s.conf Normal file
View File

@ -0,0 +1,6 @@
functor:synth2
functor:synth
functor:syn-rules
functor:cprop
functor:nodangle
flag:DLL=flow.tgt

1782
tgt-flow/flow.c Normal file

File diff suppressed because it is too large Load Diff

3
tgt-flow/flow.conf Normal file
View File

@ -0,0 +1,3 @@
functor:cprop
functor:nodangle
flag:DLL=flow.tgt