From 9b44f8e0221854ee46c49bb54d54453e4fcca0b1 Mon Sep 17 00:00:00 2001 From: "William P. Moore" Date: Fri, 19 Jun 2026 14:00:38 -0600 Subject: [PATCH 1/2] 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) --- Documentation/targets/index.rst | 1 + Documentation/targets/tgt-flow.rst | 76 ++ Makefile.in | 2 +- configure.ac | 1 + ivtest/flow.list | 11 + ivtest/flow/.gitignore | 3 + ivtest/flow/flow_basic.v | 30 + ivtest/flow/flow_comb.v | 47 + ivtest/flow/flow_generate.v | 28 + ivtest/flow/flow_genpath.v | 13 + ivtest/flow_reg.py | 188 ++++ tgt-flow/Makefile.in | 106 ++ tgt-flow/flow-s.conf | 6 + tgt-flow/flow.c | 1507 ++++++++++++++++++++++++++++ tgt-flow/flow.conf | 3 + 15 files changed, 2021 insertions(+), 1 deletion(-) create mode 100644 Documentation/targets/tgt-flow.rst create mode 100644 ivtest/flow.list create mode 100644 ivtest/flow/.gitignore create mode 100644 ivtest/flow/flow_basic.v create mode 100644 ivtest/flow/flow_comb.v create mode 100644 ivtest/flow/flow_generate.v create mode 100644 ivtest/flow/flow_genpath.v create mode 100644 ivtest/flow_reg.py create mode 100644 tgt-flow/Makefile.in create mode 100644 tgt-flow/flow-s.conf create mode 100644 tgt-flow/flow.c create mode 100644 tgt-flow/flow.conf diff --git a/Documentation/targets/index.rst b/Documentation/targets/index.rst index b7db24dc1..89b9d5c75 100644 --- a/Documentation/targets/index.rst +++ b/Documentation/targets/index.rst @@ -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 diff --git a/Documentation/targets/tgt-flow.rst b/Documentation/targets/tgt-flow.rst new file mode 100644 index 000000000..3af827aaa --- /dev/null +++ b/Documentation/targets/tgt-flow.rst @@ -0,0 +1,76 @@ +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.merged.v0``) is shared with the GHDL +``--flow`` exporter, so a single consumer works across Verilog (via this +target) and VHDL (via GHDL) designs. + + +USAGE +----- + +:: + + % iverilog -tflow -o.flow ... + +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.merged.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``). + + +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. diff --git a/Makefile.in b/Makefile.in index 2503db9c7..900bebdd5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/configure.ac b/configure.ac index 9ad90e571..66d0287fc 100644 --- a/configure.ac +++ b/configure.ac @@ -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 diff --git a/ivtest/flow.list b/ivtest/flow.list new file mode 100644 index 000000000..a1f7275b0 --- /dev/null +++ b/ivtest/flow.list @@ -0,0 +1,11 @@ +# Test list for the flow (-tflow) dataflow exporter target. +# +# Each entry names a Verilog source under flow/.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 diff --git a/ivtest/flow/.gitignore b/ivtest/flow/.gitignore new file mode 100644 index 000000000..b29bf412f --- /dev/null +++ b/ivtest/flow/.gitignore @@ -0,0 +1,3 @@ +*.flow +*.log +log/ diff --git a/ivtest/flow/flow_basic.v b/ivtest/flow/flow_basic.v new file mode 100644 index 000000000..452672d3f --- /dev/null +++ b/ivtest/flow/flow_basic.v @@ -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 diff --git a/ivtest/flow/flow_comb.v b/ivtest/flow/flow_comb.v new file mode 100644 index 000000000..44928fd49 --- /dev/null +++ b/ivtest/flow/flow_comb.v @@ -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 diff --git a/ivtest/flow/flow_generate.v b/ivtest/flow/flow_generate.v new file mode 100644 index 000000000..fa42cf5c3 --- /dev/null +++ b/ivtest/flow/flow_generate.v @@ -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 diff --git a/ivtest/flow/flow_genpath.v b/ivtest/flow/flow_genpath.v new file mode 100644 index 000000000..92503491e --- /dev/null +++ b/ivtest/flow/flow_genpath.v @@ -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 diff --git a/ivtest/flow_reg.py b/ivtest/flow_reg.py new file mode 100644 index 000000000..f1396b23f --- /dev/null +++ b/ivtest/flow_reg.py @@ -0,0 +1,188 @@ +''' Regression runner for the flow (-tflow) dataflow exporter target. + + For each test named in flow.list, this compiles flow/.v with + `iverilog -tflow` and validates the resulting flowtracer1.merged.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.merged.v0": + return False + if not doc.get("top") or not doc.get("modules") or not doc.get("hierarchy"): + return False + + check = CHECKS.get(test_name) + if check: + ok, _msg = check(doc) + return ok + 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()) diff --git a/tgt-flow/Makefile.in b/tgt-flow/Makefile.in new file mode 100644 index 000000000..69155547d --- /dev/null +++ b/tgt-flow/Makefile.in @@ -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) diff --git a/tgt-flow/flow-s.conf b/tgt-flow/flow-s.conf new file mode 100644 index 000000000..14513f232 --- /dev/null +++ b/tgt-flow/flow-s.conf @@ -0,0 +1,6 @@ +functor:synth2 +functor:synth +functor:syn-rules +functor:cprop +functor:nodangle +flag:DLL=flow.tgt diff --git a/tgt-flow/flow.c b/tgt-flow/flow.c new file mode 100644 index 000000000..be8d5f42c --- /dev/null +++ b/tgt-flow/flow.c @@ -0,0 +1,1507 @@ +/* + * Copyright (c) 2026 Stephen Williams (steve@icarus.com) + * + * This source code is free software; you can redistribute it + * and/or modify it in source code form under the terms of the GNU + * 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 General Public License for more details. + * + * You should have received a copy of the GNU 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. + */ + +/* + * tgt-flow: a dataflow exporter target module for Icarus Verilog. + * + * Emits a `.flow` JSON dataflow database that is schema-compatible + * (flowtracer1.merged.v0) with the GHDL `--flow` exporter, so a single + * IDE / consumer (vpi_flowtracer's dftrace) works across Verilog and + * VHDL designs. + * + * PHASE 1: structural skeleton (envelope, modules[], hierarchy[], + * port_map[] resolved through the shared nexus). + * PHASE 2 (added here): the dataflow graph. + * - nets[] -- one entry per canonical nexus (ivl_nexus_t): id, name, + * path, is_port, aliases[], and bidirectional drivers[]/loads[] + * (lists of CELL ids). + * - cells[] -- the dataflow nodes (processes, logic gates, LPM + * devices, constants): id, kind, label, path, pos, clocked, + * clock_net (a NET id), and drives[]/reads[] (lists of NET ids). + * + * Process reads/drives are extracted by walking the elaborated + * statement/expression tree (the Verilog analogue of GHDL's + * Collect_Reads/Collect_Drivers). Because the design is run through + * cprop/nodangle (not synth), RTL behaviour stays as ivl_process_t, + * matching GHDL's process-centric model. + */ + +# include "version_base.h" +# include "version_tag.h" +# include "config.h" +# include +# include +# include +# include +# include +# include "ivl_target.h" + +static const char*version_string = +"Icarus Verilog FLOW Dataflow Exporter " VERSION " (" VERSION_TAG ")\n"; + +/* Output file (the -o argument). */ +static FILE*out; + +/* Running error count returned to the ivl_target environment. */ +static int flow_errors = 0; + +/* ================================================================== */ +/* Generic growable helpers */ +/* ================================================================== */ + +static void intset_add(int**arr, size_t*n, size_t*cap, int v) +{ + size_t i; + for (i = 0 ; i < *n ; i += 1) + if ((*arr)[i] == v) + return; + if (*n == *cap) { + size_t nc = *cap ? *cap * 2 : 8; + int*g = (int*)realloc(*arr, nc * sizeof(int)); + if (!g) { flow_errors += 1; return; } + *arr = g; *cap = nc; + } + (*arr)[(*n)++] = v; +} + +static void stradd(const char***arr, size_t*n, size_t*cap, const char*s) +{ + size_t i; + if (!s) s = ""; + for (i = 0 ; i < *n ; i += 1) + if (strcmp((*arr)[i], s) == 0) + return; + if (*n == *cap) { + size_t nc = *cap ? *cap * 2 : 8; + const char**g = (const char**)realloc(*arr, nc * sizeof(const char*)); + if (!g) { flow_errors += 1; return; } + *arr = g; *cap = nc; + } + (*arr)[(*n)++] = s; +} + +/* ================================================================== */ +/* Module-type set + file-name set (Phase 1) */ +/* ================================================================== */ + +struct scope_set { ivl_scope_t*items; size_t count, cap; }; +static struct scope_set modules = { NULL, 0, 0 }; + +static void scope_set_push(struct scope_set*set, ivl_scope_t sc) +{ + if (set->count == set->cap) { + size_t nc = set->cap ? set->cap * 2 : 32; + ivl_scope_t*g = (ivl_scope_t*)realloc(set->items, nc*sizeof(ivl_scope_t)); + if (!g) { flow_errors += 1; return; } + set->items = g; set->cap = nc; + } + set->items[set->count++] = sc; +} + +static struct str_set { const char**items; size_t count, cap; } files = { NULL, 0, 0 }; + +/* ================================================================== */ +/* Net table: one entry per canonical nexus */ +/* ================================================================== */ + +struct net_ent { + ivl_nexus_t nex; + const char*name; + const char*path; + int is_port; + const char**aliases; size_t nalias, alias_cap; + int*drivers; size_t ndrivers, drivers_cap; + int*loads; size_t nloads, loads_cap; +}; +static struct { struct net_ent*items; size_t count, cap; } nets = { NULL, 0, 0 }; + +/* Find or create the net id (1-based) for a nexus. `hint`, when given, + provides the canonical name/path (the first/parent signal). */ +static int net_id(ivl_nexus_t nex, ivl_signal_t hint) +{ + size_t i; + struct net_ent ent; + unsigned np, j; + + if (!nex) + return 0; + for (i = 0 ; i < nets.count ; i += 1) + if (nets.items[i].nex == nex) + return (int)(i + 1); + + memset(&ent, 0, sizeof ent); + ent.nex = nex; + ent.name = ""; + ent.path = ""; + + np = ivl_nexus_ptrs(nex); + for (j = 0 ; j < np ; j += 1) { + ivl_nexus_ptr_t ptr = ivl_nexus_ptr(nex, j); + ivl_signal_t s = ivl_nexus_ptr_sig(ptr); + if (!s) + continue; + stradd(&ent.aliases, &ent.nalias, &ent.alias_cap, + ivl_signal_basename(s)); + if (ivl_signal_port(s) != IVL_SIP_NONE) + ent.is_port = 1; + if (!hint && ent.name[0] == 0) { + ent.name = ivl_signal_basename(s); + ent.path = ivl_scope_name(ivl_signal_scope(s)); + } + } + if (hint) { + ent.name = ivl_signal_basename(hint); + ent.path = ivl_scope_name(ivl_signal_scope(hint)); + } + + if (nets.count == nets.cap) { + size_t nc = nets.cap ? nets.cap * 2 : 64; + struct net_ent*g = (struct net_ent*) + realloc(nets.items, nc * sizeof(struct net_ent)); + if (!g) { flow_errors += 1; return 0; } + nets.items = g; nets.cap = nc; + } + nets.items[nets.count++] = ent; + return (int)nets.count; +} + +/* Net id for a signal (uses word 0; arrays approximated). */ +static int net_of_sig(ivl_signal_t sig, ivl_signal_t hint) +{ + if (!sig) return 0; + return net_id(ivl_signal_nex(sig, 0), hint); +} + +/* ================================================================== */ +/* Cell table */ +/* ================================================================== */ + +struct cell_ent { + const char*kind; + char label[64]; + const char*path; + unsigned line; + int clocked; + int clock_net; + int*drives; size_t nd, dcap; + int*reads; size_t nr, rcap; +}; +static struct { struct cell_ent*items; size_t count, cap; } cells = { NULL, 0, 0 }; + +/* Every elaborated process, kept so emit_module() can render the + name-based modules[].processes[] the consumer actually traverses. */ +static struct { ivl_process_t*items; size_t count, cap; } proclist = { NULL, 0, 0 }; + +static void proclist_push(ivl_process_t p) +{ + if (proclist.count == proclist.cap) { + size_t nc = proclist.cap ? proclist.cap * 2 : 32; + ivl_process_t*g = (ivl_process_t*) + realloc(proclist.items, nc * sizeof(ivl_process_t)); + if (!g) { flow_errors += 1; return; } + proclist.items = g; proclist.cap = nc; + } + proclist.items[proclist.count++] = p; +} + +static struct cell_ent* cell_new(const char*kind, const char*path, unsigned line) +{ + struct cell_ent*c; + if (cells.count == cells.cap) { + size_t nc = cells.cap ? cells.cap * 2 : 32; + struct cell_ent*g = (struct cell_ent*) + realloc(cells.items, nc * sizeof(struct cell_ent)); + if (!g) { flow_errors += 1; return 0; } + cells.items = g; cells.cap = nc; + } + c = &cells.items[cells.count++]; + memset(c, 0, sizeof *c); + c->kind = kind; + c->path = path ? path : ""; + c->line = line; + c->clock_net = 0; + return c; +} + +static void cell_drive(struct cell_ent*c, int net) { if (net) intset_add(&c->drives,&c->nd,&c->dcap,net); } +static void cell_read (struct cell_ent*c, int net) { if (net) intset_add(&c->reads, &c->nr,&c->rcap,net); } + +/* ================================================================== */ +/* Process statement / expression walk */ +/* ================================================================== */ + +static void collect_expr(ivl_expr_t e, struct cell_ent*c); +static void collect_stmt(ivl_statement_t s, struct cell_ent*c); + +/* Recurse the expression tree collecting signal reads. Only the + operand accessors that are valid for each expression kind are called + -- ivl_expr_oper1/2/3 and ivl_expr_signal assert on the wrong kind. */ +static void collect_expr(ivl_expr_t e, struct cell_ent*c) +{ + unsigned i, n; + if (!e) + return; + switch (ivl_expr_type(e)) { + case IVL_EX_SIGNAL: + case IVL_EX_ARRAY: + case IVL_EX_MEMORY: + cell_read(c, net_of_sig(ivl_expr_signal(e), 0)); + collect_expr(ivl_expr_oper1(e), c); /* array/word index */ + break; + + case IVL_EX_UNARY: + collect_expr(ivl_expr_oper1(e), c); + break; + + case IVL_EX_BINARY: + case IVL_EX_SELECT: + collect_expr(ivl_expr_oper1(e), c); + collect_expr(ivl_expr_oper2(e), c); + break; + + case IVL_EX_TERNARY: + collect_expr(ivl_expr_oper1(e), c); + collect_expr(ivl_expr_oper2(e), c); + collect_expr(ivl_expr_oper3(e), c); + break; + + case IVL_EX_CONCAT: + case IVL_EX_SFUNC: + case IVL_EX_UFUNC: + n = ivl_expr_parms(e); + for (i = 0 ; i < n ; i += 1) + collect_expr(ivl_expr_parm(e, i), c); + break; + + default: + /* NUMBER, STRING, REALNUM, ULONG, SCOPE, ENUMTYPE, NULL, + PROPERTY, etc.: no signal reads to harvest for Phase 2. */ + break; + } +} + +/* Resolve an l-value to its driven signal, descending nested l-values. */ +static ivl_signal_t lval_sig(ivl_lval_t lv) +{ + while (lv && ivl_lval_sig(lv) == 0 && ivl_lval_nest(lv) != 0) + lv = ivl_lval_nest(lv); + return lv ? ivl_lval_sig(lv) : 0; +} + +static void collect_lvals(ivl_statement_t s, struct cell_ent*c) +{ + unsigned i, n = ivl_stmt_lvals(s); + for (i = 0 ; i < n ; i += 1) { + ivl_lval_t lv = ivl_stmt_lval(s, i); + cell_drive(c, net_of_sig(lval_sig(lv), 0)); + /* part-select / array-index expressions are reads */ + collect_expr(ivl_lval_part_off(lv), c); + collect_expr(ivl_lval_idx(lv), c); + } +} + +static void collect_events(ivl_statement_t s, struct cell_ent*c) +{ + unsigned ne = ivl_stmt_nevent(s); + unsigned i, k; + for (i = 0 ; i < ne ; i += 1) { + ivl_event_t ev = ivl_stmt_events(s, i); + if (!ev) + continue; + for (k = 0 ; k < ivl_event_npos(ev) ; k += 1) { + int id = net_id(ivl_event_pos(ev, k), 0); + cell_read(c, id); + c->clocked = 1; + if (c->clock_net == 0) c->clock_net = id; + } + for (k = 0 ; k < ivl_event_nneg(ev) ; k += 1) { + int id = net_id(ivl_event_neg(ev, k), 0); + cell_read(c, id); + c->clocked = 1; + if (c->clock_net == 0) c->clock_net = id; + } + for (k = 0 ; k < ivl_event_nany(ev) ; k += 1) + cell_read(c, net_id(ivl_event_any(ev, k), 0)); + } +} + +static void collect_stmt(ivl_statement_t s, struct cell_ent*c) +{ + unsigned i, n; + if (!s) + return; + switch (ivl_statement_type(s)) { + case IVL_ST_BLOCK: + case IVL_ST_FORK: + case IVL_ST_FORK_JOIN_ANY: + case IVL_ST_FORK_JOIN_NONE: + n = ivl_stmt_block_count(s); + for (i = 0 ; i < n ; i += 1) + collect_stmt(ivl_stmt_block_stmt(s, i), c); + break; + + case IVL_ST_ASSIGN: + case IVL_ST_ASSIGN_NB: + case IVL_ST_CASSIGN: + case IVL_ST_FORCE: + collect_lvals(s, c); + collect_expr(ivl_stmt_rval(s), c); + break; + + case IVL_ST_CONDIT: + collect_expr(ivl_stmt_cond_expr(s), c); + collect_stmt(ivl_stmt_cond_true(s), c); + collect_stmt(ivl_stmt_cond_false(s), c); + break; + + case IVL_ST_CASE: + case IVL_ST_CASEX: + case IVL_ST_CASEZ: + case IVL_ST_CASER: + collect_expr(ivl_stmt_cond_expr(s), c); + n = ivl_stmt_case_count(s); + for (i = 0 ; i < n ; i += 1) { + collect_expr(ivl_stmt_case_expr(s, i), c); + collect_stmt(ivl_stmt_case_stmt(s, i), c); + } + break; + + case IVL_ST_WAIT: + collect_events(s, c); + collect_stmt(ivl_stmt_sub_stmt(s), c); + break; + + case IVL_ST_DELAY: + collect_stmt(ivl_stmt_sub_stmt(s), c); + break; + case IVL_ST_DELAYX: + collect_expr(ivl_stmt_delay_expr(s), c); + collect_stmt(ivl_stmt_sub_stmt(s), c); + break; + + case IVL_ST_FORLOOP: + collect_stmt(ivl_stmt_init_stmt(s), c); + collect_expr(ivl_stmt_cond_expr(s), c); + collect_stmt(ivl_stmt_step_stmt(s), c); + collect_stmt(ivl_stmt_sub_stmt(s), c); + break; + + case IVL_ST_WHILE: + case IVL_ST_DO_WHILE: + collect_expr(ivl_stmt_cond_expr(s), c); + collect_stmt(ivl_stmt_sub_stmt(s), c); + break; + + case IVL_ST_STASK: + n = ivl_stmt_parm_count(s); + for (i = 0 ; i < n ; i += 1) + collect_expr(ivl_stmt_parm(s, i), c); + break; + + default: + /* NOOP, DISABLE, TRIGGER, ALLOC/FREE, etc.: no dataflow for + Phase 2. Nested statements that carry a sub_stmt are still + walked where it matters above. */ + break; + } +} + +/* ================================================================== */ +/* Cell builders */ +/* ================================================================== */ + +static int build_process(ivl_process_t proc, void*cd) +{ + ivl_scope_t scope = ivl_process_scope(proc); + ivl_statement_t st = ivl_process_stmt(proc); + const char*tname; + struct cell_ent*c; + (void)cd; + + if (ivl_process_analog(proc)) + return 0; /* analog processes: out of scope for Phase 2 */ + + proclist_push(proc); + + switch (ivl_process_type(proc)) { + case IVL_PR_INITIAL: tname = "initial"; break; + case IVL_PR_FINAL: tname = "final"; break; + case IVL_PR_ALWAYS_COMB: tname = "always_comb"; break; + case IVL_PR_ALWAYS_FF: tname = "always_ff"; break; + case IVL_PR_ALWAYS_LATCH: tname = "always_latch"; break; + case IVL_PR_ALWAYS: + default: tname = "always"; break; + } + + c = cell_new("process", ivl_scope_name(scope), + st ? ivl_stmt_lineno(st) : ivl_scope_lineno(scope)); + if (!c) + return 0; + snprintf(c->label, sizeof c->label, "%s@%u", tname, c->line); + + /* always_ff / always_comb / always_latch carry the edge intent. */ + if (ivl_process_type(proc) == IVL_PR_ALWAYS_FF) + c->clocked = 1; + + collect_stmt(st, c); + return 0; +} + +static void build_logic(ivl_net_logic_t log) +{ + unsigned np = ivl_logic_pins(log); + unsigned i; + struct cell_ent*c = cell_new("gate", ivl_scope_name(ivl_logic_scope(log)), + ivl_logic_lineno(log)); + if (!c) + return; + snprintf(c->label, sizeof c->label, "%s", ivl_logic_basename(log) + ? ivl_logic_basename(log) : "gate"); + /* pin 0 is the output, the rest are inputs. */ + if (np > 0) + cell_drive(c, net_id(ivl_logic_pin(log, 0), 0)); + for (i = 1 ; i < np ; i += 1) + cell_read(c, net_id(ivl_logic_pin(log, i), 0)); +} + +/* Resolve an LPM's valid input set into `buf` (returns the count). The + pin accessors assert on the wrong device kind, so the input shape is + driven off the LPM type. Shared by the integer-cell builder and the + name-based modules[].assignments[] emitter. */ +static unsigned lpm_input_nexuses(ivl_lpm_t lpm, ivl_nexus_t*buf, unsigned max) +{ + ivl_lpm_type_t t = ivl_lpm_type(lpm); + unsigned i, ndata = 0, n = 0; + int has_sel = 0, has_enable = 0, has_clk = 0; + + switch (t) { + case IVL_LPM_ADD: case IVL_LPM_SUB: case IVL_LPM_MULT: + case IVL_LPM_DIVIDE: case IVL_LPM_MOD: case IVL_LPM_POW: + case IVL_LPM_SHIFTL: case IVL_LPM_SHIFTR: + case IVL_LPM_CMP_EQ: case IVL_LPM_CMP_NE: case IVL_LPM_CMP_EEQ: + case IVL_LPM_CMP_NEE: case IVL_LPM_CMP_EQX: case IVL_LPM_CMP_EQZ: + case IVL_LPM_CMP_WEQ: case IVL_LPM_CMP_WNE: + case IVL_LPM_CMP_GE: case IVL_LPM_CMP_GT: + ndata = 2; break; + case IVL_LPM_RE_AND: case IVL_LPM_RE_NAND: case IVL_LPM_RE_NOR: + case IVL_LPM_RE_OR: case IVL_LPM_RE_XNOR: case IVL_LPM_RE_XOR: + case IVL_LPM_ABS: case IVL_LPM_SIGN_EXT: + case IVL_LPM_CAST_INT: case IVL_LPM_CAST_INT2: case IVL_LPM_CAST_REAL: + case IVL_LPM_PART_VP: case IVL_LPM_PART_PV: case IVL_LPM_REPEAT: + ndata = 1; break; + case IVL_LPM_MUX: + ndata = ivl_lpm_size(lpm); has_sel = 1; break; + case IVL_LPM_CONCAT: case IVL_LPM_CONCATZ: + case IVL_LPM_SFUNC: case IVL_LPM_UFUNC: case IVL_LPM_SUBSTITUTE: + ndata = ivl_lpm_size(lpm); break; + case IVL_LPM_FF: + ndata = 1; has_clk = 1; has_enable = 1; break; + case IVL_LPM_LATCH: + ndata = 1; has_enable = 1; break; + case IVL_LPM_ARRAY: + ndata = 1; has_sel = 1; break; + default: + ndata = 0; break; + } + + for (i = 0 ; i < ndata && n < max ; i += 1) + buf[n++] = ivl_lpm_data(lpm, i); + if (has_sel && n < max) buf[n++] = ivl_lpm_select(lpm); + if (has_enable && n < max) buf[n++] = ivl_lpm_enable(lpm); + if (has_clk && n < max) buf[n++] = ivl_lpm_clk(lpm); + return n; +} + +static void build_lpm(ivl_lpm_t lpm) +{ + struct cell_ent*c; + ivl_nexus_t ins[64]; + unsigned ni = lpm_input_nexuses(lpm, ins, 64), i; + + c = cell_new("lpm", ivl_scope_name(ivl_lpm_scope(lpm)), + ivl_lpm_lineno(lpm)); + if (!c) + return; + snprintf(c->label, sizeof c->label, "%s", ivl_lpm_basename(lpm) + ? ivl_lpm_basename(lpm) : "lpm"); + + cell_drive(c, net_id(ivl_lpm_q(lpm), 0)); + for (i = 0 ; i < ni ; i += 1) + cell_read(c, net_id(ins[i], 0)); + + /* Sequential LPMs (FF) carry an explicit clock pin. */ + if (ivl_lpm_type(lpm) == IVL_LPM_FF) { + ivl_nexus_t clk = ivl_lpm_clk(lpm); + if (clk) { + int id = net_id(clk, 0); + c->clocked = 1; + c->clock_net = id; + } + } +} + +static void build_const(ivl_net_const_t con) +{ + struct cell_ent*c = cell_new("const", ivl_scope_name(ivl_const_scope(con)), + ivl_const_lineno(con)); + if (!c) + return; + snprintf(c->label, sizeof c->label, "const"); + cell_drive(c, net_id(ivl_const_nex(con), 0)); +} + +/* Walk a scope tree collecting logic + lpm cells (processes and consts + are enumerated design-globally via ivl_design_process/const). */ +static int build_scope_cells(ivl_scope_t sc, void*cd) +{ + unsigned i, n; + (void)cd; + n = ivl_scope_logs(sc); + for (i = 0 ; i < n ; i += 1) + build_logic(ivl_scope_log(sc, i)); + n = ivl_scope_lpms(sc); + for (i = 0 ; i < n ; i += 1) + build_lpm(ivl_scope_lpm(sc, i)); + ivl_scope_children(sc, build_scope_cells, 0); + return 0; +} + +/* ================================================================== */ +/* Pre-registration of signal nets (deterministic top-down order) */ +/* ================================================================== */ + +static int prereg_nets(ivl_scope_t sc, void*cd) +{ + unsigned i, n = ivl_scope_sigs(sc); + (void)cd; + for (i = 0 ; i < n ; i += 1) { + ivl_signal_t sig = ivl_scope_sig(sc, i); + unsigned w, words = ivl_signal_array_count(sig); + if (words == 0) words = 1; + for (w = 0 ; w < words ; w += 1) + net_id(ivl_signal_nex(sig, w), sig); + } + ivl_scope_children(sc, prereg_nets, 0); + return 0; +} + +/* ================================================================== */ +/* JSON primitives */ +/* ================================================================== */ + +static void put_jstr(const char*s) +{ + putc('"', out); + if (s) { + for ( ; *s ; s += 1) { + unsigned char c = (unsigned char)*s; + switch (c) { + case '"': fputs("\\\"", out); break; + case '\\': fputs("\\\\", out); break; + case '\n': fputs("\\n", out); break; + case '\r': fputs("\\r", out); break; + case '\t': fputs("\\t", out); break; + default: + if (c < 0x20) fprintf(out, "\\u%04x", c); + else putc(c, out); + } + } + } + putc('"', out); +} + +static void put_pos(unsigned line) +{ + fprintf(out, "{\"off\": 0, \"line\": %u, \"col\": 0}", line); +} + +static void put_int_array(const int*a, size_t n) +{ + size_t i; + putc('[', out); + for (i = 0 ; i < n ; i += 1) { + if (i) fputs(", ", out); + fprintf(out, "%d", a[i]); + } + putc(']', out); +} + +static void put_str_array(const char**a, size_t n) +{ + size_t i; + putc('[', out); + for (i = 0 ; i < n ; i += 1) { + if (i) fputs(", ", out); + put_jstr(a[i]); + } + putc(']', out); +} + +static const char*dir_str(ivl_signal_port_t p) +{ + switch (p) { + case IVL_SIP_INPUT: return "in"; + case IVL_SIP_OUTPUT: return "out"; + case IVL_SIP_INOUT: return "inout"; + default: return ""; + } +} + +static void put_sig_type(ivl_signal_t sig) +{ + unsigned d, nd = ivl_signal_packed_dimensions(sig); + const char*base; + switch (ivl_signal_data_type(sig)) { + case IVL_VT_REAL: base = "real"; break; + case IVL_VT_STRING: base = "string"; break; + case IVL_VT_BOOL: base = "bit"; break; + case IVL_VT_LOGIC: + default: base = "logic"; break; + } + putc('"', out); + fputs(base, out); + for (d = 0 ; d < nd ; d += 1) + fprintf(out, "[%d:%d]", + ivl_signal_packed_msb(sig, d), + ivl_signal_packed_lsb(sig, d)); + putc('"', out); +} + +static void put_param_value(ivl_parameter_t par) +{ + ivl_expr_t e = par ? ivl_parameter_expr(par) : 0; + if (e && ivl_expr_type(e) == IVL_EX_NUMBER) { + const char*bits = ivl_expr_bits(e); + unsigned w = ivl_expr_width(e), i; + int pure = 1; + if (bits) { + for (i = 0 ; i < w ; i += 1) + if (bits[i] != '0' && bits[i] != '1') { pure = 0; break; } + if (pure && w <= 64) { + uint64_t v = 0; + for (i = 0 ; i < w ; i += 1) + if (bits[i] == '1') v |= (uint64_t)1 << i; + fprintf(out, "%" PRIu64, v); + return; + } + putc('"', out); + for (i = w ; i > 0 ; i -= 1) putc(bits[i-1], out); + putc('"', out); + return; + } + } + fputs("null", out); +} + +/* ================================================================== */ +/* Module / file collection (Phase 1) */ +/* ================================================================== */ + +static void str_set_add(struct str_set*set, const char*s) +{ + size_t i; + if (!s || s[0] == 0) return; + for (i = 0 ; i < set->count ; i += 1) + if (strcmp(set->items[i], s) == 0) return; + if (set->count == set->cap) { + size_t nc = set->cap ? set->cap * 2 : 16; + const char**g = (const char**)realloc(set->items, nc*sizeof(const char*)); + if (!g) { flow_errors += 1; return; } + set->items = g; set->cap = nc; + } + set->items[set->count++] = s; +} + +static int module_seen(ivl_scope_t sc) +{ + size_t i; + const char*tn = ivl_scope_tname(sc); + for (i = 0 ; i < modules.count ; i += 1) + if (strcmp(ivl_scope_tname(modules.items[i]), tn) == 0) return 1; + return 0; +} + +static int collect_modules(ivl_scope_t sc, void*cd) +{ + (void)cd; + if (ivl_scope_type(sc) == IVL_SCT_MODULE) { + str_set_add(&files, ivl_scope_file(sc)); + if (!module_seen(sc)) scope_set_push(&modules, sc); + } + ivl_scope_children(sc, collect_modules, 0); + return 0; +} + +/* ================================================================== */ +/* port_map (Phase 1) */ +/* ================================================================== */ + +/* True if `target` is in the enclosing chain of `port_scope`: the + immediate parent and any transparent generate/begin frames above it, + up to and including the first enclosing module. This is exactly the + set of scopes whose nets are valid port actuals -- it stops at the + instantiating module so a same-named net further up doesn't leak in. */ +static int in_enclosing_chain(ivl_scope_t target, ivl_scope_t port_scope) +{ + ivl_scope_t p = ivl_scope_parent(port_scope); + while (p) { + if (p == target) return 1; + if (ivl_scope_type(p) == IVL_SCT_MODULE) return 0; + p = ivl_scope_parent(p); + } + return 0; +} + +/* An actual is a signal sharing the port's nexus that lives in the + instantiating scope (across transparent generate/begin frames). */ +static int sig_is_actual(ivl_signal_t s, ivl_signal_t port_sig) +{ + return s && s != port_sig + && in_enclosing_chain(ivl_signal_scope(s), ivl_signal_scope(port_sig)); +} + +static void emit_port_assoc(ivl_signal_t port_sig, int first) +{ + const char*formal = ivl_signal_basename(port_sig); + ivl_nexus_t nex = ivl_signal_nex(port_sig, 0); + const char*actual_first = 0; + unsigned np, i; + int afirst; + + if (!first) fputs(", ", out); + fputs("{\"formal\": ", out); put_jstr(formal); + fputs(", \"fbase\": ", out); put_jstr(formal); + + np = nex ? ivl_nexus_ptrs(nex) : 0; + for (i = 0 ; i < np ; i += 1) { + ivl_signal_t s = ivl_nexus_ptr_sig(ivl_nexus_ptr(nex, i)); + if (sig_is_actual(s, port_sig)) { + actual_first = ivl_signal_basename(s); + break; + } + } + fputs(", \"actual\": ", out); + put_jstr(actual_first ? actual_first : formal); + + fputs(", \"actuals\": [", out); + afirst = 1; + for (i = 0 ; i < np ; i += 1) { + ivl_signal_t s = ivl_nexus_ptr_sig(ivl_nexus_ptr(nex, i)); + if (sig_is_actual(s, port_sig)) { + if (!afirst) fputs(", ", out); + afirst = 0; + put_jstr(ivl_signal_basename(s)); + } + } + fputs("]}", out); +} + +static void emit_port_map(ivl_scope_t scope) +{ + unsigned nsig = ivl_scope_sigs(scope), i; + int first = 1; + for (i = 0 ; i < nsig ; i += 1) { + ivl_signal_t sig = ivl_scope_sig(scope, i); + if (ivl_signal_port(sig) == IVL_SIP_NONE) continue; + emit_port_assoc(sig, first); + first = 0; + } +} + +static void emit_generic_map(ivl_scope_t scope) +{ + unsigned np = ivl_scope_params(scope), i; + int first = 1; + for (i = 0 ; i < np ; i += 1) { + ivl_parameter_t par = ivl_scope_param(scope, i); + if (!first) fputs(", ", out); + first = 0; + fputs("{\"formal\": ", out); put_jstr(ivl_parameter_basename(par)); + fputs(", \"value\": ", out); put_param_value(par); + putc('}', out); + } +} + +/* ================================================================== */ +/* modules[] (Phase 1) */ +/* ================================================================== */ + +/* ------------------------------------------------------------------ */ +/* Name-based modules[].processes[] (what the consumer traverses) */ +/* ------------------------------------------------------------------ */ + +struct nameset { const char**items; size_t n, cap; }; +static void nameset_add(struct nameset*s, const char*name) +{ + if (name) stradd(&s->items, &s->n, &s->cap, name); +} + +/* Best signal basename for a nexus, preferring one in `prefer` scope. */ +static const char*nexus_signame(ivl_nexus_t nex, ivl_scope_t prefer) +{ + unsigned np = nex ? ivl_nexus_ptrs(nex) : 0, i; + const char*any = 0; + for (i = 0 ; i < np ; i += 1) { + ivl_signal_t s = ivl_nexus_ptr_sig(ivl_nexus_ptr(nex, i)); + if (!s) continue; + if (!any) any = ivl_signal_basename(s); + if (ivl_signal_scope(s) == prefer) + return ivl_signal_basename(s); + } + return any; +} + +static void cn_expr(ivl_expr_t e, struct nameset*reads) +{ + unsigned i, n; + if (!e) return; + switch (ivl_expr_type(e)) { + case IVL_EX_SIGNAL: + case IVL_EX_ARRAY: + case IVL_EX_MEMORY: + if (ivl_expr_signal(e)) + nameset_add(reads, ivl_signal_basename(ivl_expr_signal(e))); + cn_expr(ivl_expr_oper1(e), reads); + break; + case IVL_EX_UNARY: + cn_expr(ivl_expr_oper1(e), reads); + break; + case IVL_EX_BINARY: + case IVL_EX_SELECT: + cn_expr(ivl_expr_oper1(e), reads); + cn_expr(ivl_expr_oper2(e), reads); + break; + case IVL_EX_TERNARY: + cn_expr(ivl_expr_oper1(e), reads); + cn_expr(ivl_expr_oper2(e), reads); + cn_expr(ivl_expr_oper3(e), reads); + break; + case IVL_EX_CONCAT: + case IVL_EX_SFUNC: + case IVL_EX_UFUNC: + n = ivl_expr_parms(e); + for (i = 0 ; i < n ; i += 1) + cn_expr(ivl_expr_parm(e, i), reads); + break; + default: + break; + } +} + +static void cn_stmt(ivl_statement_t s, struct nameset*reads, + struct nameset*drives, struct nameset*sens, + ivl_scope_t scope) +{ + unsigned i, n; + if (!s) return; + switch (ivl_statement_type(s)) { + case IVL_ST_BLOCK: + case IVL_ST_FORK: + case IVL_ST_FORK_JOIN_ANY: + case IVL_ST_FORK_JOIN_NONE: + n = ivl_stmt_block_count(s); + for (i = 0 ; i < n ; i += 1) + cn_stmt(ivl_stmt_block_stmt(s, i), reads, drives, sens, scope); + break; + case IVL_ST_ASSIGN: + case IVL_ST_ASSIGN_NB: + case IVL_ST_CASSIGN: + case IVL_ST_FORCE: + n = ivl_stmt_lvals(s); + for (i = 0 ; i < n ; i += 1) { + ivl_signal_t sig = lval_sig(ivl_stmt_lval(s, i)); + if (sig) nameset_add(drives, ivl_signal_basename(sig)); + cn_expr(ivl_lval_part_off(ivl_stmt_lval(s, i)), reads); + cn_expr(ivl_lval_idx(ivl_stmt_lval(s, i)), reads); + } + cn_expr(ivl_stmt_rval(s), reads); + break; + case IVL_ST_CONDIT: + cn_expr(ivl_stmt_cond_expr(s), reads); + cn_stmt(ivl_stmt_cond_true(s), reads, drives, sens, scope); + cn_stmt(ivl_stmt_cond_false(s), reads, drives, sens, scope); + break; + case IVL_ST_CASE: + case IVL_ST_CASEX: + case IVL_ST_CASEZ: + case IVL_ST_CASER: + cn_expr(ivl_stmt_cond_expr(s), reads); + n = ivl_stmt_case_count(s); + for (i = 0 ; i < n ; i += 1) { + cn_expr(ivl_stmt_case_expr(s, i), reads); + cn_stmt(ivl_stmt_case_stmt(s, i), reads, drives, sens, scope); + } + break; + case IVL_ST_WAIT: { + unsigned ne = ivl_stmt_nevent(s), k; + for (i = 0 ; i < ne ; i += 1) { + ivl_event_t ev = ivl_stmt_events(s, i); + if (!ev) continue; + for (k = 0 ; k < ivl_event_npos(ev) ; k += 1) { + const char*nm = nexus_signame(ivl_event_pos(ev,k), scope); + nameset_add(sens, nm); nameset_add(reads, nm); + } + for (k = 0 ; k < ivl_event_nneg(ev) ; k += 1) { + const char*nm = nexus_signame(ivl_event_neg(ev,k), scope); + nameset_add(sens, nm); nameset_add(reads, nm); + } + for (k = 0 ; k < ivl_event_nany(ev) ; k += 1) { + const char*nm = nexus_signame(ivl_event_any(ev,k), scope); + nameset_add(sens, nm); nameset_add(reads, nm); + } + } + cn_stmt(ivl_stmt_sub_stmt(s), reads, drives, sens, scope); + break; + } + case IVL_ST_DELAY: + cn_stmt(ivl_stmt_sub_stmt(s), reads, drives, sens, scope); + break; + case IVL_ST_DELAYX: + cn_expr(ivl_stmt_delay_expr(s), reads); + cn_stmt(ivl_stmt_sub_stmt(s), reads, drives, sens, scope); + break; + case IVL_ST_FORLOOP: + cn_stmt(ivl_stmt_init_stmt(s), reads, drives, sens, scope); + cn_expr(ivl_stmt_cond_expr(s), reads); + cn_stmt(ivl_stmt_step_stmt(s), reads, drives, sens, scope); + cn_stmt(ivl_stmt_sub_stmt(s), reads, drives, sens, scope); + break; + case IVL_ST_WHILE: + case IVL_ST_DO_WHILE: + cn_expr(ivl_stmt_cond_expr(s), reads); + cn_stmt(ivl_stmt_sub_stmt(s), reads, drives, sens, scope); + break; + case IVL_ST_STASK: + n = ivl_stmt_parm_count(s); + for (i = 0 ; i < n ; i += 1) + cn_expr(ivl_stmt_parm(s, i), reads); + break; + default: + break; + } +} + +static void put_name_array(struct nameset*ns) +{ + size_t i; + putc('[', out); + for (i = 0 ; i < ns->n ; i += 1) { + if (i) fputs(", ", out); + put_jstr(ns->items[i]); + } + putc(']', out); +} + +/* The enclosing module of a scope: walk up through transparent + generate/begin/fork scopes to the first IVL_SCT_MODULE ancestor (or + the scope itself). Generate bodies are part of their module, so their + dataflow is attributed to that module. */ +static ivl_scope_t owning_module(ivl_scope_t sc) +{ + while (sc && ivl_scope_type(sc) != IVL_SCT_MODULE) + sc = ivl_scope_parent(sc); + return sc; +} + +/* Emit the name-based processes[] for the processes owned by module + `scope` -- those living directly in it or in a transparent + generate/begin scope nested under it (but not under a child module). */ +static void emit_processes(ivl_scope_t scope) +{ + size_t pi; + int first = 1; + for (pi = 0 ; pi < proclist.count ; pi += 1) { + ivl_process_t proc = proclist.items[pi]; + ivl_scope_t pscope = ivl_process_scope(proc); + ivl_statement_t st; + struct nameset reads = {0,0,0}, drives = {0,0,0}, sens = {0,0,0}; + const char*tname; + unsigned line; + char label[64]; + + if (owning_module(pscope) != scope) + continue; + st = ivl_process_stmt(proc); + line = st ? ivl_stmt_lineno(st) : ivl_scope_lineno(scope); + switch (ivl_process_type(proc)) { + case IVL_PR_INITIAL: tname = "initial"; break; + case IVL_PR_FINAL: tname = "final"; break; + case IVL_PR_ALWAYS_COMB: tname = "always_comb"; break; + case IVL_PR_ALWAYS_FF: tname = "always_ff"; break; + case IVL_PR_ALWAYS_LATCH: tname = "always_latch"; break; + default: tname = "always"; break; + } + snprintf(label, sizeof label, "%s@%u", tname, line); + + cn_stmt(st, &reads, &drives, &sens, pscope); + + if (!first) fputs(", ", out); + first = 0; + fputs("{\"label\": ", out); put_jstr(label); + fputs(", \"pos\": ", out); put_pos(line); + fputs(", \"sensitivity\": ", out); put_name_array(&sens); + fputs(", \"drives\": ", out); put_name_array(&drives); + fputs(", \"reads\": ", out); put_name_array(&reads); + putc('}', out); + + free(reads.items); free(drives.items); free(sens.items); + } +} + +/* Skip iverilog's synthetic temporaries (e.g. "_ivl_3") so the + name-based assignment view stays in terms of real signals. */ +static int synth_name(const char*s) +{ + return s == 0 || s[0] == 0 || strncmp(s, "_ivl_", 5) == 0; +} + +/* Collect the real (non-synthetic) source signal names in the backward + combinational cone of a net, following the integer graph through + synthetic intermediate nets. Stops at real names and at process / + sequential drivers. `scope` resolves local signal names. */ +static void cone_reads(int net_id, ivl_scope_t scope, + struct nameset*acc, char*visited) +{ + struct net_ent*ne; + size_t di, ri; + if (net_id < 1 || (size_t)net_id > nets.count || visited[net_id]) + return; + visited[net_id] = 1; + ne = &nets.items[net_id - 1]; + for (di = 0 ; di < ne->ndrivers ; di += 1) { + struct cell_ent*c = &cells.items[ne->drivers[di] - 1]; + if (strcmp(c->kind, "process") == 0) + continue; /* sequential boundary -- a process output */ + for (ri = 0 ; ri < c->nr ; ri += 1) { + int rid = c->reads[ri]; + const char*nm = nexus_signame(nets.items[rid-1].nex, scope); + if (!synth_name(nm)) + nameset_add(acc, nm); + else + cone_reads(rid, scope, acc, visited); + } + } +} + +/* True if the net is driven by combinational logic (gate/lpm/const) and + not by a process (a process output belongs in processes[], not here). */ +static int net_comb_driven(int net_id) +{ + struct net_ent*ne; + size_t di; + int comb = 0; + if (net_id < 1 || (size_t)net_id > nets.count) + return 0; + ne = &nets.items[net_id - 1]; + for (di = 0 ; di < ne->ndrivers ; di += 1) { + const char*k = cells.items[ne->drivers[di] - 1].kind; + if (strcmp(k, "process") == 0) return 0; + comb = 1; + } + return comb; +} + +/* Emit name-based continuous assigns for the real signals of one scope + that are driven combinationally, with transitive cone reads; recurse + through transparent generate/begin frames. */ +static void emit_assigns_rec(ivl_scope_t scope, int*first) +{ + unsigned ns = ivl_scope_sigs(scope), i; + size_t nc, ci; + char*visited = (char*)calloc(nets.count + 1, 1); + + for (i = 0 ; i < ns ; i += 1) { + ivl_signal_t sig = ivl_scope_sig(scope, i); + const char*name = ivl_signal_basename(sig); + int nid; + struct nameset reads = {0,0,0}; + if (synth_name(name) || ivl_signal_local(sig)) + continue; + if (ivl_signal_port(sig) == IVL_SIP_INPUT) + continue; /* inputs are driven from outside */ + nid = net_of_sig(sig, 0); + if (!net_comb_driven(nid)) + continue; + memset(visited, 0, nets.count + 1); + cone_reads(nid, scope, &reads, visited); + + if (!*first) fputs(", ", out); + *first = 0; + fputs("{\"target\": ", out); put_jstr(name); + fputs(", \"reads\": ", out); put_name_array(&reads); + fputs(", \"pos\": ", out); put_pos(ivl_signal_lineno(sig)); + putc('}', out); + free(reads.items); + } + free(visited); + + nc = ivl_scope_childs(scope); + for (ci = 0 ; ci < nc ; ci += 1) { + ivl_scope_t ch = ivl_scope_child(scope, ci); + if (ivl_scope_type(ch) != IVL_SCT_MODULE) + emit_assigns_rec(ch, first); /* transparent generate/begin */ + } +} + +static void emit_assignments(ivl_scope_t scope) +{ + int first = 1; + emit_assigns_rec(scope, &first); +} + +static void emit_ports(ivl_scope_t scope) +{ + unsigned nsig = ivl_scope_sigs(scope), i; + int first = 1; + for (i = 0 ; i < nsig ; i += 1) { + ivl_signal_t sig = ivl_scope_sig(scope, i); + if (ivl_signal_port(sig) == IVL_SIP_NONE) continue; + if (!first) fputs(", ", out); + first = 0; + fputs("{\"name\": ", out); put_jstr(ivl_signal_basename(sig)); + fputs(", \"dir\": ", out); put_jstr(dir_str(ivl_signal_port(sig))); + fputs(", \"type\": ", out); put_sig_type(sig); + fputs(", \"pos\": ", out); put_pos(ivl_signal_lineno(sig)); + putc('}', out); + } +} + +static void emit_generics(ivl_scope_t scope) +{ + unsigned np = ivl_scope_params(scope), i; + int first = 1; + for (i = 0 ; i < np ; i += 1) { + ivl_parameter_t par = ivl_scope_param(scope, i); + if (!first) fputs(", ", out); + first = 0; + fputs("{\"name\": ", out); put_jstr(ivl_parameter_basename(par)); + fputs(", \"type\": ", out); put_jstr("parameter"); + fputs(", \"pos\": ", out); put_pos(ivl_parameter_lineno(par)); + putc('}', out); + } +} + +static void emit_signals(ivl_scope_t scope) +{ + unsigned nsig = ivl_scope_sigs(scope), i; + int first = 1; + for (i = 0 ; i < nsig ; i += 1) { + ivl_signal_t sig = ivl_scope_sig(scope, i); + if (ivl_signal_port(sig) != IVL_SIP_NONE) continue; + if (ivl_signal_local(sig)) continue; + if (!first) fputs(", ", out); + first = 0; + fputs("{\"name\": ", out); put_jstr(ivl_signal_basename(sig)); + fputs(", \"type\": ", out); put_sig_type(sig); + fputs(", \"skind\": ", out); put_jstr("signal"); + fputs(", \"pos\": ", out); put_pos(ivl_signal_lineno(sig)); + putc('}', out); + } +} + +static void emit_instance_obj(ivl_scope_t child, int first) +{ + if (!first) fputs(", ", out); + fputs("\n {\"label\": ", out); put_jstr(ivl_scope_basename(child)); + fputs(", \"module\": ", out); put_jstr(ivl_scope_tname(child)); + fputs(", \"is_module_inst\": true, \"pos\": ", out); + put_pos(ivl_scope_lineno(child)); + fputs(", \"generic_map\": [", out); emit_generic_map(child); + fputs("], \"port_map\": [", out); emit_port_map(child); + fputs("]}", out); +} + +static void emit_module_instances(ivl_scope_t scope, int*first) +{ + size_t n = ivl_scope_childs(scope), i; + for (i = 0 ; i < n ; i += 1) { + ivl_scope_t ch = ivl_scope_child(scope, i); + if (ivl_scope_type(ch) == IVL_SCT_MODULE) { + emit_instance_obj(ch, *first); + *first = 0; + } else { + emit_module_instances(ch, first); + } + } +} + +static void emit_module(ivl_scope_t scope, int first) +{ + int ifirst = 1; + if (!first) fputs(",", out); + fputs("\n {\"name\": ", out); put_jstr(ivl_scope_tname(scope)); + fputs(", \"kind\": ", out); put_jstr("module"); + fputs(", \"lang\": ", out); put_jstr("verilog"); + fputs(", \"file\": ", out); put_jstr(ivl_scope_file(scope)); + fputs(", \"pos\": ", out); put_pos(ivl_scope_lineno(scope)); + fputs(", \"arch\": ", out); put_jstr(""); + fputs(",\n \"ports\": [", out); emit_ports(scope); + fputs("],\n \"generics\": [", out); emit_generics(scope); + fputs("],\n \"signals\": [", out); emit_signals(scope); + fputs("],\n \"constants\": [],\n \"processes\": [", out); + emit_processes(scope); + fputs("],\n \"assignments\": [", out); + emit_assignments(scope); + fputs("], \"generates\": [],", out); + fputs("\n \"instances\": [", out); + emit_module_instances(scope, &ifirst); + fputs("]}", out); +} + +/* ================================================================== */ +/* hierarchy[] (Phase 1) */ +/* ================================================================== */ + +static void emit_hier_node(ivl_scope_t scope, unsigned indent, int first); +static void emit_frame_node(ivl_scope_t scope, unsigned indent, int first); + +/* Dispatch the children of a hierarchy node: module children become + instance nodes, generate/begin children become transparent frames. */ +static void emit_hier_children(ivl_scope_t scope, unsigned indent, int*first) +{ + size_t n = ivl_scope_childs(scope), i; + for (i = 0 ; i < n ; i += 1) { + ivl_scope_t ch = ivl_scope_child(scope, i); + ivl_scope_type_t t = ivl_scope_type(ch); + if (t == IVL_SCT_MODULE) { + emit_hier_node(ch, indent + 2, *first); + *first = 0; + } else if (t == IVL_SCT_GENERATE || t == IVL_SCT_BEGIN + || t == IVL_SCT_FORK) { + emit_frame_node(ch, indent + 2, *first); + *first = 0; + } + } +} + +/* A transparent generate/begin frame: scope:true + generate{kind,label, + [index]}. iverilog names for-generate scopes "