From 52329d0d13c59c15535ad582387cb6bd4ccba810 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Wed, 12 Aug 2026 22:14:57 +0200 Subject: [PATCH 01/24] ci: Elaborate the synthesis tops and inst64 frontend in public CI Public CI was lint and codegen only, so a port, parameter or connectivity break passed every gate; the proprietary EDA sims that would catch it run on the GitLab mirror and are skipped for fork PRs. Add a verilator elaboration job over all synthesis tops plus a concrete-typed inst64 wrapper, since idma_inst64_top has all-logic parameters and no public concrete instance. --- .github/workflows/ci.yml | 5 ++ .github/workflows/elaborate.yml | 47 +++++++++++ Bender.yml | 6 ++ idma.mk | 30 +++++++ test/idma_inst64_lint.sv | 140 ++++++++++++++++++++++++++++++++ 5 files changed, 228 insertions(+) create mode 100644 .github/workflows/elaborate.yml create mode 100644 test/idma_inst64_lint.sv diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c89cd77..06f17b02 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,11 @@ jobs: uses: ./.github/workflows/analyze.yml secrets: inherit + elaborate: + needs: lint + uses: ./.github/workflows/elaborate.yml + secrets: inherit + gitlab-ci: needs: build uses: ./.github/workflows/gitlab-ci.yml diff --git a/.github/workflows/elaborate.yml b/.github/workflows/elaborate.yml new file mode 100644 index 00000000..963c1f70 --- /dev/null +++ b/.github/workflows/elaborate.yml @@ -0,0 +1,47 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Author: +# - Daniel Keller + +name: elaborate + +on: + workflow_call: + workflow_dispatch: + +jobs: + + # Elaborate every synthesis top plus the snitch_cluster-gated inst64 frontend + # (which has no public concrete instance otherwise), so fork PRs that skip the + # proprietary EDA sims still catch port, parameter and connectivity errors. + # ubuntu-24.04 ships Verilator 5.x via apt. + elaborate: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Cache Bender database + uses: ./.github/actions/bender-db-cache + - + name: Install uv + uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + - + name: Install Bender + uses: pulp-platform/pulp-actions/bender-install@v2.5.0 + with: + version: 0.32.0 + - + name: Install Verilator + run: sudo apt-get update && sudo apt-get install -y verilator + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Elaborate synthesis tops and inst64 frontend + run: uv run --locked make idma_lint_all diff --git a/Bender.yml b/Bender.yml index f779f9b0..5e41239b 100644 --- a/Bender.yml +++ b/Bender.yml @@ -162,3 +162,9 @@ sources: files: - test/tb_idma_backend_multihead.sv - test/tb_idma_backend_multihead_rw.sv + + # inst64 frontend lint/elaboration wrapper: a public concrete-typed top so CI + # can verilator --lint-only the snitch_cluster-gated idma_inst64_top + - target: all(snitch_cluster, lint) + files: + - test/idma_inst64_lint.sv diff --git a/idma.mk b/idma.mk index 887c76f8..484dd328 100644 --- a/idma.mk +++ b/idma.mk @@ -605,6 +605,36 @@ $(IDMA_VLT_DIR)/%_elab.log: $(IDMA_BENDER_FILES) $(IDMA_FULL_TB) $(IDMA_FULL_RTL idma_verilator_clean: rm -rf $(IDMA_VLT_DIR) +# inst64 frontend elaboration gate: bind the snitch_cluster-gated idma_inst64_top +# to concrete types (test/idma_inst64_lint.sv) and verilator --lint-only it, so +# public CI catches frontend port/param/elaboration errors. Run after idma_hw_all. +.PHONY: idma_lint_inst64 +idma_lint_inst64: + mkdir -p $(IDMA_VLT_DIR) + $(BENDER) script verilator -t rtl -t snitch_cluster -t lint > $(IDMA_VLT_DIR)/idma_inst64_lint.f + $(VERILATOR) --lint-only -Wno-fatal --timing -f $(IDMA_VLT_DIR)/idma_inst64_lint.f --top-module idma_inst64_lint + +# Synthesis-wrapper elaboration gate: verilator elaborates every synth top so a +# port, parameter or connectivity break is caught without the proprietary EDA +# sims (which fork PRs never run). Run after idma_hw_all. +IDMA_LINT_TOPS ?= $(addprefix idma_backend_synth_,$(IDMA_BACKEND_IDS)) \ + idma_desc64_synth \ + idma_nd_midend_synth \ + idma_mp_midend_synth \ + idma_rt_midend_synth + +.PHONY: idma_lint_elab +idma_lint_elab: + mkdir -p $(IDMA_VLT_DIR) + $(BENDER) script verilator -t rtl -t synth > $(IDMA_VLT_DIR)/idma_elab.f + @rc=0; for top in $(IDMA_LINT_TOPS); do \ + echo "--- elaborating $$top ---"; \ + $(VERILATOR) --lint-only -Wno-fatal --timing -f $(IDMA_VLT_DIR)/idma_elab.f --top-module $$top || rc=1; \ + done; exit $$rc + +.PHONY: idma_lint_all +idma_lint_all: idma_lint_elab idma_lint_inst64 + # --------------- # Trace diff --git a/test/idma_inst64_lint.sv b/test/idma_inst64_lint.sv new file mode 100644 index 00000000..3e20fc4b --- /dev/null +++ b/test/idma_inst64_lint.sv @@ -0,0 +1,140 @@ +// Copyright 2026 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +// Authors: +// - Daniel Keller + +`include "axi/typedef.svh" +`include "obi/typedef.svh" + +/// Lint/elaboration wrapper for the snitch_cluster-gated idma_inst64_top. +/// idma_inst64_top is a fully type-parameterized generic with no concrete +/// instance in the public tree, so no lint tool can elaborate it standalone. +/// This binds concrete AXI/OBI/INIT/acc types (transpose enabled) and ties the +/// ports off so public CI can elaborate the frontend: +/// verilator --lint-only --top-module idma_inst64_lint +/// It carries no behaviour; it exists only to give the generic a concrete top. +module idma_inst64_lint #( + parameter int unsigned AxiDataWidth = 32'd512, + parameter int unsigned AxiAddrWidth = 32'd64, + parameter int unsigned AxiUserWidth = 32'd1, + parameter int unsigned AxiIdWidth = 32'd3, + parameter int unsigned NumChannels = 32'd1 +)( + input logic clk_i, + input logic rst_ni +); + + typedef logic [AxiAddrWidth-1:0] axi_addr_t; + typedef logic [AxiDataWidth-1:0] axi_data_t; + typedef logic [AxiDataWidth/8-1:0] axi_strb_t; + typedef logic [AxiUserWidth-1:0] axi_user_t; + typedef logic [AxiIdWidth-1:0] axi_id_t; + + `AXI_TYPEDEF_AW_CHAN_T(axi_aw_chan_t, axi_addr_t, axi_id_t, axi_user_t) + `AXI_TYPEDEF_W_CHAN_T(axi_w_chan_t, axi_data_t, axi_strb_t, axi_user_t) + `AXI_TYPEDEF_B_CHAN_T(axi_b_chan_t, axi_id_t, axi_user_t) + `AXI_TYPEDEF_AR_CHAN_T(axi_ar_chan_t, axi_addr_t, axi_id_t, axi_user_t) + `AXI_TYPEDEF_R_CHAN_T(axi_r_chan_t, axi_data_t, axi_id_t, axi_user_t) + `AXI_TYPEDEF_REQ_T(axi_req_t, axi_aw_chan_t, axi_w_chan_t, axi_ar_chan_t) + `AXI_TYPEDEF_RESP_T(axi_resp_t, axi_b_chan_t, axi_r_chan_t) + + typedef logic [AxiDataWidth/8-1:0] obi_strb_t; + `OBI_TYPEDEF_MINIMAL_A_OPTIONAL(obi_a_optional_t) + `OBI_TYPEDEF_MINIMAL_R_OPTIONAL(obi_r_optional_t) + `OBI_TYPEDEF_TYPE_A_CHAN_T(obi_a_chan_t, axi_addr_t, axi_data_t, obi_strb_t, axi_id_t, + obi_a_optional_t) + `OBI_TYPEDEF_TYPE_R_CHAN_T(obi_r_chan_t, axi_data_t, axi_id_t, obi_r_optional_t) + `OBI_TYPEDEF_REQ_T(obi_req_t, obi_a_chan_t) + `OBI_TYPEDEF_RSP_T(obi_res_t, obi_r_chan_t) + + // INIT meta-channel types (mirror src/db/idma_init.yml) + typedef struct packed { + logic [AxiAddrWidth-1:0] cfg; + logic [AxiDataWidth-1:0] term; + logic [AxiDataWidth/8-1:0] strb; + logic [AxiIdWidth-1:0] id; + } init_req_chan_t; + typedef struct packed { init_req_chan_t req_chan; logic req_valid; logic rsp_ready; } init_req_t; + typedef struct packed { logic [AxiDataWidth-1:0] init; } init_rsp_chan_t; + typedef struct packed { init_rsp_chan_t rsp_chan; logic rsp_valid; logic req_ready; } init_rsp_t; + + typedef axi_pkg::xbar_rule_64_t addr_rule_t; + + // Snitch accelerator bus (the inst64 frontend decodes data_op/argb) + typedef struct packed { + logic [31:0] id; + logic [31:0] data_op; + logic [63:0] data_arga; + logic [63:0] data_argb; + } acc_req_t; + typedef struct packed { logic [31:0] id; logic [63:0] data; logic error; } acc_res_t; + + typedef struct packed { + logic aw_valid, aw_ready, aw_done, aw_stall; + axi_pkg::len_t aw_len; + axi_pkg::size_t aw_size; + logic ar_valid, ar_ready, ar_done, ar_stall; + axi_pkg::len_t ar_len; + axi_pkg::size_t ar_size; + logic r_valid, r_ready, r_done, r_bw, r_stall; + logic w_valid, w_ready, w_done, w_stall; + logic [31:0] num_bytes_written; + logic b_valid, b_ready, b_done; + logic obi_wr_req, obi_rd_req; + logic dma_busy; + } dma_events_t; + + axi_req_t [NumChannels-1:0] axi_req; + obi_req_t [NumChannels-1:0] obi_req; + dma_events_t [NumChannels-1:0] events; + logic [NumChannels-1:0] busy; + acc_res_t acc_res; + logic acc_req_ready, acc_res_valid; + + idma_inst64_top #( + .AxiDataWidth ( AxiDataWidth ), + .AxiAddrWidth ( AxiAddrWidth ), + .AxiUserWidth ( AxiUserWidth ), + .AxiIdWidth ( AxiIdWidth ), + .NumAxInFlight ( 32'd3 ), + .DMAReqFifoDepth ( 32'd3 ), + .NumChannels ( NumChannels ), + .DMATracing ( 1'b0 ), + .axi_ar_chan_t ( axi_ar_chan_t ), + .axi_aw_chan_t ( axi_aw_chan_t ), + .axi_req_t ( axi_req_t ), + .axi_res_t ( axi_resp_t ), + .init_req_chan_t ( init_req_chan_t ), + .init_rsp_chan_t ( init_rsp_chan_t ), + .init_req_t ( init_req_t ), + .init_rsp_t ( init_rsp_t ), + .obi_a_chan_t ( obi_a_chan_t ), + .obi_r_chan_t ( obi_r_chan_t ), + .obi_req_t ( obi_req_t ), + .obi_res_t ( obi_res_t ), + .acc_req_t ( acc_req_t ), + .acc_res_t ( acc_res_t ), + .dma_events_t ( dma_events_t ), + .addr_rule_t ( addr_rule_t ) + ) i_dut ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .axi_req_o ( axi_req ), + .axi_res_i ( '0 ), + .obi_req_o ( obi_req ), + .obi_res_i ( '0 ), + .busy_o ( busy ), + .acc_req_i ( '0 ), + .acc_req_valid_i ( 1'b0 ), + .acc_req_ready_o ( acc_req_ready ), + .acc_res_o ( acc_res ), + .acc_res_valid_o ( acc_res_valid ), + .acc_res_ready_i ( 1'b0 ), + .hart_id_i ( 32'h0 ), + .events_o ( events ), + .addr_map_i ( '0 ) + ); + +endmodule From 2fb44c012c32f57a11918bed1678b3622cfe329e Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 08:52:57 +0200 Subject: [PATCH 02/24] tracer: Qualify trace signal keys by direction A protocol present on both the read and the write side emitted the same key twice, so the write leg silently dropped the read channel. --- Bender.yml | 1 + target/.gitignore | 2 ++ target/rtl/.gitignore | 1 + test/idma_test.sv | 2 +- util/mario/tracer.py | 7 ++++--- util/trace_idma.py | 8 +++++--- 6 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Bender.yml b/Bender.yml index 5e41239b..55a91552 100644 --- a/Bender.yml +++ b/Bender.yml @@ -136,6 +136,7 @@ sources: - target: idma_test files: - target/rtl/tb_idma_generated.sv + - test/tb_idma_otf_transpose.sv - test/tb_idma_transpose_nd.sv - test/tb_idma_transpose_b2b.sv - test/tb_idma_mxquant.sv diff --git a/target/.gitignore b/target/.gitignore index ac92a5f6..0f21957a 100644 --- a/target/.gitignore +++ b/target/.gitignore @@ -1,3 +1,5 @@ doc pickle sim/verilator +sim/slang +verify diff --git a/target/rtl/.gitignore b/target/rtl/.gitignore index 9f111a44..bfb73aff 100644 --- a/target/rtl/.gitignore +++ b/target/rtl/.gitignore @@ -4,3 +4,4 @@ include/* *.sv *.hjson .vidma_ids +.idma_ids diff --git a/test/idma_test.sv b/test/idma_test.sv index 931a580d..899b8433 100644 --- a/test/idma_test.sv +++ b/test/idma_test.sv @@ -85,7 +85,7 @@ package idma_test; string res = "0x"; int now = 1; foreach (num[i]) begin - res = {res, num[i]}; + res = {res, string'(num[i])}; if (now % 4 == 0 & now != 0 & now != num.len()) res = {res, "_"}; now++; diff --git a/util/mario/tracer.py b/util/mario/tracer.py index cb161dbe..e558b3f9 100644 --- a/util/mario/tracer.py +++ b/util/mario/tracer.py @@ -120,19 +120,20 @@ def render_tracer(prot_ids: dict, db: dict, tpl_file: str) -> str: # signals signals = '' - # handle read ports + # Keys are direction-qualified: a protocol present on both sides (INIT) otherwise + # emits the same key twice and the write leg silently drops the read channel. for read_prot in prot_ids[prot_id]['ar']: sig_dict = _flatten_dict(db[read_prot]['trace_signals']['read']) for signal in sig_dict: signals += ' ' - signals += f'"{read_prot}_{signal}": __backend_inst``.{sig_dict[signal]}' + signals += f'"{read_prot}_read_{signal}": __backend_inst``.{sig_dict[signal]}' signals += ', \\\n' for write_prot in prot_ids[prot_id]['aw']: sig_dict = _flatten_dict(db[write_prot]['trace_signals']['write']) for signal in sig_dict: signals += ' ' - signals += f'"{write_prot}_{signal}": __backend_inst``.{sig_dict[signal]}' + signals += f'"{write_prot}_write_{signal}": __backend_inst``.{sig_dict[signal]}' signals += ', \\\n' # post-processing diff --git a/util/trace_idma.py b/util/trace_idma.py index 50ac8c08..5fc65a85 100644 --- a/util/trace_idma.py +++ b/util/trace_idma.py @@ -60,13 +60,15 @@ def get_global_utilization(trace: list, params: dict, be_info: dict) -> list: for ele in trace: # add read contribution for read_prot in be_info['read_prots']: - if ele['bus'][f'{read_prot}_rsp_ready'] and ele['bus'][f'{read_prot}_rsp_valid']: + if (ele['bus'][f'{read_prot}_read_rsp_ready'] + and ele['bus'][f'{read_prot}_read_rsp_valid']): read_data += params['data_width'] // 8 # add write contribution for write_prot in be_info['write_prots']: - if ele['bus'][f'{write_prot}_req_ready'] and ele['bus'][f'{write_prot}_req_valid']: - write_data += strb_to_bytes(ele['bus'][f'{write_prot}_req_strobe']) + if (ele['bus'][f'{write_prot}_write_req_ready'] + and ele['bus'][f'{write_prot}_write_req_valid']): + write_data += strb_to_bytes(ele['bus'][f'{write_prot}_write_req_strobe']) # calculate maximum possible amount of data max_data = len(trace) * params['data_width'] // 8 From 1f05358d9631c01ecfef75359fdefc14ac0c6757 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 08:52:58 +0200 Subject: [PATCH 03/24] ci: Verify the backend variants publicly with verilator and slang Public CI could not see a port, parameter or datapath defect: the variant matrix runs on the private mirror with proprietary tools and is skipped for fork PRs, so collaborators had no visibility and every check spent a licence. Elaborate all eight backend variants under both tools across their real parameter sets, simulate the DPI-backed datapath tests byte-exact, run the negative guards with assertions enabled, and check the codegen and job tables stay consistent. --- .github/actions/verify-setup/action.yml | 34 +++ .github/workflows/ci.yml | 10 +- .github/workflows/verify.yml | 268 ++++++++++++++++++ idma.mk | 350 +++++++++++++++++++++++- util/check_jobs.py | 135 +++++++++ util/idma_params.py | 131 +++++++++ 6 files changed, 921 insertions(+), 7 deletions(-) create mode 100644 .github/actions/verify-setup/action.yml create mode 100644 .github/workflows/verify.yml create mode 100644 util/check_jobs.py create mode 100644 util/idma_params.py diff --git a/.github/actions/verify-setup/action.yml b/.github/actions/verify-setup/action.yml new file mode 100644 index 00000000..3007cc60 --- /dev/null +++ b/.github/actions/verify-setup/action.yml @@ -0,0 +1,34 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Authors: +# - Daniel Keller + +name: Set up the public verification toolchain +description: Bender database cache, uv, Bender and optionally Verilator +inputs: + verilator: + description: Install Verilator from apt; ubuntu-24.04 ships 5.020 + default: 'false' +runs: + using: composite + steps: + - uses: ./.github/actions/bender-db-cache + - uses: astral-sh/setup-uv@v7 + with: + enable-cache: true + - uses: pulp-platform/pulp-actions/bender-install@v2.5.0 + with: + version: 0.32.0 + - if: inputs.verilator == 'true' + shell: bash + run: sudo apt-get update && sudo apt-get install -y verilator + # Assert the major version: a silently older Verilator would still exit 0 on + # the elaboration legs while missing everything the 5.x front end catches. + - if: inputs.verilator == 'true' + shell: bash + run: | + verilator --version + verilator --version | grep -qE '^Verilator 5\.' || { + echo "expected Verilator 5.x from apt"; exit 1; } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06f17b02..91f295e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,8 +42,16 @@ jobs: uses: ./.github/workflows/elaborate.yml secrets: inherit + verify: + needs: lint + uses: ./.github/workflows/verify.yml + secrets: inherit + + # The licensed EDA pipeline runs only once the license-free matrix is green, + # so it confirms rather than being the only signal, and no licences are spent + # on a PR that already fails structurally. gitlab-ci: - needs: build + needs: [build, verify] uses: ./.github/workflows/gitlab-ci.yml secrets: inherit diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 00000000..c051aeb1 --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,268 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Author: +# - Daniel Keller + +# License-free verification matrix. It runs on Verilator 5.020 (apt, ubuntu-24.04) +# and slang 11 (pyslang) only, so fork PRs - which never reach the proprietary EDA +# sims on the GitLab mirror - get a real gate, and the licensed run becomes a +# confirmation rather than the only signal. +# +# House rules for this file, enforced in review: +# - no `|| true`, no `continue-on-error` on any step of a gating job; +# - no negated grep as a pass criterion; every simulation leg checks exit +# status AND a non-empty log AND a positive token (util/run_vlt_sim.py); +# - no numeric warning-count thresholds; +# - every excluded top or configuration is named below with its reason. +# +# NAMED EXCLUSIONS - dropped coverage, not silent skips: +# tb_idma_nd_midend_b2b hangs ([B2B] timeout); latent reset/LFSR race +# tb_idma_transpose_nd AW outside the destination allocation at 80 ns +# tb_idma_transpose_b2b stream_watchdog trip after 4000 idle cycles +# tb_idma_mxrand testbench timeout at 400 ms of simulated time +# tb_idma_mxperf hangs on the first compute transfer +# tb_idma_rt_midend fails under --assert on cc_rr_arb_tree lock +# mxquant/mxroundtrip at DataWidth 512 and 1024, and mxneg cases 4 and 12 +# Verilator 5.020 SIGSEGVs before time 0 (512 in the +# initial-block coroutine, 1024 in the model +# constructor); elaboration-only for now +# mxneg case 11 the request is never accepted under Verilator, so +# ComputeMxdequantLengthFits is never sampled +# the 88 directed job files +# Verilator cannot simulate the idma_test class-based +# driver (virtual interface, input combinational +# region does not converge) +# All of these need triage; none may be flagged away to make a leg green. + +name: verify + +on: + workflow_call: + workflow_dispatch: + +jobs: + + # Codegen hygiene, not verification: jobs.json and the generated tree must + # still describe the same design, and a second generation must be identical. + codegen-consistency: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + - + name: Check jobs.json, the CI matrix and codegen determinism + run: uv run --locked make idma_verify_codegen + + # Elaboration of one backend variant under both front ends, with the real + # per-variant jobs.json parameters plus a DataWidth sweep, and slang + # elaboration of that variant's testbench. + elab-backend: + strategy: + fail-fast: false + matrix: + id: + - rw_axi + - r_obi_w_axi + - r_axi_w_obi + - rw_axi_rw_axis + - rw_obi + - r_obi_rw_init_w_axi + - r_axi_rw_init_rw_obi + - rw_axi_rw_init_rw_obi + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Elaborate ${{ matrix.id }} + run: uv run --locked make idma_verify_backend IDMA_VERIFY_ID=${{ matrix.id }} + + # The four non-backend synthesis tops with their own jobs.json parameters, + # plus PR #184's default-parameter elaboration of all 12 tops and inst64. + elab-shared-tops: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Elaborate the shared synthesis tops + run: uv run --locked make idma_verify_shared + + # Testbench tops that are not per-backend. Verilator cannot parse the + # verification stack at all, so slang is the only public tool for this half + # of the repository. + elab-tb-shared: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Elaborate the shared testbenches + run: uv run --locked make idma_verify_tb_shared + + # Out-of-tree multi-head build; license-free but generated only on request. + elab-multihead: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Elaborate the multi-head variants and testbenches + run: uv run --locked make idma_verify_multihead + + # Real simulation, byte-exact against the DPI-C goldens. The destination is + # pre-poisoned, so a backend that writes nothing fails. + sim-datapath-mxquant: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Simulate tb_idma_mxquant at DataWidth 32, 64 and 256 + run: uv run --locked make idma_verify_sim_mxquant IDMA_VLT_MAKEFLAGS=-j4 + + sim-datapath-mxroundtrip: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Simulate tb_idma_mxroundtrip at DataWidth 32, 64 and 256 + run: uv run --locked make idma_verify_sim_mxroundtrip IDMA_VLT_MAKEFLAGS=-j4 + + sim-datapath-transpose: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Simulate the transpose datapath, plain and backpressured + run: uv run --locked make idma_verify_sim_transpose IDMA_VLT_MAKEFLAGS=-j4 + + # Negative tests, inverted polarity: the run must exit non-zero AND name its + # guard. --assert is load-bearing; without it both testbenches exit 0 and the + # leg could not fail. + sim-negative: + strategy: + fail-fast: false + matrix: + include: + - name: mxclear-and-cases-1-3 + clear: 'yes' + cases: 1 2 3 + - name: cases-5-7 + clear: 'no' + cases: 5 6 7 + - name: cases-8-13 + clear: 'no' + cases: 8 10 13 + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + with: + verilator: 'true' + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Check the clear-with-in-flight-state guard + if: matrix.clear == 'yes' + run: uv run --locked make idma_verify_sim_mxclear IDMA_VLT_MAKEFLAGS=-j4 + - + name: Check legalizer compute guards ${{ matrix.name }} + run: | + uv run --locked make idma_verify_sim_mxneg IDMA_VLT_MAKEFLAGS=-j4 \ + IDMA_MXNEG_CASES="${{ matrix.cases }}" + + # A report, not a gate: -Wextra without -Werror, so findings do not fail the + # job and a real compile error still does. Never add it to the required checks. + report-slang-extra: + runs-on: ubuntu-24.04 + steps: + - + name: Checkout + uses: actions/checkout@v4 + - + name: Set up toolchain + uses: ./.github/actions/verify-setup + - + name: Generate RTL + run: uv run --locked make idma_hw_all + - + name: Collect slang -Wextra findings + run: uv run --locked make idma_slang_report + - + name: Upload the report + uses: actions/upload-artifact@v4 + with: + name: slang-extra + path: target/verify/slang_extra.txt diff --git a/idma.mk b/idma.mk index 484dd328..9a6406b7 100644 --- a/idma.mk +++ b/idma.mk @@ -7,6 +7,7 @@ BENDER ?= bender CAT ?= cat +CC ?= cc GIT ?= git PRINTF ?= printf UV ?= uv @@ -133,7 +134,20 @@ $(IDMA_RTL_DIR)/tb_idma_backend_%.sv: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_RTL_DIR $(IDMA_VSIM_DIR)/wave/backend_%.do: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_RTL_DIR)/tb_idma_backend_%.sv $(IDMA_VSIM_DIR)/wave/tpl/backend.do.tpl $(call idma_gen,vsim_wave,$(IDMA_VSIM_DIR)/wave/tpl/backend.do.tpl,$(IDMA_DB_FILES),$*,,$@) -$(IDMA_RTL_DIR)/include/idma/tracer.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_ROOT)/src/include/idma/tpl/tracer.svh.tpl $(IDMA_DB_FILES) $(IDMA_ROOT)/idma.mk $(IDMA_DB_FILES) +# Stamp holding the id lists the tree was last generated with. The tracer covers +# every id in one file, so a change to IDMA_ADD_IDS must rebuild it; without the +# stamp an out-of-tree id list silently keeps the previous tracer. +IDMA_IDS_STAMP := $(IDMA_RTL_DIR)/.idma_ids + +.PHONY: IDMA_FORCE +IDMA_FORCE: + +$(IDMA_IDS_STAMP): IDMA_FORCE + @mkdir -p $(IDMA_RTL_DIR) + @echo "$(IDMA_BACKEND_IDS)|$(IDMA_FE_IDS)" | cmp -s - $@ || \ + echo "$(IDMA_BACKEND_IDS)|$(IDMA_FE_IDS)" > $@ + +$(IDMA_RTL_DIR)/include/idma/tracer.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_ROOT)/src/include/idma/tpl/tracer.svh.tpl $(IDMA_DB_FILES) $(IDMA_ROOT)/idma.mk $(IDMA_IDS_STAMP) mkdir -p $(IDMA_RTL_DIR)/include/idma $(call idma_gen,tracer,$(IDMA_ROOT)/src/include/idma/tpl/tracer.svh.tpl,$(IDMA_DB_FILES),$(IDMA_BACKEND_IDS),$(IDMA_FE_IDS),$@) @@ -143,6 +157,7 @@ $(IDMA_RTL_DIR)/include/idma/compute.svh: $(IDMA_ROOT)/src/frontend/reg/tpl/comp --template $(IDMA_ROOT)/src/frontend/reg/tpl/compute.svh.tpl -o $@ idma_rtl_clean: + rm -f $(IDMA_IDS_STAMP) rm -f $(IDMA_RTL_DIR)/Bender.yml rm -f $(IDMA_RTL_DIR)/*.sv rm -f $(IDMA_VSIM_DIR)/wave/*.do @@ -285,11 +300,13 @@ IDMA_SW_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_SW_DIR)/idma_$Y_raw_regs. # RTL assembly # --------------- -$(IDMA_FULL_RTL): $(IDMA_RTL_ALL) - $(CAT) $^ > $@ +# The stamp catches an id-list change: both files concatenate a per-id set, so a +# shorter list leaves the previous variants in place without it. +$(IDMA_FULL_RTL): $(IDMA_RTL_ALL) $(IDMA_IDS_STAMP) + $(CAT) $(IDMA_RTL_ALL) > $@ -$(IDMA_FULL_TB): $(IDMA_TB_ALL) - $(CAT) $^ > $@ +$(IDMA_FULL_TB): $(IDMA_TB_ALL) $(IDMA_IDS_STAMP) + $(CAT) $(IDMA_TB_ALL) > $@ # --------------- @@ -636,6 +653,327 @@ idma_lint_elab: idma_lint_all: idma_lint_elab idma_lint_inst64 +# --------------- +# Public verification +# --------------- + +# License-free elaboration and simulation gates, run on verilator and slang only, +# so fork PRs (which skip the proprietary EDA sims) still get a real signal. +# Every top-level target below is one CI leg and reproduces a red check locally. + +.PHONY: idma_verify_codegen idma_verify_backend idma_verify_shared idma_verify_multihead +.PHONY: idma_verify_tb_shared idma_verify_sim_mxquant idma_verify_sim_mxroundtrip +.PHONY: idma_verify_sim_transpose idma_verify_sim_mxclear idma_verify_sim_mxneg +.PHONY: idma_verify_all idma_lint_params idma_slang_elab idma_slang_tb idma_slang_report +.PHONY: idma_verify_clean + +# Module to elaborate; every per-top target takes it +IDMA_TOP ?= +# Backend variant a leg covers +IDMA_VERIFY_ID ?= rw_axi +# DataWidth points elaborated on top of the jobs.json parameter sets. Every +# jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise. +IDMA_ELAB_WIDTHS ?= 32 64 512 1024 + +IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify +IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang +IDMA_SLANG_VERSION ?= 11.0.0 +# pyslang ships the slang driver itself. Never call a site `slang` wrapper: one +# turns a SIGSEGV into rc=0 with no output, which makes the gate vacuous. +SLANG ?= $(UV) run --with pyslang==$(IDMA_SLANG_VERSION) python \ + $(IDMA_UTIL_DIR)/slang_elab.py +IDMA_SLANG_ARGS := -Werror --error-limit 0 +# -Wno-finish-num covers 9 $fatal("") calls in common_verification; no +# iDMA file has one, and slang cannot scope a waiver to a dependency checkout. +IDMA_SLANG_TB_ARGS := --timescale=1ns/1ps -Wno-finish-num +IDMA_SLANG_SYNTH_T := -t rtl -t synth +IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ + -t snitch_cluster -t asic + +# Warning classes measured at 0 occurrences over all 12 synth tops, so they gate. +# Not promoted, with the measurement: UNOPTFLAT (31, includes the known +# idma_nd_midend stage_done loop) and PINMISSING (12, all in axi_stream and +# common_cells, none iDMA-owned). +IDMA_VLT_WERROR := -Werror-LATCH -Werror-MULTIDRIVEN -Werror-IMPLICIT +IDMA_VLT_LINT_ARGS := --lint-only -Wno-fatal --timing $(IDMA_VLT_WERROR) + +# Non-backend synthesis tops, elaborated with their own jobs.json parameters +IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synth \ + idma_rt_midend_synth + +# Out-of-tree multi-head variants; license-free but generated only on request +IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi + +# Testbench tops that are not per-backend. tb_idma_backend_* are covered by the +# matching idma_verify_backend leg. +IDMA_TB_SHARED_TOPS := tb_idma_desc64_top tb_idma_desc64_bench tb_idma_reg_frontend \ + tb_idma_nd_midend tb_idma_nd_midend_b2b tb_idma_rt_midend \ + tb_idma_transpose_midend tb_idma_otf_transpose \ + tb_idma_transpose_nd tb_idma_transpose_b2b tb_idma_mxquant \ + tb_idma_mxroundtrip tb_idma_mxrand tb_idma_mxneg \ + tb_idma_mxperf tb_idma_mxclear + +IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src/**/*.sv' \ + --source '$(IDMA_ROOT)/test/**/*.sv' + +# CI workflow that has to fan out over every backend id and negative-test case; +# a matrix leg that silently does not exist is worse than no leg +IDMA_MATRIX_FILE ?= $(IDMA_ROOT)/.github/workflows/verify.yml + +# Emit the elaboration configurations of $1 (jobs.json parameter sets + width sweep) +define idma_elab_cfg + mkdir -p $(IDMA_VERIFY_DIR) + $(PYTHON) $(IDMA_UTIL_DIR)/idma_params.py --top $1 \ + --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --widths "$(IDMA_ELAB_WIDTHS)" \ + $(IDMA_SOURCE_GLOBS) > $(IDMA_VERIFY_DIR)/$1.cfg +endef + +# Filelists +$(IDMA_VLT_DIR)/idma_verify.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_VLT_DIR) + $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) > $@ + +$(IDMA_SLANG_DIR)/synth.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_SLANG_DIR) + $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) > $@ + +$(IDMA_SLANG_DIR)/tb.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_SLANG_DIR) + $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) > $@ + + +# Verilator elaboration of IDMA_TOP over every jobs.json configuration and width +# point. verilator rejects an unknown -G name; slang ignores it, which is why +# idma_params.py checks the names against the module header itself. +idma_lint_params: $(IDMA_VLT_DIR)/idma_verify.f + @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } + $(call idma_elab_cfg,$(IDMA_TOP)) + @test -s $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg || \ + { echo "error: no configurations for $(IDMA_TOP)"; exit 1; } + @rc=0; while read -r cfg flags; do \ + echo "--- verilator $(IDMA_TOP) [$$cfg] $$flags ---"; \ + $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_verify.f \ + --top-module $(IDMA_TOP) $$flags || rc=1; \ + done < $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg; \ + test $$rc -eq 0 && echo "idma_lint_params: $(IDMA_TOP) OK" || \ + echo "idma_lint_params: $(IDMA_TOP) FAILED"; exit $$rc + +# slang elaboration of IDMA_TOP over the same configurations +idma_slang_elab: $(IDMA_SLANG_DIR)/synth.f + @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } + $(call idma_elab_cfg,$(IDMA_TOP)) + @test -s $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg || \ + { echo "error: no configurations for $(IDMA_TOP)"; exit 1; } + @rc=0; while read -r cfg flags; do \ + echo "--- slang $(IDMA_TOP) [$$cfg] $$flags ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/synth.f --top $(IDMA_TOP) $(IDMA_SLANG_ARGS) $$flags \ + || rc=1; \ + done < $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg; \ + test $$rc -eq 0 && echo "idma_slang_elab: $(IDMA_TOP) OK" || \ + echo "idma_slang_elab: $(IDMA_TOP) FAILED"; exit $$rc + +# slang elaboration of one testbench top; verilator cannot parse the verification +# stack at all (axi_test/apb_test type-identifier errors, randomize()-with). +idma_slang_tb: $(IDMA_SLANG_DIR)/tb.f + @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } + $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $(IDMA_TOP) \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS) + +# One backend variant: synthesis top under both front ends, plus its testbench +idma_verify_backend: + $(MAKE) idma_lint_params IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) + $(MAKE) idma_slang_elab IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) + $(MAKE) idma_slang_tb IDMA_TOP=tb_idma_backend_$(IDMA_VERIFY_ID) + +# The four non-backend synthesis tops with their jobs.json parameters, plus the +# default-parameter elaboration of every top from idma_lint_all +idma_verify_shared: idma_lint_all + set -e; for top in $(IDMA_SHARED_TOPS); do \ + $(MAKE) idma_lint_params IDMA_TOP=$$top; \ + $(MAKE) idma_slang_elab IDMA_TOP=$$top; \ + done + +idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f + set -e; for top in $(IDMA_TB_SHARED_TOPS); do \ + echo "--- slang $$top ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $$top \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ + done + +# Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the +# two extra synthesis tops and the two multi-head testbenches +idma_verify_multihead: + $(MAKE) idma_hw_all IDMA_ADD_IDS="$(IDMA_MULTIHEAD_IDS)" + mkdir -p $(IDMA_VLT_DIR) $(IDMA_SLANG_DIR) + $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) > $(IDMA_VLT_DIR)/idma_multihead.f + $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) > $(IDMA_SLANG_DIR)/mh_synth.f + $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead > $(IDMA_SLANG_DIR)/mh_tb.f + set -e; for id in $(IDMA_MULTIHEAD_IDS); do \ + echo "--- verilator idma_backend_synth_$$id ---"; \ + $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_multihead.f \ + --top-module idma_backend_synth_$$id; \ + echo "--- slang idma_backend_synth_$$id ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/mh_synth.f --top idma_backend_synth_$$id \ + $(IDMA_SLANG_ARGS); \ + done + set -e; for tb in tb_idma_backend_multihead tb_idma_backend_multihead_rw; do \ + echo "--- slang $$tb ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/mh_tb.f --top $$tb \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ + done + # leave the tree on the default id list so a following target is not surprised + $(MAKE) idma_hw_all + + +# --------------- +# Public simulation (verilator) +# --------------- + +IDMA_VLT_SIM_T := -t rtl -t idma_test -t simulation -t synth +IDMA_VLT_MAKEFLAGS ?= +IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" IDMA_VLT_MAKEFLAGS="$(IDMA_VLT_MAKEFLAGS)" \ + $(PYTHON) $(IDMA_UTIL_DIR)/run_vlt_sim.py --dir $(IDMA_VLT_DIR) + +# DataWidth 512 and 1024 are elaboration-only: verilator 5.020 SIGSEGVs before +# time 0 (512 in the initial-block coroutine, 1024 in the model constructor). +IDMA_MX_WIDTHS ?= 32 64 256 + +# case:guard:DataWidth:EnDequant:EnFp16 +# Not gated, named rather than skipped: cases 4 and 12 need DataWidth=1024, where +# verilator 5.020 SIGSEGVs in the model constructor; case 11's request is never +# accepted under verilator, so ComputeMxdequantLengthFits is never sampled. +IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ + 2:ComputeSrcAligned:64:1:1 \ + 3:ComputeDstAligned:64:1:1 \ + 5:ComputeMxdequantBeatAligned:64:1:1 \ + 6:ComputeOpUnsupported:64:0:1 \ + 7:ComputeMxSrcProtocol:64:1:1 \ + 8:ComputeMxDstProtocol:64:1:1 \ + 10:ComputeTransposeSingleBeat:64:1:1 \ + 13:ComputeOpUnsupported:64:1:0 +IDMA_MXNEG_CASES ?= 1 2 3 5 6 7 8 10 13 + +$(IDMA_VLT_DIR)/%.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_VLT_DIR) + $(BENDER) script verilator $(IDMA_VLT_SIM_T) --top $* > $@ + +# verilator hands a .c file to the C++ driver, so the DPI goldens are compiled +# here and linked in. idma_mxquant_dpi and idma_transpose_dpi both export +# gm_load/gm_get and must never end up in the same binary. +$(IDMA_VLT_DIR)/%_dpi.o: $(IDMA_ROOT)/test/%_dpi.c + mkdir -p $(IDMA_VLT_DIR) + $(CC) -c -O2 -fPIC $< -o $@ + +idma_verify_sim_mxquant: $(IDMA_VLT_DIR)/tb_idma_mxquant.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; for dw in $(IDMA_MX_WIDTHS); do \ + $(IDMA_VLT_SIM) --top tb_idma_mxquant --flist $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ + --tag mxquant_$$dw --param DataWidth=$$dw \ + --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXQ] ALL PASS"; \ + done + +idma_verify_sim_mxroundtrip: $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ + $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; for dw in $(IDMA_MX_WIDTHS); do \ + $(IDMA_VLT_SIM) --top tb_idma_mxroundtrip \ + --flist $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ + --tag mxroundtrip_$$dw --param DataWidth=$$dw \ + --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXRT] ALL PASS"; \ + done + +# +BP is a second stimulus (backpressured transpose), not a rerun +idma_verify_sim_transpose: $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ + $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ + $(IDMA_VLT_DIR)/idma_transpose_dpi.o + $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ + --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose \ + --param StrbWidth=8 --param FullDuplex=1 \ + --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" + $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ + --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose_bp \ + --param StrbWidth=8 --param FullDuplex=1 --plusarg +BP \ + --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" + set -e; for dw in 64 512; do \ + $(IDMA_VLT_SIM) --top tb_idma_transpose_midend \ + --flist $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f --tag transpose_midend_$$dw \ + --param DataWidth=$$dw --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o \ + --token "[MID] ALL PASS"; \ + done + +# Negative tests: the run must exit non-zero AND name its guard. --assert is +# load-bearing; without it both testbenches exit 0 and the leg cannot fail. +idma_verify_sim_mxclear: $(IDMA_VLT_DIR)/tb_idma_mxclear.f + set -e; for q in 1 0; do \ + $(IDMA_VLT_SIM) --top tb_idma_mxclear --flist $(IDMA_VLT_DIR)/tb_idma_mxclear.f \ + --tag mxclear_$$q --param Quant=$$q --define INC_ASSERT --expect fail \ + --token "clear with in-flight state"; \ + done + +idma_verify_sim_mxneg: $(IDMA_VLT_DIR)/tb_idma_mxneg.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; ran=0; \ + for entry in $(IDMA_MXNEG_TABLE); do \ + IFS=: read -r c guard dw deq fp <<< "$$entry"; \ + case " $(IDMA_MXNEG_CASES) " in *" $$c "*) ;; *) continue;; esac; \ + $(IDMA_VLT_SIM) --top tb_idma_mxneg --flist $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ + --tag mxneg_$$c --param NegCase=$$c --param DataWidth=$$dw \ + --param EnDequant=$$deq --param EnFp16=$$fp \ + --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --define INC_ASSERT --expect fail \ + --token "$$guard"; \ + ran=$$((ran+1)); \ + done; \ + want=$$(echo $(IDMA_MXNEG_CASES) | wc -w); \ + test $$ran -eq $$want || { echo "error: ran $$ran of $$want requested cases"; exit 1; } + + +# --------------- +# Codegen consistency and advisory report +# --------------- + +IDMA_GEN_FILES := $(IDMA_RTL_ALL) $(IDMA_TB_ALL) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) \ + $(IDMA_INCLUDE_ALL) $(IDMA_WAVE_ALL) + +# Not verification: jobs.json and the generated tree must still describe the same +# design, and a second generation must be byte-identical. The zero-git-diff form +# is deliberately not used; target/rtl is gitignored on source branches, so it +# would pass unconditionally. +idma_verify_codegen: + $(MAKE) idma_hw_all + $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ + --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --jobs-dir $(IDMA_ROOT)/jobs \ + --matrix-file $(IDMA_MATRIX_FILE) --mxneg-cases "$(IDMA_MXNEG_CASES)" \ + $(IDMA_SOURCE_GLOBS) + mkdir -p $(IDMA_VERIFY_DIR) + md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 + $(MAKE) idma_rtl_clean idma_reg_clean + $(MAKE) idma_hw_all + md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen2.md5 + diff -u $(IDMA_VERIFY_DIR)/gen1.md5 $(IDMA_VERIFY_DIR)/gen2.md5 + +# Advisory only: -Wextra without -Werror, so findings do not fail the target and +# a real compile error still does. Never add this to the required checks. +idma_slang_report: $(IDMA_SLANG_DIR)/synth.f + mkdir -p $(IDMA_VERIFY_DIR) + : > $(IDMA_VERIFY_DIR)/slang_extra.log + set -e; for top in $(IDMA_LINT_TOPS); do \ + $(SLANG) -f $(IDMA_SLANG_DIR)/synth.f --top $$top -Wextra --error-limit 0 \ + >> $(IDMA_VERIFY_DIR)/slang_extra.log 2>&1; \ + done + awk '/ (warning|error): /' $(IDMA_VERIFY_DIR)/slang_extra.log | \ + awk '!/^\.bender\//' | sort -u > $(IDMA_VERIFY_DIR)/slang_extra.txt + @echo "slang -Wextra: $$(wc -l < $(IDMA_VERIFY_DIR)/slang_extra.txt) unique iDMA findings" + +idma_verify_all: idma_verify_codegen idma_verify_shared idma_verify_tb_shared \ + idma_verify_multihead idma_verify_sim_mxquant \ + idma_verify_sim_mxroundtrip idma_verify_sim_transpose \ + idma_verify_sim_mxclear idma_verify_sim_mxneg + set -e; for id in $(IDMA_BACKEND_IDS); do \ + $(MAKE) idma_verify_backend IDMA_VERIFY_ID=$$id; \ + done + +idma_verify_clean: + rm -rf $(IDMA_VERIFY_DIR) + rm -rf $(IDMA_SLANG_DIR) + + # --------------- # Trace # --------------- @@ -697,7 +1035,7 @@ idma_nonfree_clean: .PHONY: idma_clean_all idma_clean idma_misc_clean idma_sw_clean -idma_clean_all idma_clean: idma_rtl_clean idma_reg_clean idma_pickle_clean idma_sim_clean idma_vcs_clean idma_verilator_clean idma_doc_clean idma_trace_clean idma_sw_clean +idma_clean_all idma_clean: idma_rtl_clean idma_reg_clean idma_pickle_clean idma_sim_clean idma_vcs_clean idma_verilator_clean idma_verify_clean idma_doc_clean idma_trace_clean idma_sw_clean idma_misc_clean: rm -rf scripts/__pycache__ diff --git a/util/check_jobs.py b/util/check_jobs.py new file mode 100644 index 00000000..661609b5 --- /dev/null +++ b/util/check_jobs.py @@ -0,0 +1,135 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +"""Codegen hygiene checks over jobs.json and the generated RTL. + +This is not verification. It checks that the run list and the generated output +still describe the same design: + + a) every backend id has a jobs.json entry, + b) every job path referenced by jobs.json exists, + c) every testbench and synth_top named by jobs.json exists in the sources. + +The run list is always taken from jobs.json, never from `ls jobs/*`: four +error_*.txt files exist on disk for variants built with ErrorHandling=0 and +must not be run. They are listed in UNWIRED_JOBS so the reverse check (a file +on disk that nothing references) stays meaningful. +""" + +import argparse +import glob +import json +import os +import re +import sys + +# Directed job files that exist on disk and are deliberately not wired into +# jobs.json: the r_axi_w_obi and r_obi_w_axi backends elaborate with +# ErrorHandling=0, so the error tests do not apply to them. +UNWIRED_JOBS = { + 'backend_r_axi_w_obi/error_simple.txt', + 'backend_r_axi_w_obi/error_mixed.txt', + 'backend_r_obi_w_axi/error_simple.txt', + 'backend_r_obi_w_axi/error_mixed.txt', +} + + +def load_sources(patterns): + text = [] + for pattern in patterns: + for path in sorted(glob.glob(pattern, recursive=True)): + with open(path, 'r', errors='replace') as handle: + text.append(handle.read()) + return '\n'.join(text) + + +def main(): + par = argparse.ArgumentParser(description=__doc__) + par.add_argument('--jobs', default='jobs/jobs.json') + par.add_argument('--jobs-dir', default='jobs') + par.add_argument('--ids', required=True, help='space-separated IDMA_BACKEND_IDS') + par.add_argument('--source', action='append', default=[], metavar='GLOB') + par.add_argument('--matrix-file', required=True, + help='CI workflow that must fan out over every backend id') + par.add_argument('--mxneg-cases', default='', + help='negative-test cases the workflow legs must request between them') + args = par.parse_args() + + patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] + with open(args.jobs, 'r') as handle: + jobs = json.load(handle) + sources = load_sources(patterns) + errors = [] + + # (a) every backend id is represented by a jobs.json entry + synth_tops = {body.get('synth_top') for body in jobs.values()} + for backend_id in args.ids.split(): + expected = 'idma_backend_synth_' + backend_id + if expected not in synth_tops: + errors.append('backend id {} has no {} entry (expected synth_top {})'.format( + backend_id, os.path.basename(args.jobs), expected)) + + # (b) every referenced job path exists + referenced = set() + for name, body in jobs.items(): + for job, rel in body.get('jobs', {}).items(): + referenced.add(rel) + if not os.path.isfile(os.path.join(args.jobs_dir, rel)): + errors.append('{}: job "{}" references missing file {}/{}'.format( + name, job, args.jobs_dir, rel)) + + # (b, inverse) a job file on disk that nothing references, and is not a + # known-unwired file, is either dead or a forgotten entry + on_disk = set() + for path in glob.glob(os.path.join(args.jobs_dir, '**', '*.txt'), recursive=True): + on_disk.add(os.path.relpath(path, args.jobs_dir)) + for rel in sorted(on_disk - referenced - UNWIRED_JOBS): + errors.append('{}/{} is referenced by no {} entry'.format( + args.jobs_dir, rel, os.path.basename(args.jobs))) + for rel in sorted(UNWIRED_JOBS - on_disk): + errors.append('{} is listed as deliberately unwired but does not exist'.format(rel)) + + # (c) named testbenches and synth wrappers exist in the sources + for name, body in jobs.items(): + for field in ('testbench', 'synth_top'): + module = body.get(field) + if not module: + errors.append('{}: no {} named'.format(name, field)) + continue + if not re.search(r'\bmodule\s+' + re.escape(module) + r'\b', sources): + errors.append('{}: {} "{}" is not defined in the sources'.format( + name, field, module)) + + # (d) the CI matrix must name every backend id and request every negative + # test case; a leg that silently does not exist is worse than no leg + with open(args.matrix_file, 'r') as handle: + matrix = handle.read() + for backend_id in args.ids.split(): + if not re.search(r'^\s*-\s*' + re.escape(backend_id) + r'\s*$', matrix, re.M): + errors.append('{} has no matrix leg for backend id {}'.format( + args.matrix_file, backend_id)) + if args.mxneg_cases: + requested = set() + for line in re.findall(r'^\s*cases:\s*(.+?)\s*$', matrix, re.M): + requested.update(line.split()) + for case in args.mxneg_cases.split(): + if case not in requested: + errors.append('{} requests no negative-test case {}'.format( + args.matrix_file, case)) + + for message in errors: + print('error: ' + message) + if errors: + print('check_jobs: {} problem(s)'.format(len(errors))) + return 1 + print('check_jobs: {} entries, {} job paths, all consistent'.format( + len(jobs), len(referenced))) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/util/idma_params.py b/util/idma_params.py new file mode 100644 index 00000000..daf41c47 --- /dev/null +++ b/util/idma_params.py @@ -0,0 +1,131 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +"""Emit the elaboration configurations of one top, straight from jobs.json. + +Prints one line per configuration: + + -GName=Value -GName=Value ... + +Both slang and verilator take those flags verbatim. slang silently ignores a +``-G`` whose parameter does not exist, so every name is checked against the +module header here; an unknown or misspelled parameter is a hard error rather +than a configuration that quietly does not apply. +""" + +import argparse +import glob +import json +import os +import re +import sys + +# Configurations that sweep the bus width on top of the jobs.json parameters. +# Every jobs.json entry pins DataWidth=32, so nothing wider is elaborated by +# the directed matrix. +DEFAULT_WIDTHS = [32, 64, 512, 1024] + + +def module_parameters(top, sources): + """Return the parameter names declared in the header of module *top*.""" + decl = re.compile(r'\bmodule\s+' + re.escape(top) + r'\b') + for path in sources: + try: + with open(path, 'r', errors='replace') as handle: + text = handle.read() + except OSError: + continue + match = decl.search(text) + if not match: + continue + start = text.find('#(', match.end()) + if start < 0: + return set() + depth = 0 + for pos in range(start + 1, len(text)): + if text[pos] == '(': + depth += 1 + elif text[pos] == ')': + depth -= 1 + if depth == 0: + header = text[start:pos] + break + else: + raise SystemExit('error: unterminated parameter list of {}'.format(top)) + names = re.findall(r'\bparameter\b[^,;()]*?\b([A-Za-z_]\w*)\s*=', header) + return set(names) + raise SystemExit('error: module {} not found in the given sources'.format(top)) + + +def entries_for(jobs, top): + """jobs.json entries whose synth_top or testbench is *top*.""" + return [(name, body) for name, body in jobs.items() + if body.get('synth_top') == top or body.get('testbench') == top] + + +def main(): + par = argparse.ArgumentParser(description=__doc__) + par.add_argument('--top', required=True) + par.add_argument('--jobs', default='jobs/jobs.json') + par.add_argument('--source', action='append', default=[], metavar='GLOB', + help='glob of SystemVerilog sources to search for the module header') + par.add_argument('--widths', default=' '.join(str(w) for w in DEFAULT_WIDTHS), + help='space-separated DataWidth sweep; empty disables the sweep') + args = par.parse_args() + + patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] + sources = [] + for pattern in patterns: + sources += sorted(glob.glob(pattern, recursive=True)) + if not sources: + raise SystemExit('error: no sources matched {}'.format(patterns)) + + with open(args.jobs, 'r') as handle: + jobs = json.load(handle) + + declared = module_parameters(args.top, sources) + matches = entries_for(jobs, args.top) + if not matches: + raise SystemExit('error: no {} entry names {} as a synth_top or testbench'.format( + os.path.basename(args.jobs), args.top)) + + lines = [] + seen = set() + base = {} + for name, body in matches: + params = body.get('params', {}) + unknown = sorted(set(params) - declared) + if unknown: + raise SystemExit('error: {} names parameter(s) {} that module {} does not ' + 'declare'.format(name, ', '.join(unknown), args.top)) + if not base: + base = dict(params) + flags = ' '.join('-G{}={}'.format(k, v) for k, v in params.items()) + seen.add(flags) + lines.append('{} {}'.format(name, flags).rstrip()) + + widths = [w for w in args.widths.split() if w] + if widths: + if 'DataWidth' not in declared: + print('note: {} has no DataWidth parameter; width sweep not applicable'.format( + args.top), file=sys.stderr) + else: + for width in widths: + cfg = dict(base) + cfg['DataWidth'] = width + flags = ' '.join('-G{}={}'.format(k, v) for k, v in cfg.items()) + if flags in seen: # identical to a jobs.json configuration + continue + seen.add(flags) + lines.append('dw{} {}'.format(width, flags)) + + print('\n'.join(lines)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) From 82341ddfd2603de97be99f537638e51ec9086fa4 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 09:44:19 +0200 Subject: [PATCH 04/24] ci: Fold the elaborate workflow into the verify matrix verify.yml already runs the same elaboration through idma_verify_shared, so the separate workflow only duplicated the run and installed Verilator its own way instead of through the shared setup action. --- .github/workflows/ci.yml | 5 ---- .github/workflows/elaborate.yml | 47 --------------------------------- 2 files changed, 52 deletions(-) delete mode 100644 .github/workflows/elaborate.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 91f295e8..fff45e8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,11 +37,6 @@ jobs: uses: ./.github/workflows/analyze.yml secrets: inherit - elaborate: - needs: lint - uses: ./.github/workflows/elaborate.yml - secrets: inherit - verify: needs: lint uses: ./.github/workflows/verify.yml diff --git a/.github/workflows/elaborate.yml b/.github/workflows/elaborate.yml deleted file mode 100644 index 963c1f70..00000000 --- a/.github/workflows/elaborate.yml +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2026 ETH Zurich and University of Bologna. -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -# Author: -# - Daniel Keller - -name: elaborate - -on: - workflow_call: - workflow_dispatch: - -jobs: - - # Elaborate every synthesis top plus the snitch_cluster-gated inst64 frontend - # (which has no public concrete instance otherwise), so fork PRs that skip the - # proprietary EDA sims still catch port, parameter and connectivity errors. - # ubuntu-24.04 ships Verilator 5.x via apt. - elaborate: - runs-on: ubuntu-24.04 - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Cache Bender database - uses: ./.github/actions/bender-db-cache - - - name: Install uv - uses: astral-sh/setup-uv@v7 - with: - enable-cache: true - - - name: Install Bender - uses: pulp-platform/pulp-actions/bender-install@v2.5.0 - with: - version: 0.32.0 - - - name: Install Verilator - run: sudo apt-get update && sudo apt-get install -y verilator - - - name: Generate RTL - run: uv run --locked make idma_hw_all - - - name: Elaborate synthesis tops and inst64 frontend - run: uv run --locked make idma_lint_all From 197402ca7763ed4226f4a35701610e6a7c189ea7 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 10:34:29 +0200 Subject: [PATCH 05/24] ci: Add the missing verify helper scripts idma.mk references both, so the verification targets could not run from a clean checkout. --- util/run_vlt_sim.py | 129 ++++++++++++++++++++++++++++++++++++++++++++ util/slang_elab.py | 44 +++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 util/run_vlt_sim.py create mode 100644 util/slang_elab.py diff --git a/util/run_vlt_sim.py b/util/run_vlt_sim.py new file mode 100644 index 00000000..8cd4d8e0 --- /dev/null +++ b/util/run_vlt_sim.py @@ -0,0 +1,129 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +"""Build and run one Verilator simulation, then check it three ways. + +A simulation leg only passes when all three hold: + 1. the run exited with the expected status (0 for a positive test, non-zero + for a negative test, whose guard is supposed to fire), + 2. the log exists and is non-empty, + 3. the log contains the expected positive token. + +A negated grep for "Error:" is deliberately not used: it passes when the log is +missing, when the simulator died before writing anything, and when the token +was never printed. Every condition here is stated positively. +""" + +import argparse +import os +import shlex +import subprocess +import sys +import time + + +def run(cmd, log_path, cwd): + """Run cmd, tee to log_path, return (rc, wall_seconds).""" + start = time.monotonic() + with open(log_path, 'wb') as log: + proc = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + for chunk in iter(lambda: proc.stdout.read(4096), b''): + log.write(chunk) + sys.stdout.buffer.write(chunk) + sys.stdout.flush() + proc.stdout.close() + rc = proc.wait() + return rc, time.monotonic() - start + + +def parse_args(): + par = argparse.ArgumentParser(description=__doc__) + par.add_argument('--top', required=True) + par.add_argument('--flist', required=True) + par.add_argument('--dir', required=True, help='build/run scratch directory') + par.add_argument('--tag', default=None, help='unique name for this configuration') + par.add_argument('--token', required=True, help='string the log must contain') + par.add_argument('--expect', choices=['pass', 'fail'], default='pass', + help='pass: run must exit 0; fail: run must exit non-zero') + par.add_argument('--param', action='append', default=[], metavar='NAME=VALUE', + help='top-level parameter override (-G)') + par.add_argument('--plusarg', action='append', default=[], metavar='+ARG') + par.add_argument('--dpi', action='append', default=[], metavar='OBJ', + help='precompiled DPI object to link') + par.add_argument('--verilator', default=os.environ.get('VERILATOR', 'verilator')) + par.add_argument('--makeflags', default=os.environ.get('IDMA_VLT_MAKEFLAGS', '')) + par.add_argument('--define', action='append', default=[], metavar='NAME', + help='preprocessor define passed to verilator (-D)') + par.add_argument('--vlt-arg', action='append', default=[], metavar='ARG', + help='extra verilator argument; use --vlt-arg=-X for dashed values') + return par.parse_args() + + +def main(): + args = parse_args() + tag = args.tag or args.top + workdir = os.path.abspath(args.dir) + os.makedirs(workdir, exist_ok=True) + objdir = os.path.join(workdir, 'obj_' + tag) + binary = os.path.join(objdir, 'simv') + build_log = os.path.join(workdir, tag + '_build.log') + run_log = os.path.join(workdir, tag + '_run.log') + + build = shlex.split(args.verilator) + [ + '--binary', '--timing', '--assert', '-Wno-fatal', '--error-limit', '1000', + '-CFLAGS', '-O2', + '--unroll-count', '4096', '--unroll-stmts', '200000', + '-Mdir', objdir, '-o', 'simv', + '-f', os.path.abspath(args.flist), '--top-module', args.top, + ] + if args.makeflags: + build += ['-MAKEFLAGS', args.makeflags] + build += ['-G' + p for p in args.param] + build += ['-D' + d for d in args.define] + if args.dpi: + build += ['-LDFLAGS', ' '.join(os.path.abspath(d) for d in args.dpi)] + build += args.vlt_arg + + print('--- building {} [{}] ---'.format(args.top, tag), flush=True) + rc, secs = run(build, build_log, workdir) + print('--- build {} rc={} ({:.1f} s) ---'.format(tag, rc, secs), flush=True) + if rc != 0: + print('FAIL {}: verilator build failed (see {})'.format(tag, build_log)) + return 1 + if not os.path.isfile(binary): + print('FAIL {}: no simulation binary at {}'.format(tag, binary)) + return 1 + + print('--- running {} [{}] ---'.format(args.top, tag), flush=True) + rc, secs = run([binary] + args.plusarg, run_log, workdir) + print('--- run {} rc={} ({:.2f} s) ---'.format(tag, rc, secs), flush=True) + + # 1. exit status + if args.expect == 'pass' and rc != 0: + print('FAIL {}: expected exit 0, got {}'.format(tag, rc)) + return 1 + if args.expect == 'fail' and rc == 0: + print('FAIL {}: negative test exited 0; the guard never fired'.format(tag)) + return 1 + # 2. log present and non-empty + if not os.path.isfile(run_log) or os.path.getsize(run_log) == 0: + print('FAIL {}: run log {} is missing or empty'.format(tag, run_log)) + return 1 + # 3. positive token + with open(run_log, 'r', errors='replace') as handle: + text = handle.read() + if args.token not in text: + print('FAIL {}: token "{}" absent from {}'.format(tag, args.token, run_log)) + return 1 + + print('PASS {}: rc={} token="{}"'.format(tag, rc, args.token)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/util/slang_elab.py b/util/slang_elab.py new file mode 100644 index 00000000..1682808c --- /dev/null +++ b/util/slang_elab.py @@ -0,0 +1,44 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +"""Run a slang elaboration through pyslang. + +pyslang ships the same driver as the ``slang`` binary, so every argument is +forwarded verbatim and a red CI leg reproduces locally with the same flags. +A wrapper script is deliberately avoided: some site wrappers swallow a crash +and return 0, which would turn this gate vacuous. + +Exit codes mirror the slang driver: 0 clean, 1 bad command line, 5 compilation +or elaboration error. Any unexpected exception is also a non-zero exit. +""" + +import shlex +import sys + +import pyslang + + +def main(argv): + driver = pyslang.driver.Driver() + driver.addStandardArgs() + + # The string overload parses a full command line; token 0 is the program name. + cmdline = ' '.join(shlex.quote(a) for a in ['slang'] + list(argv)) + if not driver.parseCommandLine(cmdline): + return 1 + if not driver.processOptions(): + return 1 + + # Both stages must run; `&` rather than `and` so parse failures are reported + # together with the elaboration diagnostics instead of masking them. + ok = driver.parseAllSources() + ok = driver.runFullCompilation() & ok + return 0 if ok else 5 + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) From 7d5f888e626141d19777468f84d6f0565bfdd648 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 10:36:23 +0200 Subject: [PATCH 06/24] ci: Lint the whole source tree with Verible, not just the diff The existing job reports through reviewdog with -diff, so it only sees lines a pull request touched and a violation that predates it never fails anything. One had already accumulated unseen. Add a tree-scoped run over src/ and wrap the long localparam it found. --- .github/workflows/lint.yml | 18 ++++++++++++++++++ idma.mk | 10 ++++++++++ src/frontend/inst64/idma_inst64_top.sv | 3 ++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 4fcbf353..69adf7d7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,6 +32,24 @@ jobs: exclude_paths: | target/sim/vsim/wave/tpl/*.do.tpl + # Tree-scoped counterpart to lint-sv: that job reports through reviewdog with + # -diff, so it only sees lines a PR touched and pre-existing violations never + # fail it. This one checks all of src/ so the backlog stays at zero. + lint-sv-tree: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v5 + - + name: Install Verible + uses: chipsalliance/verible-actions-common/install-verible@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - + name: Lint all of src + run: make idma_lint_sv + lint-sv: runs-on: ubuntu-latest steps: diff --git a/idma.mk b/idma.mk index 9a6406b7..56ced250 100644 --- a/idma.mk +++ b/idma.mk @@ -640,6 +640,16 @@ IDMA_LINT_TOPS ?= $(addprefix idma_backend_synth_,$(IDMA_BACKEND_IDS)) \ idma_mp_midend_synth \ idma_rt_midend_synth +# Tree-scoped SystemVerilog style lint. The PR-annotating CI job reports through +# reviewdog with -diff, so it only ever sees lines a PR touched; this target +# checks all of src/ so pre-existing violations cannot accumulate unseen. +VERIBLE ?= verible-verilog-lint + +.PHONY: idma_lint_sv +idma_lint_sv: + $(VERIBLE) --waiver_files $(IDMA_ROOT)/.github/verible.waiver \ + $$(find $(IDMA_ROOT)/src -name '*.sv' -o -name '*.svh' | sort) + .PHONY: idma_lint_elab idma_lint_elab: mkdir -p $(IDMA_VLT_DIR) diff --git a/src/frontend/inst64/idma_inst64_top.sv b/src/frontend/inst64/idma_inst64_top.sv index 802961c3..79b37fff 100644 --- a/src/frontend/inst64/idma_inst64_top.sv +++ b/src/frontend/inst64/idma_inst64_top.sv @@ -72,7 +72,8 @@ module idma_inst64_top #( localparam int unsigned NumDim = 32'd2; localparam int unsigned BufferDepth = 32'd3; localparam int unsigned NumRules = 32'd5; - localparam int unsigned AwInFlightCntWidth = (NumAxInFlight < 2) ? 32'd1 : $clog2(NumAxInFlight + 1); + localparam int unsigned AwInFlightCntWidth = + (NumAxInFlight < 2) ? 32'd1 : $clog2(NumAxInFlight + 1); // derived constants and types localparam int unsigned StrbWidth = AxiDataWidth / 32'd8; From e75b80a2d580400c655ad088edf9cf349c34eb22 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 10:52:22 +0200 Subject: [PATCH 07/24] ci: Widen the elaboration sweep and drop the duplicate inst64 wrapper The inst64 testbench gives the frontend a concrete top, so the lint-only wrapper was a second copy of the same instantiation; the register frontend was elaborated for one variant of three, and no configuration ever turned compute on. Elaborate the testbench instead, add the missing register variants and a compute sweep, and reject sweep entries whose ops or tuning do not fit the RTL structs or enable no datapath at all. --- .github/workflows/verify.yml | 26 +++-- Bender.yml | 6 -- idma.mk | 67 +++++++++--- src/backend/tpl/idma_legalizer.sv.tpl | 3 +- test/frontend/idma_inst64_drv_if.sv | 5 +- test/frontend/tb_idma_reg_frontend.sv | 127 +++++++++++++++++------ test/idma_inst64_lint.sv | 140 -------------------------- util/idma_params.py | 96 ++++++++++++++++++ 8 files changed, 273 insertions(+), 197 deletions(-) delete mode 100644 test/idma_inst64_lint.sv diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index c051aeb1..6c883a07 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -10,12 +10,21 @@ # sims on the GitLab mirror - get a real gate, and the licensed run becomes a # confirmation rather than the only signal. # +# Two tiers: elaboration first, simulation behind it. elab-backend, elab-shared-tops +# and elab-tb-shared run in parallel and every simulation job `needs:` all three, so a +# port, parameter or connectivity break fails in seconds instead of burning every +# simulation runner. `fail-fast: false` stays on the matrices, so the elaboration tier +# still reports every leg. elab-multihead, codegen-consistency and report-slang-extra +# are parallel peers, not part of the barrier: they gate nothing the simulations use. +# # House rules for this file, enforced in review: # - no `|| true`, no `continue-on-error` on any step of a gating job; # - no negated grep as a pass criterion; every simulation leg checks exit # status AND a non-empty log AND a positive token (util/run_vlt_sim.py); # - no numeric warning-count thresholds; -# - every excluded top or configuration is named below with its reason. +# - every excluded top or configuration is named below with its reason; +# - `needs:` orders jobs, it does not share workspaces; every job keeps its own +# `Generate RTL` step. # # NAMED EXCLUSIONS - dropped coverage, not silent skips: # tb_idma_nd_midend_b2b hangs ([B2B] timeout); latent reset/LFSR race @@ -92,8 +101,9 @@ jobs: name: Elaborate ${{ matrix.id }} run: uv run --locked make idma_verify_backend IDMA_VERIFY_ID=${{ matrix.id }} - # The four non-backend synthesis tops with their own jobs.json parameters, - # plus PR #184's default-parameter elaboration of all 12 tops and inst64. + # The four non-backend synthesis tops with their own jobs.json parameters, plus + # the default-parameter elaboration of all 12 tops and of the inst64 testbench, + # the only public concrete binding of idma_inst64_top. elab-shared-tops: runs-on: ubuntu-24.04 steps: @@ -112,9 +122,9 @@ jobs: name: Elaborate the shared synthesis tops run: uv run --locked make idma_verify_shared - # Testbench tops that are not per-backend. Verilator cannot parse the - # verification stack at all, so slang is the only public tool for this half - # of the repository. + # Testbench tops that are not per-backend, including the three generated register + # frontends through tb_idma_reg_frontend. Verilator cannot parse the verification + # stack at all, so slang is the only public tool for this half of the repository. elab-tb-shared: runs-on: ubuntu-24.04 steps: @@ -150,6 +160,7 @@ jobs: # Real simulation, byte-exact against the DPI-C goldens. The destination is # pre-poisoned, so a backend that writes nothing fails. sim-datapath-mxquant: + needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 steps: - @@ -168,6 +179,7 @@ jobs: run: uv run --locked make idma_verify_sim_mxquant IDMA_VLT_MAKEFLAGS=-j4 sim-datapath-mxroundtrip: + needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 steps: - @@ -186,6 +198,7 @@ jobs: run: uv run --locked make idma_verify_sim_mxroundtrip IDMA_VLT_MAKEFLAGS=-j4 sim-datapath-transpose: + needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 steps: - @@ -207,6 +220,7 @@ jobs: # guard. --assert is load-bearing; without it both testbenches exit 0 and the # leg could not fail. sim-negative: + needs: [elab-backend, elab-shared-tops, elab-tb-shared] strategy: fail-fast: false matrix: diff --git a/Bender.yml b/Bender.yml index 55a91552..b2b62ee9 100644 --- a/Bender.yml +++ b/Bender.yml @@ -163,9 +163,3 @@ sources: files: - test/tb_idma_backend_multihead.sv - test/tb_idma_backend_multihead_rw.sv - - # inst64 frontend lint/elaboration wrapper: a public concrete-typed top so CI - # can verilator --lint-only the snitch_cluster-gated idma_inst64_top - - target: all(snitch_cluster, lint) - files: - - test/idma_inst64_lint.sv diff --git a/idma.mk b/idma.mk index 56ced250..6bb2175c 100644 --- a/idma.mk +++ b/idma.mk @@ -611,6 +611,16 @@ IDMA_VLT_ARGS := --cc \ IDMA_VLT_TOP ?= IDMA_VLT_PARAMS ?= +# Warning classes measured at 0 occurrences over all 12 synth tops, so they gate. +# Not promoted, with the measurement: UNOPTFLAT (31, includes the known +# idma_nd_midend stage_done loop) and PINMISSING (12, all in axi_stream and +# common_cells, none iDMA-owned). +IDMA_VLT_WERROR := -Werror-LATCH -Werror-MULTIDRIVEN -Werror-IMPLICIT +# The unroll budget matches util/run_vlt_sim.py; without it verilator 5.020 reports +# BLKLOOPINIT on the compute pack/unpack loops at DataWidth >= 256. +IDMA_VLT_LINT_ARGS := --lint-only -Wno-fatal --timing $(IDMA_VLT_WERROR) \ + --unroll-count 4096 --unroll-stmts 200000 + .PRECIOUS: $(IDMA_VLT_DIR)/%_elab.log $(IDMA_VLT_DIR)/%_elab.log: $(IDMA_BENDER_FILES) $(IDMA_FULL_TB) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) @@ -622,14 +632,22 @@ $(IDMA_VLT_DIR)/%_elab.log: $(IDMA_BENDER_FILES) $(IDMA_FULL_TB) $(IDMA_FULL_RTL idma_verilator_clean: rm -rf $(IDMA_VLT_DIR) -# inst64 frontend elaboration gate: bind the snitch_cluster-gated idma_inst64_top -# to concrete types (test/idma_inst64_lint.sv) and verilator --lint-only it, so -# public CI catches frontend port/param/elaboration errors. Run after idma_hw_all. +# inst64 frontend elaboration gate: verilator --lint-only over tb_idma_inst64_axi_copy, +# the only public concrete binding of the snitch_cluster-gated idma_inst64_top. -t test +# adds obi_sim_mem; --top-module is load-bearing, it trims the flat filelist to the +# reachable cone. Run after idma_hw_all. slang covers the same top via +# IDMA_TB_SHARED_TOPS. +IDMA_INST64_TB := tb_idma_inst64_axi_copy +IDMA_INST64_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ + -t snitch_cluster + .PHONY: idma_lint_inst64 idma_lint_inst64: mkdir -p $(IDMA_VLT_DIR) - $(BENDER) script verilator -t rtl -t snitch_cluster -t lint > $(IDMA_VLT_DIR)/idma_inst64_lint.f - $(VERILATOR) --lint-only -Wno-fatal --timing -f $(IDMA_VLT_DIR)/idma_inst64_lint.f --top-module idma_inst64_lint + $(BENDER) script verilator $(IDMA_INST64_T) --top $(IDMA_INST64_TB) \ + > $(IDMA_VLT_DIR)/idma_inst64_tb.f + $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_inst64_tb.f \ + --top-module $(IDMA_INST64_TB) # Synthesis-wrapper elaboration gate: verilator elaborates every synth top so a # port, parameter or connectivity break is caught without the proprietary EDA @@ -685,6 +703,15 @@ IDMA_VERIFY_ID ?= rw_axi # jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise. IDMA_ELAB_WIDTHS ?= 32 64 512 1024 +# Compute-enabled configurations, as DataWidth:ComputeOps:ComputeTuning. No +# jobs.json entry enables the compute datapath, so its generate branches are +# unreachable otherwise. ComputeOps is {transpose, mxquant, mxdequant, mxfp16}. +# 64:15:1 every sub-unit, ping-pong transpose banks +# 512:15:1 the wide bus, where the pack/unpack loops are stressed +# 64:8:0 transpose only, single bank; also the no-mxquant/no-mxdequant branches +# 64:6:1 mx only, Fp16En=0; also the no-transpose branch +IDMA_ELAB_COMPUTE ?= 64:15:1 512:15:1 64:8:0 64:6:1 + IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang IDMA_SLANG_VERSION ?= 11.0.0 @@ -700,13 +727,6 @@ IDMA_SLANG_SYNTH_T := -t rtl -t synth IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ -t snitch_cluster -t asic -# Warning classes measured at 0 occurrences over all 12 synth tops, so they gate. -# Not promoted, with the measurement: UNOPTFLAT (31, includes the known -# idma_nd_midend stage_done loop) and PINMISSING (12, all in axi_stream and -# common_cells, none iDMA-owned). -IDMA_VLT_WERROR := -Werror-LATCH -Werror-MULTIDRIVEN -Werror-IMPLICIT -IDMA_VLT_LINT_ARGS := --lint-only -Wno-fatal --timing $(IDMA_VLT_WERROR) - # Non-backend synthesis tops, elaborated with their own jobs.json parameters IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synth \ idma_rt_midend_synth @@ -714,14 +734,20 @@ IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synt # Out-of-tree multi-head variants; license-free but generated only on request IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi +# Generated register frontends, as RegVariant:module. They share a parameter and +# port list, so tb_idma_reg_frontend - the only public concrete binding - elaborates +# each of them; without this reg64_2d and reg64_1d are elaborated by nothing. Only +# variant 0 is simulated; the testbench refuses to run on the other two. +IDMA_REG_VARIANTS ?= 0:idma_reg32_3d 1:idma_reg64_2d 2:idma_reg64_1d + # Testbench tops that are not per-backend. tb_idma_backend_* are covered by the -# matching idma_verify_backend leg. -IDMA_TB_SHARED_TOPS := tb_idma_desc64_top tb_idma_desc64_bench tb_idma_reg_frontend \ +# matching idma_verify_backend leg; tb_idma_reg_frontend by IDMA_REG_VARIANTS. +IDMA_TB_SHARED_TOPS := tb_idma_desc64_top tb_idma_desc64_bench \ tb_idma_nd_midend tb_idma_nd_midend_b2b tb_idma_rt_midend \ tb_idma_transpose_midend tb_idma_otf_transpose \ tb_idma_transpose_nd tb_idma_transpose_b2b tb_idma_mxquant \ tb_idma_mxroundtrip tb_idma_mxrand tb_idma_mxneg \ - tb_idma_mxperf tb_idma_mxclear + tb_idma_mxperf tb_idma_mxclear $(IDMA_INST64_TB) IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src/**/*.sv' \ --source '$(IDMA_ROOT)/test/**/*.sv' @@ -735,6 +761,7 @@ define idma_elab_cfg mkdir -p $(IDMA_VERIFY_DIR) $(PYTHON) $(IDMA_UTIL_DIR)/idma_params.py --top $1 \ --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --widths "$(IDMA_ELAB_WIDTHS)" \ + --compute "$(IDMA_ELAB_COMPUTE)" \ $(IDMA_SOURCE_GLOBS) > $(IDMA_VERIFY_DIR)/$1.cfg endef @@ -804,11 +831,21 @@ idma_verify_shared: idma_lint_all done idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f + @set -e; for id in $(IDMA_FE_IDS); do \ + case " $(IDMA_REG_VARIANTS) " in *":idma_$$id "*) ;; \ + *) echo "error: no IDMA_REG_VARIANTS entry elaborates idma_$$id"; exit 1;; esac; \ + done set -e; for top in $(IDMA_TB_SHARED_TOPS); do \ echo "--- slang $$top ---"; \ $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $$top \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done + set -e; for entry in $(IDMA_REG_VARIANTS); do \ + IFS=: read -r v mod <<< "$$entry"; \ + echo "--- slang tb_idma_reg_frontend [$$mod] ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top tb_idma_reg_frontend -GRegVariant=$$v \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ + done # Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the # two extra synthesis tops and the two multi-head testbenches diff --git a/src/backend/tpl/idma_legalizer.sv.tpl b/src/backend/tpl/idma_legalizer.sv.tpl index 8329ba88..bd56cf3e 100644 --- a/src/backend/tpl/idma_legalizer.sv.tpl +++ b/src/backend/tpl/idma_legalizer.sv.tpl @@ -726,13 +726,14 @@ ${database[protocol]['legalizer_write_data_path']} (req_i.opt.compute.op == idma_pkg::COMPUTE_MXDEQUANT_FP16)) & (req_i.length % (idma_pkg::MxBlockBytes*StrbWidth) != 0)), clk_i, !rst_ni) // NOT IMPLEMENTED: dequant output length that overflows the length field + // 65-bit literal: a 64-bit length shifts by 64, which overflows a 64-bit operand `ASSERT_NEVER(ComputeMxdequantLengthFits, (ready_o & valid_i & req_i.opt.compute.enable & ((req_i.opt.compute.op == idma_pkg::COMPUTE_MXDEQUANT) | (req_i.opt.compute.op == idma_pkg::COMPUTE_MXDEQUANT_FP16)) & ($bits(req_i.length) < 64) & (((64'(req_i.length) / 64'(idma_pkg::MxBlockBytes)) * 64'(idma_pkg::compute_out_bytes(req_i.opt.compute.op))) >= - (64'd1 << $bits(req_i.length)))), clk_i, !rst_ni) + (65'd1 << $bits(req_i.length)))), clk_i, !rst_ni) // compute retires on the per-beat write pulse; TileLink writes retire per burst `ASSERT_NEVER(ComputeDstTilelink, (ready_o & valid_i & req_i.opt.compute.enable & (req_i.opt.dst_protocol == idma_pkg::TILELINK)), clk_i, !rst_ni) diff --git a/test/frontend/idma_inst64_drv_if.sv b/test/frontend/idma_inst64_drv_if.sv index b403569a..fd641de7 100644 --- a/test/frontend/idma_inst64_drv_if.sv +++ b/test/frontend/idma_inst64_drv_if.sv @@ -67,10 +67,13 @@ interface idma_inst64_drv_if #( // it pops on the posedge, so sample it in a clocked process instead of reading it // combinationally from a task. acc_rsp_item_t rsp_queue [$]; + // built in a variable first: verilator rejects an assignment pattern as a call argument + acc_rsp_item_t rsp_item; always_ff @(posedge clk) begin : proc_capture_rsp if (rst_n && acc_res_valid && acc_res_ready) begin - rsp_queue.push_back('{id: acc_res.id, data: acc_res.data, error: acc_res.error}); + rsp_item = '{id: acc_res.id, data: acc_res.data, error: acc_res.error}; + rsp_queue.push_back(rsp_item); end end diff --git a/test/frontend/tb_idma_reg_frontend.sv b/test/frontend/tb_idma_reg_frontend.sv index 7132d1b3..2912195a 100644 --- a/test/frontend/tb_idma_reg_frontend.sv +++ b/test/frontend/tb_idma_reg_frontend.sv @@ -6,6 +6,8 @@ // - Daniel Keller // Self-checking testbench for the iDMA register frontend (idma_reg32_3d, apb4-flat). +// `RegVariant` also binds idma_reg64_2d and idma_reg64_1d for elaboration only; they +// have no stimulus here, and are covered by nothing else. // Drives the APB config slave with the standard apb_test::apb_driver against a // controllable backend stub and checks the non-blocking next_id launch contract: // the config read completes promptly (even under backend backpressure) and the @@ -20,7 +22,11 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // number of streams the elaborated DUT exposes (checked at instantiation) parameter int unsigned NumStreams = 32'd1, // number of config-bus ports (arbitrated by the reg frontend's rr_arb_tree) - parameter int unsigned NumRegs = 32'd1 + parameter int unsigned NumRegs = 32'd1, + // generated frontend under test: 0 = idma_reg32_3d, 1 = idma_reg64_2d, 2 = idma_reg64_1d. + // Only variant 0 is simulated; 1 and 2 are elaboration-only (their register maps differ + // and reg64_1d emits a flat 1D request), and the stimulus refuses to run on them. + parameter int unsigned RegVariant = 32'd0 ); // -------------------------------------------------------------------------- @@ -33,11 +39,13 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( localparam int unsigned CfgDataWidth = 32'd32; localparam int unsigned CfgStrbWidth = CfgDataWidth / 32'd8; localparam int unsigned IdCounterWidth = 32'd32; - // idma data-path (reg32_3d: 32-bit data, 3 ND dims) - localparam int unsigned AddrWidth = 32'd32; - localparam int unsigned DataWidth = 32'd32; - localparam int unsigned NumDim = 32'd3; - localparam int unsigned RepWidth = 32'd32; + // idma data-path: reg32_3d is 32-bit over 3 ND dims, both reg64 variants are 64-bit. + // NumDim shapes the ND request type; reg64_1d takes the flat idma_req_t instead, so + // its ND type is unused and the 2-dim shape is kept only to keep the macro legal. + localparam int unsigned AddrWidth = (RegVariant == 32'd0) ? 32'd32 : 32'd64; + localparam int unsigned DataWidth = AddrWidth; + localparam int unsigned NumDim = (RegVariant == 32'd0) ? 32'd3 : 32'd2; + localparam int unsigned RepWidth = AddrWidth; // apb_driver framing: the blocking driver.read() spans SETUP + first-ACCESS-check + // trailing edge before returning, so a same-cycle (non-blocking) read takes this many // config clocks end-to-end; each extra ACCESS wait state adds one more clock. @@ -50,7 +58,8 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // config-clock cycles is a hang (the non-blocking read completes immediately). localparam int unsigned DeadlockCycles = 32'd2000; - // register map (idma_reg32_3d_addrmap_pkg): base + per-stream stride 0x4 + // register map (idma_reg32_3d_addrmap_pkg): base + per-stream stride 0x4; the reg64 + // variants have their own map and are elaboration-only, so it is not modelled here localparam logic [31:0] REG_CONF = 32'h0000_0000; localparam logic [31:0] REG_STATUS0 = 32'h0000_0004; localparam logic [31:0] REG_NEXT_ID0 = 32'h0000_0044; @@ -179,27 +188,86 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // -------------------------------------------------------------------------- // DUT // -------------------------------------------------------------------------- - idma_reg32_3d #( - .NumRegs ( NumRegs ), - .NumStreams ( NumStreams ), - .IdCounterWidth ( IdCounterWidth ), - .apb_req_t ( cfg_apb_req_t ), - .apb_rsp_t ( cfg_apb_rsp_t ), - .dma_req_t ( idma_nd_req_t ) - ) i_dut ( - .clk_i ( clk ), - .rst_ni ( rst_n ), - .dma_ctrl_req_i ( apb_req ), - .dma_ctrl_rsp_o ( apb_rsp ), - .dma_req_o ( dma_req ), - .req_valid_o ( req_valid ), - .req_ready_i ( req_ready ), - .next_id_i ( next_id ), - .stream_idx_o ( stream_idx ), - .done_id_i ( done_id ), - .busy_i ( busy ), - .midend_busy_i ( midend_busy ) - ); + // All three generated frontends share the parameter and port list; only the module + // name and the emitted request type differ. reg64_1d (NumDim=1) emits a flat + // idma_req_t, which is widened back to the ND shape the stimulus works on. + if (RegVariant == 32'd0) begin : gen_reg32_3d + idma_reg32_3d #( + .NumRegs ( NumRegs ), + .NumStreams ( NumStreams ), + .IdCounterWidth ( IdCounterWidth ), + .apb_req_t ( cfg_apb_req_t ), + .apb_rsp_t ( cfg_apb_rsp_t ), + .dma_req_t ( idma_nd_req_t ) + ) i_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .dma_ctrl_req_i ( apb_req ), + .dma_ctrl_rsp_o ( apb_rsp ), + .dma_req_o ( dma_req ), + .req_valid_o ( req_valid ), + .req_ready_i ( req_ready ), + .next_id_i ( next_id ), + .stream_idx_o ( stream_idx ), + .done_id_i ( done_id ), + .busy_i ( busy ), + .midend_busy_i ( midend_busy ) + ); + end else if (RegVariant == 32'd1) begin : gen_reg64_2d + idma_reg64_2d #( + .NumRegs ( NumRegs ), + .NumStreams ( NumStreams ), + .IdCounterWidth ( IdCounterWidth ), + .apb_req_t ( cfg_apb_req_t ), + .apb_rsp_t ( cfg_apb_rsp_t ), + .dma_req_t ( idma_nd_req_t ) + ) i_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .dma_ctrl_req_i ( apb_req ), + .dma_ctrl_rsp_o ( apb_rsp ), + .dma_req_o ( dma_req ), + .req_valid_o ( req_valid ), + .req_ready_i ( req_ready ), + .next_id_i ( next_id ), + .stream_idx_o ( stream_idx ), + .done_id_i ( done_id ), + .busy_i ( busy ), + .midend_busy_i ( midend_busy ) + ); + end else if (RegVariant == 32'd2) begin : gen_reg64_1d + idma_req_t dut_req_1d; + + idma_reg64_1d #( + .NumRegs ( NumRegs ), + .NumStreams ( NumStreams ), + .IdCounterWidth ( IdCounterWidth ), + .apb_req_t ( cfg_apb_req_t ), + .apb_rsp_t ( cfg_apb_rsp_t ), + .dma_req_t ( idma_req_t ) + ) i_dut ( + .clk_i ( clk ), + .rst_ni ( rst_n ), + .dma_ctrl_req_i ( apb_req ), + .dma_ctrl_rsp_o ( apb_rsp ), + .dma_req_o ( dut_req_1d ), + .req_valid_o ( req_valid ), + .req_ready_i ( req_ready ), + .next_id_i ( next_id ), + .stream_idx_o ( stream_idx ), + .done_id_i ( done_id ), + .busy_i ( busy ), + .midend_busy_i ( midend_busy ) + ); + + always_comb begin : proc_widen_1d_req + dma_req = '0; + dma_req.burst_req = dut_req_1d; + end + end else begin : gen_bad_variant + // an out-of-range RegVariant must not silently re-elaborate the last variant + $error("RegVariant %0d is not a generated register frontend", RegVariant); + end // -------------------------------------------------------------------------- // Backend stub. `req_ready` is directly controllable by the tests. On each @@ -418,6 +486,9 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( int unsigned rcyc; // last read's ACCESS-phase cycle count initial begin : test + // the register map and request layout below model reg32_3d only + if (RegVariant != 32'd0) + $fatal(1, "[TB] RegVariant %0d is elaboration-only, it has no stimulus", RegVariant); errors = 0; checks = 0; req_ready = 1'b1; diff --git a/test/idma_inst64_lint.sv b/test/idma_inst64_lint.sv deleted file mode 100644 index 3e20fc4b..00000000 --- a/test/idma_inst64_lint.sv +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright 2026 ETH Zurich and University of Bologna. -// Solderpad Hardware License, Version 0.51, see LICENSE for details. -// SPDX-License-Identifier: SHL-0.51 - -// Authors: -// - Daniel Keller - -`include "axi/typedef.svh" -`include "obi/typedef.svh" - -/// Lint/elaboration wrapper for the snitch_cluster-gated idma_inst64_top. -/// idma_inst64_top is a fully type-parameterized generic with no concrete -/// instance in the public tree, so no lint tool can elaborate it standalone. -/// This binds concrete AXI/OBI/INIT/acc types (transpose enabled) and ties the -/// ports off so public CI can elaborate the frontend: -/// verilator --lint-only --top-module idma_inst64_lint -/// It carries no behaviour; it exists only to give the generic a concrete top. -module idma_inst64_lint #( - parameter int unsigned AxiDataWidth = 32'd512, - parameter int unsigned AxiAddrWidth = 32'd64, - parameter int unsigned AxiUserWidth = 32'd1, - parameter int unsigned AxiIdWidth = 32'd3, - parameter int unsigned NumChannels = 32'd1 -)( - input logic clk_i, - input logic rst_ni -); - - typedef logic [AxiAddrWidth-1:0] axi_addr_t; - typedef logic [AxiDataWidth-1:0] axi_data_t; - typedef logic [AxiDataWidth/8-1:0] axi_strb_t; - typedef logic [AxiUserWidth-1:0] axi_user_t; - typedef logic [AxiIdWidth-1:0] axi_id_t; - - `AXI_TYPEDEF_AW_CHAN_T(axi_aw_chan_t, axi_addr_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_W_CHAN_T(axi_w_chan_t, axi_data_t, axi_strb_t, axi_user_t) - `AXI_TYPEDEF_B_CHAN_T(axi_b_chan_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_AR_CHAN_T(axi_ar_chan_t, axi_addr_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_R_CHAN_T(axi_r_chan_t, axi_data_t, axi_id_t, axi_user_t) - `AXI_TYPEDEF_REQ_T(axi_req_t, axi_aw_chan_t, axi_w_chan_t, axi_ar_chan_t) - `AXI_TYPEDEF_RESP_T(axi_resp_t, axi_b_chan_t, axi_r_chan_t) - - typedef logic [AxiDataWidth/8-1:0] obi_strb_t; - `OBI_TYPEDEF_MINIMAL_A_OPTIONAL(obi_a_optional_t) - `OBI_TYPEDEF_MINIMAL_R_OPTIONAL(obi_r_optional_t) - `OBI_TYPEDEF_TYPE_A_CHAN_T(obi_a_chan_t, axi_addr_t, axi_data_t, obi_strb_t, axi_id_t, - obi_a_optional_t) - `OBI_TYPEDEF_TYPE_R_CHAN_T(obi_r_chan_t, axi_data_t, axi_id_t, obi_r_optional_t) - `OBI_TYPEDEF_REQ_T(obi_req_t, obi_a_chan_t) - `OBI_TYPEDEF_RSP_T(obi_res_t, obi_r_chan_t) - - // INIT meta-channel types (mirror src/db/idma_init.yml) - typedef struct packed { - logic [AxiAddrWidth-1:0] cfg; - logic [AxiDataWidth-1:0] term; - logic [AxiDataWidth/8-1:0] strb; - logic [AxiIdWidth-1:0] id; - } init_req_chan_t; - typedef struct packed { init_req_chan_t req_chan; logic req_valid; logic rsp_ready; } init_req_t; - typedef struct packed { logic [AxiDataWidth-1:0] init; } init_rsp_chan_t; - typedef struct packed { init_rsp_chan_t rsp_chan; logic rsp_valid; logic req_ready; } init_rsp_t; - - typedef axi_pkg::xbar_rule_64_t addr_rule_t; - - // Snitch accelerator bus (the inst64 frontend decodes data_op/argb) - typedef struct packed { - logic [31:0] id; - logic [31:0] data_op; - logic [63:0] data_arga; - logic [63:0] data_argb; - } acc_req_t; - typedef struct packed { logic [31:0] id; logic [63:0] data; logic error; } acc_res_t; - - typedef struct packed { - logic aw_valid, aw_ready, aw_done, aw_stall; - axi_pkg::len_t aw_len; - axi_pkg::size_t aw_size; - logic ar_valid, ar_ready, ar_done, ar_stall; - axi_pkg::len_t ar_len; - axi_pkg::size_t ar_size; - logic r_valid, r_ready, r_done, r_bw, r_stall; - logic w_valid, w_ready, w_done, w_stall; - logic [31:0] num_bytes_written; - logic b_valid, b_ready, b_done; - logic obi_wr_req, obi_rd_req; - logic dma_busy; - } dma_events_t; - - axi_req_t [NumChannels-1:0] axi_req; - obi_req_t [NumChannels-1:0] obi_req; - dma_events_t [NumChannels-1:0] events; - logic [NumChannels-1:0] busy; - acc_res_t acc_res; - logic acc_req_ready, acc_res_valid; - - idma_inst64_top #( - .AxiDataWidth ( AxiDataWidth ), - .AxiAddrWidth ( AxiAddrWidth ), - .AxiUserWidth ( AxiUserWidth ), - .AxiIdWidth ( AxiIdWidth ), - .NumAxInFlight ( 32'd3 ), - .DMAReqFifoDepth ( 32'd3 ), - .NumChannels ( NumChannels ), - .DMATracing ( 1'b0 ), - .axi_ar_chan_t ( axi_ar_chan_t ), - .axi_aw_chan_t ( axi_aw_chan_t ), - .axi_req_t ( axi_req_t ), - .axi_res_t ( axi_resp_t ), - .init_req_chan_t ( init_req_chan_t ), - .init_rsp_chan_t ( init_rsp_chan_t ), - .init_req_t ( init_req_t ), - .init_rsp_t ( init_rsp_t ), - .obi_a_chan_t ( obi_a_chan_t ), - .obi_r_chan_t ( obi_r_chan_t ), - .obi_req_t ( obi_req_t ), - .obi_res_t ( obi_res_t ), - .acc_req_t ( acc_req_t ), - .acc_res_t ( acc_res_t ), - .dma_events_t ( dma_events_t ), - .addr_rule_t ( addr_rule_t ) - ) i_dut ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .axi_req_o ( axi_req ), - .axi_res_i ( '0 ), - .obi_req_o ( obi_req ), - .obi_res_i ( '0 ), - .busy_o ( busy ), - .acc_req_i ( '0 ), - .acc_req_valid_i ( 1'b0 ), - .acc_req_ready_o ( acc_req_ready ), - .acc_res_o ( acc_res ), - .acc_res_valid_o ( acc_res_valid ), - .acc_res_ready_i ( 1'b0 ), - .hart_id_i ( 32'h0 ), - .events_o ( events ), - .addr_map_i ( '0 ) - ); - -endmodule diff --git a/util/idma_params.py b/util/idma_params.py index daf41c47..908b993c 100644 --- a/util/idma_params.py +++ b/util/idma_params.py @@ -29,6 +29,36 @@ # the directed matrix. DEFAULT_WIDTHS = [32, 64, 512, 1024] +# Parameters a compute-enabled configuration sets. jobs.json never enables the +# compute datapath, so its generate branches are unreachable without this sweep. +COMPUTE_PARAMS = ('EnableCompute', 'ComputeOps', 'ComputeTuning') + + +def packed_struct_fields(typename, sources): + """Return the field names of packed struct *typename*, MSB field first.""" + decl = re.compile(r'typedef\s+struct\s+packed\s*\{([^{}]*)\}\s*' + + re.escape(typename) + r'\s*;', re.S) + for path in sources: + try: + with open(path, 'r', errors='replace') as handle: + text = handle.read() + except OSError: + continue + match = decl.search(text) + if not match: + continue + fields = [] + for line in match.group(1).splitlines(): + line = re.sub(r'//.*', '', line).strip() + if not line.startswith('logic'): + continue + for name in line[len('logic'):].rstrip(';').split(','): + name = name.strip() + if name: + fields.append(name) + return fields + return [] + def module_parameters(top, sources): """Return the parameter names declared in the header of module *top*.""" @@ -75,6 +105,9 @@ def main(): help='glob of SystemVerilog sources to search for the module header') par.add_argument('--widths', default=' '.join(str(w) for w in DEFAULT_WIDTHS), help='space-separated DataWidth sweep; empty disables the sweep') + par.add_argument('--compute', default='', metavar='WIDTH:OPS:TUNING', + help='space-separated compute-enabled configurations; empty ' + 'disables the sweep. Skipped for tops without the parameters') args = par.parse_args() patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] @@ -123,6 +156,69 @@ def main(): seen.add(flags) lines.append('dw{} {}'.format(width, flags)) + computes = [c for c in args.compute.split() if c] + if computes: + have = [p for p in COMPUTE_PARAMS if p in declared] + if have and len(have) != len(COMPUTE_PARAMS): + raise SystemExit('error: module {} declares {} but not {}; the compute sweep ' + 'would silently not apply'.format( + args.top, ', '.join(have), + ', '.join(p for p in COMPUTE_PARAMS if p not in declared))) + if not have: + print('note: {} has no compute parameters; compute sweep not applicable'.format( + args.top), file=sys.stderr) + else: + ops_fields = packed_struct_fields('compute_enable_t', sources) + tuning_fields = packed_struct_fields('compute_tuning_t', sources) + if not ops_fields or not tuning_fields: + raise SystemExit('error: cannot read compute_enable_t/compute_tuning_t; ' + 'the compute sweep cannot be validated') + # Fields are declared MSB first, and mxfp16 only gates the FP16 paths of + # the MX ops, so it enables no datapath on its own. + real_ops = [f for f in ops_fields if f != 'mxfp16'] + real_mask = 0 + for pos, name in enumerate(reversed(ops_fields)): + if name in real_ops: + real_mask |= 1 << pos + for spec in computes: + fields = spec.split(':') + if len(fields) != 3: + raise SystemExit('error: --compute entry {} is not ' + 'WIDTH:OPS:TUNING'.format(spec)) + width, ops, tuning = fields + for name, value in (('WIDTH', width), ('OPS', ops), ('TUNING', tuning)): + if not value.isdigit(): + raise SystemExit('error: --compute entry {}: {} is not a ' + 'non-negative integer'.format(spec, name)) + if int(width) <= 0: + raise SystemExit('error: --compute entry {}: WIDTH must be ' + 'positive'.format(spec)) + if int(ops) >= 1 << len(ops_fields): + raise SystemExit('error: --compute entry {}: OPS {} does not fit ' + 'compute_enable_t ({} bits: {})'.format( + spec, ops, len(ops_fields), ', '.join(ops_fields))) + if int(tuning) >= 1 << len(tuning_fields): + raise SystemExit('error: --compute entry {}: TUNING {} does not fit ' + 'compute_tuning_t ({} bits: {})'.format( + spec, tuning, len(tuning_fields), + ', '.join(tuning_fields))) + if not int(ops) & real_mask: + raise SystemExit('error: --compute entry {}: OPS {} enables no compute ' + 'op ({}), so the datapath is never instantiated and ' + 'the configuration elaborates nothing'.format( + spec, ops, ', '.join(real_ops))) + cfg = dict(base) + if 'DataWidth' in declared: + cfg['DataWidth'] = width + cfg['EnableCompute'] = 1 + cfg['ComputeOps'] = ops + cfg['ComputeTuning'] = tuning + flags = ' '.join('-G{}={}'.format(k, v) for k, v in cfg.items()) + if flags in seen: + continue + seen.add(flags) + lines.append('compute{}_ops{}_t{} {}'.format(width, ops, tuning, flags)) + print('\n'.join(lines)) return 0 From ecdaebc84fd3386166a14fb9f1f35885ca501d46 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 15:01:10 +0200 Subject: [PATCH 08/24] ci: Trim the verification matrix comments --- .github/workflows/ci.yml | 4 +-- .github/workflows/lint.yml | 25 +------------ .github/workflows/verify.yml | 51 +-------------------------- test/frontend/tb_idma_reg_frontend.sv | 12 ++----- util/mario/tracer.py | 3 +- 5 files changed, 6 insertions(+), 89 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fff45e8b..c7ddf13a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -42,9 +42,7 @@ jobs: uses: ./.github/workflows/verify.yml secrets: inherit - # The licensed EDA pipeline runs only once the license-free matrix is green, - # so it confirms rather than being the only signal, and no licences are spent - # on a PR that already fails structurally. + # The licensed EDA pipeline runs only once the license-free matrix is green gitlab-ci: needs: [build, verify] uses: ./.github/workflows/gitlab-ci.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 69adf7d7..b62cfb77 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -33,8 +33,6 @@ jobs: target/sim/vsim/wave/tpl/*.do.tpl # Tree-scoped counterpart to lint-sv: that job reports through reviewdog with - # -diff, so it only sees lines a PR touched and pre-existing violations never - # fail it. This one checks all of src/ so the backlog stays at zero. lint-sv-tree: runs-on: ubuntu-latest steps: @@ -66,28 +64,7 @@ jobs: fail_on_error: true reviewdog_reporter: github-check -# lint-cxx: -# runs-on: ubuntu-latest -# steps: -# - -# name: Checkout -# uses: actions/checkout@v3 -# - -# name: Run Clang-format -# uses: DoozyX/clang-format-lint-action@v0.14 -# with: -# extensions: 'c,h,cpp' -# clangFormatVersion: 14 -# style: > -# { -# IndentWidth: 4, -# ColumnLimit: 100, -# AlignEscapedNewlines: DontAlign, -# SortIncludes: false, -# AllowShortFunctionsOnASingleLine: None, -# AllowShortIfStatementsOnASingleLine: true, -# AllowShortLoopsOnASingleLine: true -# } +# lint-cxx lint-python: runs-on: ubuntu-latest diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 6c883a07..94007e03 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -5,45 +5,7 @@ # Author: # - Daniel Keller -# License-free verification matrix. It runs on Verilator 5.020 (apt, ubuntu-24.04) -# and slang 11 (pyslang) only, so fork PRs - which never reach the proprietary EDA -# sims on the GitLab mirror - get a real gate, and the licensed run becomes a -# confirmation rather than the only signal. -# -# Two tiers: elaboration first, simulation behind it. elab-backend, elab-shared-tops -# and elab-tb-shared run in parallel and every simulation job `needs:` all three, so a -# port, parameter or connectivity break fails in seconds instead of burning every -# simulation runner. `fail-fast: false` stays on the matrices, so the elaboration tier -# still reports every leg. elab-multihead, codegen-consistency and report-slang-extra -# are parallel peers, not part of the barrier: they gate nothing the simulations use. -# -# House rules for this file, enforced in review: -# - no `|| true`, no `continue-on-error` on any step of a gating job; -# - no negated grep as a pass criterion; every simulation leg checks exit -# status AND a non-empty log AND a positive token (util/run_vlt_sim.py); -# - no numeric warning-count thresholds; -# - every excluded top or configuration is named below with its reason; -# - `needs:` orders jobs, it does not share workspaces; every job keeps its own -# `Generate RTL` step. -# -# NAMED EXCLUSIONS - dropped coverage, not silent skips: -# tb_idma_nd_midend_b2b hangs ([B2B] timeout); latent reset/LFSR race -# tb_idma_transpose_nd AW outside the destination allocation at 80 ns -# tb_idma_transpose_b2b stream_watchdog trip after 4000 idle cycles -# tb_idma_mxrand testbench timeout at 400 ms of simulated time -# tb_idma_mxperf hangs on the first compute transfer -# tb_idma_rt_midend fails under --assert on cc_rr_arb_tree lock -# mxquant/mxroundtrip at DataWidth 512 and 1024, and mxneg cases 4 and 12 -# Verilator 5.020 SIGSEGVs before time 0 (512 in the -# initial-block coroutine, 1024 in the model -# constructor); elaboration-only for now -# mxneg case 11 the request is never accepted under Verilator, so -# ComputeMxdequantLengthFits is never sampled -# the 88 directed job files -# Verilator cannot simulate the idma_test class-based -# driver (virtual interface, input combinational -# region does not converge) -# All of these need triage; none may be flagged away to make a leg green. +# License-free verification matrix: elaboration gates the simulation tier. name: verify @@ -54,7 +16,6 @@ on: jobs: # Codegen hygiene, not verification: jobs.json and the generated tree must - # still describe the same design, and a second generation must be identical. codegen-consistency: runs-on: ubuntu-24.04 steps: @@ -69,8 +30,6 @@ jobs: run: uv run --locked make idma_verify_codegen # Elaboration of one backend variant under both front ends, with the real - # per-variant jobs.json parameters plus a DataWidth sweep, and slang - # elaboration of that variant's testbench. elab-backend: strategy: fail-fast: false @@ -102,8 +61,6 @@ jobs: run: uv run --locked make idma_verify_backend IDMA_VERIFY_ID=${{ matrix.id }} # The four non-backend synthesis tops with their own jobs.json parameters, plus - # the default-parameter elaboration of all 12 tops and of the inst64 testbench, - # the only public concrete binding of idma_inst64_top. elab-shared-tops: runs-on: ubuntu-24.04 steps: @@ -123,8 +80,6 @@ jobs: run: uv run --locked make idma_verify_shared # Testbench tops that are not per-backend, including the three generated register - # frontends through tb_idma_reg_frontend. Verilator cannot parse the verification - # stack at all, so slang is the only public tool for this half of the repository. elab-tb-shared: runs-on: ubuntu-24.04 steps: @@ -158,7 +113,6 @@ jobs: run: uv run --locked make idma_verify_multihead # Real simulation, byte-exact against the DPI-C goldens. The destination is - # pre-poisoned, so a backend that writes nothing fails. sim-datapath-mxquant: needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 @@ -217,8 +171,6 @@ jobs: run: uv run --locked make idma_verify_sim_transpose IDMA_VLT_MAKEFLAGS=-j4 # Negative tests, inverted polarity: the run must exit non-zero AND name its - # guard. --assert is load-bearing; without it both testbenches exit 0 and the - # leg could not fail. sim-negative: needs: [elab-backend, elab-shared-tops, elab-tb-shared] strategy: @@ -258,7 +210,6 @@ jobs: IDMA_MXNEG_CASES="${{ matrix.cases }}" # A report, not a gate: -Wextra without -Werror, so findings do not fail the - # job and a real compile error still does. Never add it to the required checks. report-slang-extra: runs-on: ubuntu-24.04 steps: diff --git a/test/frontend/tb_idma_reg_frontend.sv b/test/frontend/tb_idma_reg_frontend.sv index 2912195a..4807c5c6 100644 --- a/test/frontend/tb_idma_reg_frontend.sv +++ b/test/frontend/tb_idma_reg_frontend.sv @@ -7,7 +7,6 @@ // Self-checking testbench for the iDMA register frontend (idma_reg32_3d, apb4-flat). // `RegVariant` also binds idma_reg64_2d and idma_reg64_1d for elaboration only; they -// have no stimulus here, and are covered by nothing else. // Drives the APB config slave with the standard apb_test::apb_driver against a // controllable backend stub and checks the non-blocking next_id launch contract: // the config read completes promptly (even under backend backpressure) and the @@ -23,9 +22,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( parameter int unsigned NumStreams = 32'd1, // number of config-bus ports (arbitrated by the reg frontend's rr_arb_tree) parameter int unsigned NumRegs = 32'd1, - // generated frontend under test: 0 = idma_reg32_3d, 1 = idma_reg64_2d, 2 = idma_reg64_1d. - // Only variant 0 is simulated; 1 and 2 are elaboration-only (their register maps differ - // and reg64_1d emits a flat 1D request), and the stimulus refuses to run on them. + // generated frontend under test: 0 = idma_reg32_3d, 1 = idma_reg64_2d, 2 = idma_reg64_1d parameter int unsigned RegVariant = 32'd0 ); @@ -39,9 +36,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( localparam int unsigned CfgDataWidth = 32'd32; localparam int unsigned CfgStrbWidth = CfgDataWidth / 32'd8; localparam int unsigned IdCounterWidth = 32'd32; - // idma data-path: reg32_3d is 32-bit over 3 ND dims, both reg64 variants are 64-bit. - // NumDim shapes the ND request type; reg64_1d takes the flat idma_req_t instead, so - // its ND type is unused and the 2-dim shape is kept only to keep the macro legal. + // idma data-path: reg32_3d is 32-bit over 3 ND dims, both reg64 variants are 64-bit localparam int unsigned AddrWidth = (RegVariant == 32'd0) ? 32'd32 : 32'd64; localparam int unsigned DataWidth = AddrWidth; localparam int unsigned NumDim = (RegVariant == 32'd0) ? 32'd3 : 32'd2; @@ -59,7 +54,6 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( localparam int unsigned DeadlockCycles = 32'd2000; // register map (idma_reg32_3d_addrmap_pkg): base + per-stream stride 0x4; the reg64 - // variants have their own map and are elaboration-only, so it is not modelled here localparam logic [31:0] REG_CONF = 32'h0000_0000; localparam logic [31:0] REG_STATUS0 = 32'h0000_0004; localparam logic [31:0] REG_NEXT_ID0 = 32'h0000_0044; @@ -189,8 +183,6 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // DUT // -------------------------------------------------------------------------- // All three generated frontends share the parameter and port list; only the module - // name and the emitted request type differ. reg64_1d (NumDim=1) emits a flat - // idma_req_t, which is widened back to the ND shape the stimulus works on. if (RegVariant == 32'd0) begin : gen_reg32_3d idma_reg32_3d #( .NumRegs ( NumRegs ), diff --git a/util/mario/tracer.py b/util/mario/tracer.py index e558b3f9..7250cfb5 100644 --- a/util/mario/tracer.py +++ b/util/mario/tracer.py @@ -37,7 +37,7 @@ def _flatten_dict(d, parent_key='', delimiter='_'): automatic `IDMA_TRACER_MAX_TYPE bus [string]; <%text>\\ automatic string trace; <%text>\\ `ifndef VERILATOR <%text>\\ - #0; <%text>\\ + # 0; <%text>\\ `endif <%text>\\ tf = $fopen(__out_f, "w"); <%text>\\ $display("[iDMA Tracer] Logging %s to %s", `"__backend_inst`", __out_f); <%text>\\ @@ -121,7 +121,6 @@ def render_tracer(prot_ids: dict, db: dict, tpl_file: str) -> str: signals = '' # Keys are direction-qualified: a protocol present on both sides (INIT) otherwise - # emits the same key twice and the write leg silently drops the read channel. for read_prot in prot_ids[prot_id]['ar']: sig_dict = _flatten_dict(db[read_prot]['trace_signals']['read']) for signal in sig_dict: From 933357189455320127ca2a8ac335577df564fdb1 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 15:44:07 +0200 Subject: [PATCH 09/24] test: Number the register frontend variants by their dimension count The index was arbitrary, so 0 meant the 3D variant; it now matches the dimension count in the module name and the selector comes before the parameters it selects between. --- idma.mk | 2 +- test/frontend/tb_idma_reg_frontend.sv | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/idma.mk b/idma.mk index 6bb2175c..f369b08a 100644 --- a/idma.mk +++ b/idma.mk @@ -738,7 +738,7 @@ IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi # port list, so tb_idma_reg_frontend - the only public concrete binding - elaborates # each of them; without this reg64_2d and reg64_1d are elaborated by nothing. Only # variant 0 is simulated; the testbench refuses to run on the other two. -IDMA_REG_VARIANTS ?= 0:idma_reg32_3d 1:idma_reg64_2d 2:idma_reg64_1d +IDMA_REG_VARIANTS ?= 3:idma_reg32_3d 2:idma_reg64_2d 1:idma_reg64_1d # Testbench tops that are not per-backend. tb_idma_backend_* are covered by the # matching idma_verify_backend leg; tb_idma_reg_frontend by IDMA_REG_VARIANTS. diff --git a/test/frontend/tb_idma_reg_frontend.sv b/test/frontend/tb_idma_reg_frontend.sv index 4807c5c6..377ca752 100644 --- a/test/frontend/tb_idma_reg_frontend.sv +++ b/test/frontend/tb_idma_reg_frontend.sv @@ -18,12 +18,12 @@ `include "idma/typedef.svh" module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( + // generated frontend under test, by ND dimensions: 3 = reg32_3d, 2 = reg64_2d, 1 = reg64_1d + parameter int unsigned RegVariant = 32'd3, // number of streams the elaborated DUT exposes (checked at instantiation) parameter int unsigned NumStreams = 32'd1, // number of config-bus ports (arbitrated by the reg frontend's rr_arb_tree) - parameter int unsigned NumRegs = 32'd1, - // generated frontend under test: 0 = idma_reg32_3d, 1 = idma_reg64_2d, 2 = idma_reg64_1d - parameter int unsigned RegVariant = 32'd0 + parameter int unsigned NumRegs = 32'd1 ); // -------------------------------------------------------------------------- @@ -37,9 +37,9 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( localparam int unsigned CfgStrbWidth = CfgDataWidth / 32'd8; localparam int unsigned IdCounterWidth = 32'd32; // idma data-path: reg32_3d is 32-bit over 3 ND dims, both reg64 variants are 64-bit - localparam int unsigned AddrWidth = (RegVariant == 32'd0) ? 32'd32 : 32'd64; + localparam int unsigned AddrWidth = (RegVariant == 32'd3) ? 32'd32 : 32'd64; localparam int unsigned DataWidth = AddrWidth; - localparam int unsigned NumDim = (RegVariant == 32'd0) ? 32'd3 : 32'd2; + localparam int unsigned NumDim = (RegVariant == 32'd3) ? 32'd3 : 32'd2; localparam int unsigned RepWidth = AddrWidth; // apb_driver framing: the blocking driver.read() spans SETUP + first-ACCESS-check + // trailing edge before returning, so a same-cycle (non-blocking) read takes this many @@ -183,7 +183,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // DUT // -------------------------------------------------------------------------- // All three generated frontends share the parameter and port list; only the module - if (RegVariant == 32'd0) begin : gen_reg32_3d + if (RegVariant == 32'd3) begin : gen_reg32_3d idma_reg32_3d #( .NumRegs ( NumRegs ), .NumStreams ( NumStreams ), @@ -205,7 +205,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( .busy_i ( busy ), .midend_busy_i ( midend_busy ) ); - end else if (RegVariant == 32'd1) begin : gen_reg64_2d + end else if (RegVariant == 32'd2) begin : gen_reg64_2d idma_reg64_2d #( .NumRegs ( NumRegs ), .NumStreams ( NumStreams ), @@ -227,7 +227,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( .busy_i ( busy ), .midend_busy_i ( midend_busy ) ); - end else if (RegVariant == 32'd2) begin : gen_reg64_1d + end else if (RegVariant == 32'd1) begin : gen_reg64_1d idma_req_t dut_req_1d; idma_reg64_1d #( @@ -479,7 +479,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( initial begin : test // the register map and request layout below model reg32_3d only - if (RegVariant != 32'd0) + if (RegVariant != 32'd3) $fatal(1, "[TB] RegVariant %0d is elaboration-only, it has no stimulus", RegVariant); errors = 0; checks = 0; From f96331f7f2c35e490726079d2dfc4bf996f62442 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 17:19:04 +0200 Subject: [PATCH 10/24] gen: Generate one tracer header per backend id The tracer covered every backend id in a single generated header, so its content depended on the id list rather than on its own inputs. That needed a recorded stamp to notice an id-list change, and the stamp made the tracked aggregates depend on a command-line variable: generating with IDMA_ADD_IDS and then without it left the tree in a state where the multihead build had already rewritten idma_generated.sv, which is how mxquant_32 came to segfault in a tree that had just run idma_verify_multihead. Each id now gets its own idma/tracer_.svh, a pure function of the id in the target name; idma/tracer.svh keeps only the id-independent helpers. The tracked aggregates concatenate the literal IDMA_TREE_IDS and out-of-tree ids collect into idma_generated_add.sv behind the new add_ids bender target, so no command-line variable can change what a tracked build contains. The stamp and its IDMA_FORCE phony are gone. Both contract changes are breaking for downstream and are in the CHANGELOG. --- Bender.yml | 9 ++ CHANGELOG.md | 8 ++ .../src/content/docs/architecture/backend.md | 4 +- idma.mk | 127 +++++++++++------- src/frontend/inst64/idma_inst64_top.sv | 5 +- src/include/idma/tpl/tracer.svh.tpl | 5 +- src/include/idma/tpl/tracer_id.svh.tpl | 14 ++ target/rtl/.gitignore | 1 - test/frontend/tb_idma_desc64_bench.sv | 2 +- test/midend/tb_idma_nd_midend.sv | 2 +- test/tpl/tb_idma_backend.sv.tpl | 2 +- util/gen_idma.py | 6 +- util/mario/tracer.py | 15 ++- 13 files changed, 142 insertions(+), 58 deletions(-) create mode 100644 src/include/idma/tpl/tracer_id.svh.tpl diff --git a/Bender.yml b/Bender.yml index b2b62ee9..8f72c4df 100644 --- a/Bender.yml +++ b/Bender.yml @@ -62,6 +62,11 @@ sources: files: - target/rtl/idma_generated.sv + # Out-of-tree IDMA_ADD_IDS variants; empty unless the tree was generated with them + - target: all(rtl,add_ids) + files: + - target/rtl/idma_generated_add.sv + # Midends - target: rtl files: @@ -146,6 +151,10 @@ sources: - test/tb_idma_mxperf.sv - test/tb_idma_mxclear.sv + - target: all(idma_test,add_ids) + files: + - target/rtl/tb_idma_generated_add.sv + # Tightly-coupled inst64 frontend testbenches (needs the snitch_cluster-gated RTL) - target: all(idma_test, snitch_cluster) files: diff --git a/CHANGELOG.md b/CHANGELOG.md index 799e6f78..bcf54064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed +- **Breaking**: the tracer is generated one header per backend id. `idma/tracer.svh` now only holds the id-independent helpers; the `IDMA_TRACER_` macro of a variant lives in `idma/tracer_.svh`, which includes `idma/tracer.svh` itself. Downstream users replace `` `include "idma/tracer.svh" `` with the per-id header of the variant they trace. +- **Breaking**: out-of-tree `IDMA_ADD_IDS` / `IDMA_ADD_FE_IDS` variants no longer land in `target/rtl/idma_generated.sv`. They are built by the new `idma_add_all` target into `target/rtl/idma_generated_add.sv` and `target/rtl/tb_idma_generated_add.sv`, and reach Bender through the new `add_ids` target; a build using `IDMA_ADD_IDS` has to select `-t add_ids`. The tracked aggregates now cover the literal `IDMA_TREE_IDS`, which is what lets make see an id-list change without a recorded stamp. +- **Breaking**: overriding `IDMA_BACKEND_IDS` or `IDMA_TREE_IDS` from the command line is no longer supported without a preceding `make idma_rtl_clean`; make cannot see a command-line id set shrink. `IDMA_ADD_IDS` is the supported extension knob. + + ## 0.6.5 - 2025-07-15 ### Added diff --git a/doc/site/src/content/docs/architecture/backend.md b/doc/site/src/content/docs/architecture/backend.md index ec9688cb..2c706205 100644 --- a/doc/site/src/content/docs/architecture/backend.md +++ b/doc/site/src/content/docs/architecture/backend.md @@ -90,7 +90,9 @@ Each backend variant combines a set of read and write protocols: | `r_axi_rw_init_rw_obi` | AXI4 | INIT + OBI | | `rw_axi_rw_init_rw_obi` | AXI4 | AXI4 + INIT + OBI | -The active set is `IDMA_BACKEND_IDS` in `idma.mk`; extend it with `IDMA_ADD_IDS` for custom protocol combinations. +The tracked set is `IDMA_TREE_IDS` in `idma.mk`; extend it with `IDMA_ADD_IDS` for custom protocol combinations. Add-id variants are generated by `make idma_hw_all idma_add_all IDMA_ADD_IDS="..."` into `target/rtl/idma_generated_add.sv` and reach Bender through the `add_ids` target. + +Each variant also gets its own tracer header, `idma/tracer_.svh`, holding the `IDMA_TRACER_` macro; `idma/tracer.svh` carries only the id-independent helpers and is included by the per-id headers. ![Variant Matrix](/iDMA/fig/variant_matrix.svg) diff --git a/idma.mk b/idma.mk index f369b08a..9fd30025 100644 --- a/idma.mk +++ b/idma.mk @@ -42,8 +42,13 @@ IDMA_OCCAMY_IDS := \ r_obi_rw_init_w_axi \ r_axi_rw_init_rw_obi \ rw_axi_rw_init_rw_obi +# The ids the tracked aggregates cover. Literal on purpose: growing or shrinking the +# tracked set is an edit to this file, which every aggregate depends on, so make sees +# it. Overriding it - or IDMA_BACKEND_IDS - from the command line is not supported +# without a preceding idma_rtl_clean; IDMA_ADD_IDS is the supported extension knob. +IDMA_TREE_IDS := $(IDMA_BASE_IDS) $(IDMA_OCCAMY_IDS) IDMA_ADD_IDS ?= -IDMA_BACKEND_IDS ?= $(IDMA_BASE_IDS) $(IDMA_OCCAMY_IDS) $(IDMA_ADD_IDS) +IDMA_BACKEND_IDS ?= $(IDMA_TREE_IDS) $(IDMA_ADD_IDS) # generated frontends IDMA_BASE_FE_IDS := reg32_3d reg64_2d reg64_1d @@ -87,6 +92,8 @@ IDMA_RTL_DOC_ALL := # Generated cumulative RTL files IDMA_FULL_RTL := $(IDMA_RTL_DIR)/idma_generated.sv IDMA_FULL_TB := $(IDMA_RTL_DIR)/tb_idma_generated.sv +IDMA_ADD_RTL := $(IDMA_RTL_DIR)/idma_generated_add.sv +IDMA_ADD_TB := $(IDMA_RTL_DIR)/tb_idma_generated_add.sv IDMA_GEN := $(IDMA_UTIL_DIR)/gen_idma.py IDMA_GEN_SRC := $(IDMA_UTIL_DIR)/mario/backend.py \ @@ -134,43 +141,44 @@ $(IDMA_RTL_DIR)/tb_idma_backend_%.sv: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_RTL_DIR $(IDMA_VSIM_DIR)/wave/backend_%.do: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_RTL_DIR)/tb_idma_backend_%.sv $(IDMA_VSIM_DIR)/wave/tpl/backend.do.tpl $(call idma_gen,vsim_wave,$(IDMA_VSIM_DIR)/wave/tpl/backend.do.tpl,$(IDMA_DB_FILES),$*,,$@) -# Stamp holding the id lists the tree was last generated with. The tracer covers -# every id in one file, so a change to IDMA_ADD_IDS must rebuild it; without the -# stamp an out-of-tree id list silently keeps the previous tracer. -IDMA_IDS_STAMP := $(IDMA_RTL_DIR)/.idma_ids +IDMA_INC_DIR := $(IDMA_RTL_DIR)/include/idma +IDMA_INC_TPL := $(IDMA_ROOT)/src/include/idma/tpl -.PHONY: IDMA_FORCE -IDMA_FORCE: +# The id-independent tracer helpers; a pure function of their own template +$(IDMA_INC_DIR)/tracer.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_INC_TPL)/tracer.svh.tpl + mkdir -p $(@D) + $(call idma_gen,tracer_common,$(IDMA_INC_TPL)/tracer.svh.tpl,,,,$@) -$(IDMA_IDS_STAMP): IDMA_FORCE - @mkdir -p $(IDMA_RTL_DIR) - @echo "$(IDMA_BACKEND_IDS)|$(IDMA_FE_IDS)" | cmp -s - $@ || \ - echo "$(IDMA_BACKEND_IDS)|$(IDMA_FE_IDS)" > $@ +# One tracer macro per backend id. The content is a function of the id in the target +# name, the databases and the template only, so "up to date" implies "correct" and no +# id-list state has to be recorded anywhere. +$(IDMA_INC_DIR)/tracer_%.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_INC_TPL)/tracer_id.svh.tpl $(IDMA_DB_FILES) + mkdir -p $(@D) + $(call idma_gen,tracer,$(IDMA_INC_TPL)/tracer_id.svh.tpl,$(IDMA_DB_FILES),$*,,$@) -$(IDMA_RTL_DIR)/include/idma/tracer.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_ROOT)/src/include/idma/tpl/tracer.svh.tpl $(IDMA_DB_FILES) $(IDMA_ROOT)/idma.mk $(IDMA_IDS_STAMP) - mkdir -p $(IDMA_RTL_DIR)/include/idma - $(call idma_gen,tracer,$(IDMA_ROOT)/src/include/idma/tpl/tracer.svh.tpl,$(IDMA_DB_FILES),$(IDMA_BACKEND_IDS),$(IDMA_FE_IDS),$@) - -$(IDMA_RTL_DIR)/include/idma/compute.svh: $(IDMA_ROOT)/src/frontend/reg/tpl/compute.svh.tpl $(IDMA_ROOT)/src/frontend/reg/idma_reg.rdl - mkdir -p $(IDMA_RTL_DIR)/include/idma +$(IDMA_INC_DIR)/compute.svh: $(IDMA_ROOT)/src/frontend/reg/tpl/compute.svh.tpl $(IDMA_ROOT)/src/frontend/reg/idma_reg.rdl + mkdir -p $(IDMA_INC_DIR) $(PEAKRDL) raw-header $(IDMA_ROOT)/src/frontend/reg/idma_reg.rdl \ --template $(IDMA_ROOT)/src/frontend/reg/tpl/compute.svh.tpl -o $@ idma_rtl_clean: - rm -f $(IDMA_IDS_STAMP) rm -f $(IDMA_RTL_DIR)/Bender.yml rm -f $(IDMA_RTL_DIR)/*.sv rm -f $(IDMA_VSIM_DIR)/wave/*.do - rm -f $(IDMA_RTL_DIR)/include/idma/tracer.svh - rm -f $(IDMA_RTL_DIR)/include/idma/compute.svh - rm -rf $(IDMA_RTL_DIR)/include/idma + rm -rf $(IDMA_INC_DIR) # assemble the required files -IDMA_INCLUDE_ALL += $(IDMA_RTL_DIR)/include/idma/tracer.svh -IDMA_INCLUDE_ALL += $(IDMA_RTL_DIR)/include/idma/compute.svh -IDMA_RTL_ALL += $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_BACKEND_IDS),$X_$Y.sv)) -IDMA_TB_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) -IDMA_WAVE_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_VSIM_DIR)/wave/backend_$Y.do) +IDMA_INCLUDE_ALL += $(IDMA_INC_DIR)/tracer.svh +IDMA_INCLUDE_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_INC_DIR)/tracer_$Y.svh) +IDMA_INCLUDE_ALL += $(IDMA_INC_DIR)/compute.svh + +# The tracked aggregates below concatenate the tree ids only; out-of-tree ids are +# collected separately so no command-line variable can change what they contain. +IDMA_TREE_RTL_ALL := $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_TREE_IDS),$X_$Y.sv)) +IDMA_ADD_RTL_ALL := $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_ADD_IDS),$X_$Y.sv)) +IDMA_TREE_TB_ALL := $(foreach Y,$(IDMA_TREE_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) +IDMA_ADD_TB_ALL := $(foreach Y,$(IDMA_ADD_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) +IDMA_WAVE_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_VSIM_DIR)/wave/backend_$Y.do) # -------------- @@ -183,8 +191,9 @@ IDMA_DOC_FIG_DIR := $(IDMA_ROOT)/doc/fig IDMA_DOC_OUT_DIR := $(IDMA_ROOT)/target/doc IDMA_HTML_DIR := $(IDMA_DOC_OUT_DIR)/html IDMA_FE_DIR := $(IDMA_ROOT)/src/frontend -IDMA_FE_REGS := desc64 -IDMA_FE_REGS += $(IDMA_FE_IDS) +IDMA_FE_REGS := desc64 +IDMA_FE_REGS += $(IDMA_FE_IDS) +IDMA_TREE_FE_REGS := desc64 $(IDMA_BASE_FE_IDS) # Config-bus CPUIF for the register frontend: PeakRDL regblock --cpuif + matching wrapper # packing. apb4-flat (default, industry standard); also obi-flat / passthrough / axi4-lite-flat. @@ -283,11 +292,15 @@ idma_reg_clean: rm -f $(IDMA_REG_CUST_ALL) # assemble the required files -IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) -IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_top.sv) -IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_addrmap_pkg.sv) -IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_top.sv) -IDMA_RTL_DOC_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_HTML_DIR)/regs/idma_$Y_reg/index.html) +IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) +IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_top.sv) +IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_addrmap_pkg.sv) +IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_top.sv) +IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) +IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_reg_top.sv) +IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_addrmap_pkg.sv) +IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_top.sv) +IDMA_RTL_DOC_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_HTML_DIR)/regs/idma_$Y_reg/index.html) # C headers IDMA_SW_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_SW_DIR)/idma_$Y_regs.h) @@ -300,13 +313,30 @@ IDMA_SW_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_SW_DIR)/idma_$Y_raw_regs. # RTL assembly # --------------- -# The stamp catches an id-list change: both files concatenate a per-id set, so a -# shorter list leaves the previous variants in place without it. -$(IDMA_FULL_RTL): $(IDMA_RTL_ALL) $(IDMA_IDS_STAMP) - $(CAT) $(IDMA_RTL_ALL) > $@ +IDMA_RTL_ALL += $(IDMA_TREE_RTL_ALL) $(IDMA_ADD_RTL_ALL) +IDMA_TB_ALL += $(IDMA_TREE_TB_ALL) $(IDMA_ADD_TB_ALL) + +# Bender hardcodes the two names below, so both files stay a cat of a per-id set. A +# set that shrinks is invisible to make - every remaining part is older than the +# target - hence the tracked aggregates take their id list from the literal +# IDMA_TREE_IDS and depend on the file that spells it out. +$(IDMA_FULL_RTL): $(IDMA_TREE_RTL_ALL) $(IDMA_ROOT)/idma.mk + $(CAT) $(IDMA_TREE_RTL_ALL) > $@ -$(IDMA_FULL_TB): $(IDMA_TB_ALL) $(IDMA_IDS_STAMP) - $(CAT) $(IDMA_TB_ALL) > $@ +$(IDMA_FULL_TB): $(IDMA_TREE_TB_ALL) $(IDMA_ROOT)/idma.mk + $(CAT) $(IDMA_TREE_TB_ALL) > $@ + +# Out-of-tree IDMA_ADD_IDS variants, reached through the add_ids bender target only. +# Not part of idma_hw_all: IDMA_ADD_IDS is a command-line knob, so make cannot see +# its set shrink and this pair carries the one staleness make cannot express. Ask for +# it explicitly (idma_add_all) and it is exact; a set that shrinks between two such +# builds leaves the dropped variant behind until idma_rtl_clean. Nothing a tracked +# build parses can reach these files. +$(IDMA_ADD_RTL): $(IDMA_ADD_RTL_ALL) $(IDMA_ROOT)/idma.mk + $(CAT) /dev/null $(IDMA_ADD_RTL_ALL) > $@ + +$(IDMA_ADD_TB): $(IDMA_ADD_TB_ALL) $(IDMA_ROOT)/idma.mk + $(CAT) /dev/null $(IDMA_ADD_TB_ALL) > $@ # --------------- @@ -850,11 +880,12 @@ idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f # Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the # two extra synthesis tops and the two multi-head testbenches idma_verify_multihead: - $(MAKE) idma_hw_all IDMA_ADD_IDS="$(IDMA_MULTIHEAD_IDS)" + $(MAKE) idma_hw_all idma_add_all IDMA_ADD_IDS="$(IDMA_MULTIHEAD_IDS)" mkdir -p $(IDMA_VLT_DIR) $(IDMA_SLANG_DIR) - $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) > $(IDMA_VLT_DIR)/idma_multihead.f - $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) > $(IDMA_SLANG_DIR)/mh_synth.f - $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead > $(IDMA_SLANG_DIR)/mh_tb.f + $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_VLT_DIR)/idma_multihead.f + $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_SLANG_DIR)/mh_synth.f + $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead -t add_ids \ + > $(IDMA_SLANG_DIR)/mh_tb.f set -e; for id in $(IDMA_MULTIHEAD_IDS); do \ echo "--- verilator idma_backend_synth_$$id ---"; \ $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_multihead.f \ @@ -868,7 +899,7 @@ idma_verify_multihead: $(SLANG) -f $(IDMA_SLANG_DIR)/mh_tb.f --top $$tb \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done - # leave the tree on the default id list so a following target is not surprised + # the tracked aggregates never saw the extra ids, so this is a no-op assertion $(MAKE) idma_hw_all @@ -1101,7 +1132,7 @@ idma_sw_clean: # Phony Targets # -------------- -.PHONY: idma_all idma_doc_all idma_pickle_all idma_rtl_all idma_sim_all +.PHONY: idma_all idma_add_all idma_doc_all idma_pickle_all idma_rtl_all idma_sim_all # Build the Starlight/Astro site (output in doc/site/dist) after staging the graphs idma_doc_all: idma_doc_site @@ -1109,7 +1140,11 @@ idma_doc_all: idma_doc_site idma_pickle_all: $(IDMA_PICKLE_ALL) -idma_hw_all: $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) $(IDMA_FULL_TB) $(IDMA_HJSON_ALL) $(IDMA_WAVE_ALL) +idma_hw_all: $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) $(IDMA_FULL_TB) $(IDMA_HJSON_ALL) \ + $(IDMA_WAVE_ALL) + +# The IDMA_ADD_IDS aggregates; ask for it next to idma_hw_all on an add-id build +idma_add_all: $(IDMA_ADD_RTL) $(IDMA_ADD_TB) idma_sw_all: $(IDMA_SW_ALL) diff --git a/src/frontend/inst64/idma_inst64_top.sv b/src/frontend/inst64/idma_inst64_top.sv index 79b37fff..7702f3df 100644 --- a/src/frontend/inst64/idma_inst64_top.sv +++ b/src/frontend/inst64/idma_inst64_top.sv @@ -8,7 +8,10 @@ `include "common_cells/registers.svh" `include "common_cells/assertions.svh" `include "idma/typedef.svh" -`include "idma/tracer.svh" +// the tracer macro is only expanded below the `ifndef SYNTHESIS guard +`ifndef SYNTHESIS +`include "idma/tracer_rw_axi.svh" +`endif /// Implements the tightly-coupled frontend. This module can directly be connected /// to an accelerator bus in the snitch system diff --git a/src/include/idma/tpl/tracer.svh.tpl b/src/include/idma/tpl/tracer.svh.tpl index 604b20c4..40f85f12 100644 --- a/src/include/idma/tpl/tracer.svh.tpl +++ b/src/include/idma/tpl/tracer.svh.tpl @@ -5,7 +5,8 @@ // Authors: // - Thomas Benz -// Macro holding all the resources for the iDMA backend tracer +// Shared resources of the iDMA backend tracer; the per-id macros live in +// idma/tracer_.svh and include this file themselves `ifndef IDMA_TRACER_SVH_ `define IDMA_TRACER_SVH_ @@ -26,5 +27,5 @@ if(__cond) begin <%text>\ __cond = ~__cond; <%text>\ end -${body} + `endif diff --git a/src/include/idma/tpl/tracer_id.svh.tpl b/src/include/idma/tpl/tracer_id.svh.tpl new file mode 100644 index 00000000..8fb9845b --- /dev/null +++ b/src/include/idma/tpl/tracer_id.svh.tpl @@ -0,0 +1,14 @@ +// Copyright 2023 ETH Zurich and University of Bologna. +// Solderpad Hardware License, Version 0.51, see LICENSE for details. +// SPDX-License-Identifier: SHL-0.51 + +// Authors: +// - Thomas Benz + +// Tracer macro of the ${identifier} iDMA backend +`ifndef IDMA_TRACER_${identifier_cap}_SVH_ +`define IDMA_TRACER_${identifier_cap}_SVH_ + +`include "idma/tracer.svh" +${body} +`endif diff --git a/target/rtl/.gitignore b/target/rtl/.gitignore index bfb73aff..9f111a44 100644 --- a/target/rtl/.gitignore +++ b/target/rtl/.gitignore @@ -4,4 +4,3 @@ include/* *.sv *.hjson .vidma_ids -.idma_ids diff --git a/test/frontend/tb_idma_desc64_bench.sv b/test/frontend/tb_idma_desc64_bench.sv index 2df3b601..442bc4d3 100644 --- a/test/frontend/tb_idma_desc64_bench.sv +++ b/test/frontend/tb_idma_desc64_bench.sv @@ -7,7 +7,7 @@ `include "apb/typedef.svh" `include "apb/assign.svh" -`include "idma/tracer.svh" +`include "idma/tracer_rw_axi.svh" `include "idma/typedef.svh" `include "axi/typedef.svh" `include "axi/assign.svh" diff --git a/test/midend/tb_idma_nd_midend.sv b/test/midend/tb_idma_nd_midend.sv index cefcc935..e0e1a240 100644 --- a/test/midend/tb_idma_nd_midend.sv +++ b/test/midend/tb_idma_nd_midend.sv @@ -8,7 +8,7 @@ `timescale 1ns/1ns `include "axi/typedef.svh" -`include "idma/tracer.svh" +`include "idma/tracer_rw_axi.svh" `include "idma/typedef.svh" // Protocol testbench defines diff --git a/test/tpl/tb_idma_backend.sv.tpl b/test/tpl/tb_idma_backend.sv.tpl index 279ed81d..296911bd 100644 --- a/test/tpl/tb_idma_backend.sv.tpl +++ b/test/tpl/tb_idma_backend.sv.tpl @@ -9,7 +9,7 @@ `timescale 1ns/1ns `include "axi/typedef.svh" `include "axi_stream/typedef.svh" -`include "idma/tracer.svh" +`include "idma/tracer_${name_uniqueifier}.svh" `include "idma/typedef.svh" `include "obi/typedef.svh" `include "tilelink/typedef.svh" diff --git a/util/gen_idma.py b/util/gen_idma.py index bf3d711b..18bb9ce5 100644 --- a/util/gen_idma.py +++ b/util/gen_idma.py @@ -21,10 +21,10 @@ from mario.synth import render_synth_wrapper from mario.testbench import render_testbench from mario.frontend import render_reg_hjson, render_reg_top -from mario.tracer import render_tracer +from mario.tracer import render_tracer, render_tracer_common GENABLE_ENTITIES = ['transport', 'legalizer', 'backend', 'vsim_wave', 'testbench', 'synth_wrapper', - 'reg_top', 'reg_hjson', 'tracer'] + 'reg_top', 'reg_hjson', 'tracer', 'tracer_common'] EPILOG = ''' The iDMA configuration ID is composed of a underscore-separated list of specifiers and protocols. @@ -75,6 +75,8 @@ def main(): print(render_reg_top(frontend_ids, args.tpl, args.cpuif)) elif args.entity == 'tracer': print(render_tracer(protocol_ids, protocol_db, args.tpl)) + elif args.entity == 'tracer_common': + print(render_tracer_common(args.tpl)) else: return 1 diff --git a/util/mario/tracer.py b/util/mario/tracer.py index 7250cfb5..ff9a2cfa 100644 --- a/util/mario/tracer.py +++ b/util/mario/tracer.py @@ -107,14 +107,23 @@ def _flatten_dict(d, parent_key='', delimiter='_'): ''' +def render_tracer_common(tpl_file: str) -> str: + """Generate the id-independent tracer helpers""" + with open(tpl_file, 'r', encoding='utf-8') as templ_file: + return Template(templ_file.read()).render() + + def render_tracer(prot_ids: dict, db: dict, tpl_file: str) -> str: - """Generate racer""" + """Generate the tracer of one backend id""" tracer_body = '' + # one header per id: the header name carries the id, so a list is meaningless here + if len(prot_ids) != 1: + raise ValueError(f'the tracer renders exactly one id, got {len(prot_ids)}') + with open(tpl_file, 'r', encoding='utf-8') as templ_file: tracer_tpl = templ_file.read() - # render for every is for prot_id in prot_ids: # signals @@ -149,6 +158,8 @@ def render_tracer(prot_ids: dict, db: dict, tpl_file: str) -> str: # render tracer context context = { + 'identifier': prot_id, + 'identifier_cap': prot_id.upper(), 'body': tracer_body } From 65dba812a272066bbd24e97c05fad446a54bc474 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 17:53:18 +0200 Subject: [PATCH 11/24] ci: Close the review gaps in the verification matrix Rewrite the twelve comments the previous trim left as sentence fragments, and restore the named-exclusion inventory: it records dropped coverage, so deleting it turned documented gaps into silent skips. Give run_vlt_sim.py a run and build timeout. Several testbenches here hang rather than fail, and a hang was reported as neither: it pinned a runner until the job limit. A timed-out leg now fails even when it is a negative test, since a hang is not a guard firing. Teach check_jobs.py that a negative-test case the testbench defines must be run or named as skipped, and that a legalizer compute guard must be proven to fire by some case or named as untested. Both lists move out of prose and into idma.mk. This immediately found ComputeDstTilelink, ComputeMxFp16Width and ComputeMxdequantLengthFits with no firing test at all. --- .github/workflows/lint.yml | 5 +-- .github/workflows/verify.yml | 48 ++++++++++++++++++--- idma.mk | 18 ++++++-- src/include/idma/tpl/tracer.svh.tpl | 1 - test/frontend/idma_inst64_drv_if.sv | 4 +- test/frontend/tb_idma_reg_frontend.sv | 8 +++- util/check_jobs.py | 62 ++++++++++++++++++++++++++- util/mario/tracer.py | 5 ++- util/run_vlt_sim.py | 60 ++++++++++++++++++++------ 9 files changed, 178 insertions(+), 33 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b62cfb77..e52c4e8b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,7 +32,8 @@ jobs: exclude_paths: | target/sim/vsim/wave/tpl/*.do.tpl - # Tree-scoped counterpart to lint-sv: that job reports through reviewdog with + # Tree-scoped counterpart to lint-sv: that job reports through reviewdog with -diff, so + # it only sees lines a PR touched. This one checks all of src/ so the backlog stays at 0 lint-sv-tree: runs-on: ubuntu-latest steps: @@ -64,8 +65,6 @@ jobs: fail_on_error: true reviewdog_reporter: github-check -# lint-cxx - lint-python: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 94007e03..2bb5b7b9 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -6,6 +6,33 @@ # - Daniel Keller # License-free verification matrix: elaboration gates the simulation tier. +# +# House rules, enforced in review: +# - no `|| true` and no `continue-on-error` on any step of a gating job; +# - no negated grep as a pass criterion; every simulation leg checks exit status +# AND a non-empty log AND a positive token (util/run_vlt_sim.py); +# - no numeric warning-count thresholds; +# - every excluded top or configuration is named below with its reason; +# - `needs:` orders jobs, it does not share workspaces; every job regenerates. +# +# NAMED EXCLUSIONS - dropped coverage, not silent skips. All need triage; none may +# be flagged away to make a leg green. +# tb_idma_nd_midend_b2b hangs ([B2B] timeout); latent reset/LFSR race +# tb_idma_transpose_nd AW outside the destination allocation at 80 ns +# tb_idma_transpose_b2b stream_watchdog trip after 4000 idle cycles +# tb_idma_mxrand testbench timeout at 400 ms of simulated time +# tb_idma_mxperf hangs on the first compute transfer +# tb_idma_rt_midend fails under --assert on cc_rr_arb_tree lock +# mxquant/mxroundtrip at DataWidth 512 and 1024 +# verilator 5.020 SIGSEGVs before time 0 (512 in the +# initial-block coroutine, 1024 in the model +# constructor); elaboration-only for now +# the 88 directed job files +# verilator cannot simulate the idma_test class-based +# driver (virtual interface, input combinational region +# does not converge) +# The excluded mxneg cases and legalizer guards are named in idma.mk, next to the +# table that drives them, and util/check_jobs.py fails if that list drifts. name: verify @@ -15,7 +42,8 @@ on: jobs: - # Codegen hygiene, not verification: jobs.json and the generated tree must + # Codegen hygiene, not verification: jobs.json and the generated tree must still + # describe the same design, and a second generation must be identical codegen-consistency: runs-on: ubuntu-24.04 steps: @@ -29,7 +57,8 @@ jobs: name: Check jobs.json, the CI matrix and codegen determinism run: uv run --locked make idma_verify_codegen - # Elaboration of one backend variant under both front ends, with the real + # One backend variant under both front ends, with its real jobs.json parameters, + # a DataWidth sweep and slang elaboration of that variant's testbench elab-backend: strategy: fail-fast: false @@ -60,7 +89,8 @@ jobs: name: Elaborate ${{ matrix.id }} run: uv run --locked make idma_verify_backend IDMA_VERIFY_ID=${{ matrix.id }} - # The four non-backend synthesis tops with their own jobs.json parameters, plus + # The four non-backend synthesis tops with their own jobs.json parameters, plus the + # default-parameter elaboration of all 12 tops and of the inst64 testbench elab-shared-tops: runs-on: ubuntu-24.04 steps: @@ -80,6 +110,7 @@ jobs: run: uv run --locked make idma_verify_shared # Testbench tops that are not per-backend, including the three generated register + # frontends; verilator cannot parse the verification stack, so slang is the only tool elab-tb-shared: runs-on: ubuntu-24.04 steps: @@ -96,7 +127,7 @@ jobs: name: Elaborate the shared testbenches run: uv run --locked make idma_verify_tb_shared - # Out-of-tree multi-head build; license-free but generated only on request. + # Out-of-tree multi-head build; license-free but generated only on request elab-multihead: runs-on: ubuntu-24.04 steps: @@ -112,7 +143,8 @@ jobs: name: Elaborate the multi-head variants and testbenches run: uv run --locked make idma_verify_multihead - # Real simulation, byte-exact against the DPI-C goldens. The destination is + # Real simulation, byte-exact against the DPI-C goldens; the destination is + # pre-poisoned, so a backend that writes nothing fails sim-datapath-mxquant: needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 @@ -170,7 +202,8 @@ jobs: name: Simulate the transpose datapath, plain and backpressured run: uv run --locked make idma_verify_sim_transpose IDMA_VLT_MAKEFLAGS=-j4 - # Negative tests, inverted polarity: the run must exit non-zero AND name its + # Negative tests, inverted polarity: the run must exit non-zero AND name its guard. + # --assert is load-bearing; without it both testbenches exit 0 and cannot fail sim-negative: needs: [elab-backend, elab-shared-tops, elab-tb-shared] strategy: @@ -209,7 +242,8 @@ jobs: uv run --locked make idma_verify_sim_mxneg IDMA_VLT_MAKEFLAGS=-j4 \ IDMA_MXNEG_CASES="${{ matrix.cases }}" - # A report, not a gate: -Wextra without -Werror, so findings do not fail the + # A report, not a gate: -Wextra without -Werror, so findings do not fail the job and + # a real compile error still does. Never add it to the required checks report-slang-extra: runs-on: ubuntu-24.04 steps: diff --git a/idma.mk b/idma.mk index 9fd30025..535fcdfc 100644 --- a/idma.mk +++ b/idma.mk @@ -917,9 +917,6 @@ IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" IDMA_VLT_MAKEFLAGS="$(IDMA_VLT_MA IDMA_MX_WIDTHS ?= 32 64 256 # case:guard:DataWidth:EnDequant:EnFp16 -# Not gated, named rather than skipped: cases 4 and 12 need DataWidth=1024, where -# verilator 5.020 SIGSEGVs in the model constructor; case 11's request is never -# accepted under verilator, so ComputeMxdequantLengthFits is never sampled. IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ 2:ComputeSrcAligned:64:1:1 \ 3:ComputeDstAligned:64:1:1 \ @@ -931,6 +928,16 @@ IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ 13:ComputeOpUnsupported:64:1:0 IDMA_MXNEG_CASES ?= 1 2 3 5 6 7 8 10 13 +# Named rather than skipped, and checked by check_jobs.py: a case the testbench +# defines must be here or in the table, and a legalizer guard must be proven to +# fire by some case or be named here. case:reason, no spaces in the reason. +IDMA_MXNEG_SKIP := 4:needs-DataWidth-1024-verilator-SIGSEGV \ + 11:request-never-accepted-guard-never-sampled \ + 12:needs-DataWidth-1024-verilator-SIGSEGV +IDMA_MXNEG_GUARD_SKIP := ComputeMxFp16Width:only-reachable-from-the-DataWidth-1024-cases \ + ComputeMxdequantLengthFits:case-11-never-accepted \ + ComputeDstTilelink:no-tilelink-backend-variant-exists + $(IDMA_VLT_DIR)/%.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) mkdir -p $(IDMA_VLT_DIR) $(BENDER) script verilator $(IDMA_VLT_SIM_T) --top $* > $@ @@ -1018,6 +1025,11 @@ idma_verify_codegen: $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --jobs-dir $(IDMA_ROOT)/jobs \ --matrix-file $(IDMA_MATRIX_FILE) --mxneg-cases "$(IDMA_MXNEG_CASES)" \ + --mxneg-tb $(IDMA_ROOT)/test/tb_idma_mxneg.sv \ + --mxneg-skip "$(IDMA_MXNEG_SKIP)" \ + --mxneg-guard-src $(IDMA_ROOT)/src/backend/tpl/idma_legalizer.sv.tpl \ + --mxneg-guard-tested "$(IDMA_MXNEG_TABLE)" \ + --mxneg-guard-skip "$(IDMA_MXNEG_GUARD_SKIP)" \ $(IDMA_SOURCE_GLOBS) mkdir -p $(IDMA_VERIFY_DIR) md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 diff --git a/src/include/idma/tpl/tracer.svh.tpl b/src/include/idma/tpl/tracer.svh.tpl index 40f85f12..2ee37135 100644 --- a/src/include/idma/tpl/tracer.svh.tpl +++ b/src/include/idma/tpl/tracer.svh.tpl @@ -27,5 +27,4 @@ if(__cond) begin <%text>\ __cond = ~__cond; <%text>\ end - `endif diff --git a/test/frontend/idma_inst64_drv_if.sv b/test/frontend/idma_inst64_drv_if.sv index fd641de7..3a645cf0 100644 --- a/test/frontend/idma_inst64_drv_if.sv +++ b/test/frontend/idma_inst64_drv_if.sv @@ -67,10 +67,10 @@ interface idma_inst64_drv_if #( // it pops on the posedge, so sample it in a clocked process instead of reading it // combinationally from a task. acc_rsp_item_t rsp_queue [$]; - // built in a variable first: verilator rejects an assignment pattern as a call argument - acc_rsp_item_t rsp_item; always_ff @(posedge clk) begin : proc_capture_rsp + // built in a variable first: verilator rejects an assignment pattern as an argument + automatic acc_rsp_item_t rsp_item; if (rst_n && acc_res_valid && acc_res_ready) begin rsp_item = '{id: acc_res.id, data: acc_res.data, error: acc_res.error}; rsp_queue.push_back(rsp_item); diff --git a/test/frontend/tb_idma_reg_frontend.sv b/test/frontend/tb_idma_reg_frontend.sv index 377ca752..350986de 100644 --- a/test/frontend/tb_idma_reg_frontend.sv +++ b/test/frontend/tb_idma_reg_frontend.sv @@ -6,7 +6,8 @@ // - Daniel Keller // Self-checking testbench for the iDMA register frontend (idma_reg32_3d, apb4-flat). -// `RegVariant` also binds idma_reg64_2d and idma_reg64_1d for elaboration only; they +// `RegVariant` also binds idma_reg64_2d and idma_reg64_1d, elaboration-only and +// covered by nothing else. // Drives the APB config slave with the standard apb_test::apb_driver against a // controllable backend stub and checks the non-blocking next_id launch contract: // the config read completes promptly (even under backend backpressure) and the @@ -54,6 +55,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( localparam int unsigned DeadlockCycles = 32'd2000; // register map (idma_reg32_3d_addrmap_pkg): base + per-stream stride 0x4; the reg64 + // variants have their own map and are elaboration-only, so it is not modelled here localparam logic [31:0] REG_CONF = 32'h0000_0000; localparam logic [31:0] REG_STATUS0 = 32'h0000_0004; localparam logic [31:0] REG_NEXT_ID0 = 32'h0000_0044; @@ -182,7 +184,9 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // -------------------------------------------------------------------------- // DUT // -------------------------------------------------------------------------- - // All three generated frontends share the parameter and port list; only the module + // All three generated frontends share the parameter and port list; only the module name + // and the request type differ. reg64_1d (NumDim=1) emits a flat idma_req_t, widened + // back to the ND shape the stimulus works on if (RegVariant == 32'd3) begin : gen_reg32_3d idma_reg32_3d #( .NumRegs ( NumRegs ), diff --git a/util/check_jobs.py b/util/check_jobs.py index 661609b5..1294c242 100644 --- a/util/check_jobs.py +++ b/util/check_jobs.py @@ -12,7 +12,12 @@ a) every backend id has a jobs.json entry, b) every job path referenced by jobs.json exists, - c) every testbench and synth_top named by jobs.json exists in the sources. + c) every testbench and synth_top named by jobs.json exists in the sources, + d) the CI matrix fans out over every backend id and negative-test case, + e) every negative-test case the testbench defines is either run or named as + skipped, and every legalizer compute guard is either proven to fire by some + case or named as untested. Without (e) a new case or a new guard is added to + the design and silently exercised by nothing. The run list is always taken from jobs.json, never from `ls jobs/*`: four error_*.txt files exist on disk for variants built with ErrorHandling=0 and @@ -57,6 +62,16 @@ def main(): help='CI workflow that must fan out over every backend id') par.add_argument('--mxneg-cases', default='', help='negative-test cases the workflow legs must request between them') + par.add_argument('--mxneg-tb', default=None, + help='negative-test testbench; its case labels must all be accounted for') + par.add_argument('--mxneg-skip', default='', + help='cases deliberately not run, as case:reason') + par.add_argument('--mxneg-guard-src', default=None, + help='source declaring the compute guards, e.g. the legalizer template') + par.add_argument('--mxneg-guard-tested', default='', + help='IDMA_MXNEG_TABLE entries, as case:guard:...') + par.add_argument('--mxneg-guard-skip', default='', + help='guards with no firing test, as guard:reason') args = par.parse_args() patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] @@ -121,6 +136,51 @@ def main(): errors.append('{} requests no negative-test case {}'.format( args.matrix_file, case)) + # (e) no negative-test case and no compute guard may go silently unexercised + def tagged(spec): + """Parse a 'key:reason' or 'case:guard:...' list into {key: rest}.""" + out = {} + for entry in spec.split(): + key, _, rest = entry.partition(':') + out[key] = rest + return out + + if args.mxneg_tb: + with open(args.mxneg_tb, 'r', errors='replace') as handle: + tb_text = handle.read() + body = re.search(r'\bcase\s*\(\s*NegCase\s*\)(.*?)\bendcase', tb_text, re.S) + if not body: + errors.append('{}: no case (NegCase) block found'.format(args.mxneg_tb)) + else: + defined = set(re.findall(r'^\s*(\d+)\s*:', body.group(1), re.M)) + run_cases = set(args.mxneg_cases.split()) + skipped = tagged(args.mxneg_skip) + for case in sorted(defined - run_cases - set(skipped), key=int): + errors.append('{}: case {} is defined but neither run nor named as ' + 'skipped'.format(args.mxneg_tb, case)) + for case in sorted(set(skipped) - defined, key=int): + errors.append('case {} is named as skipped but the testbench does not ' + 'define it'.format(case)) + for case in sorted(run_cases - defined, key=int): + errors.append('case {} is run but the testbench does not define it'.format(case)) + + if args.mxneg_guard_src: + with open(args.mxneg_guard_src, 'r', errors='replace') as handle: + guard_text = handle.read() + declared = set(re.findall(r'`ASSERT_NEVER\(\s*(Compute\w+)', guard_text)) + tested = {entry.split(':')[1] for entry in args.mxneg_guard_tested.split() + if len(entry.split(':')) > 1} + guard_skip = tagged(args.mxneg_guard_skip) + for guard in sorted(declared - tested - set(guard_skip)): + errors.append('{}: guard {} has no negative test and is not named as ' + 'untested'.format(args.mxneg_guard_src, guard)) + for guard in sorted(set(guard_skip) - declared): + errors.append('guard {} is named as untested but is not declared in {}'.format( + guard, args.mxneg_guard_src)) + for guard in sorted(tested - declared): + errors.append('guard {} is claimed by the case table but is not declared in ' + '{}'.format(guard, args.mxneg_guard_src)) + for message in errors: print('error: ' + message) if errors: diff --git a/util/mario/tracer.py b/util/mario/tracer.py index ff9a2cfa..0b5783a1 100644 --- a/util/mario/tracer.py +++ b/util/mario/tracer.py @@ -37,7 +37,7 @@ def _flatten_dict(d, parent_key='', delimiter='_'): automatic `IDMA_TRACER_MAX_TYPE bus [string]; <%text>\\ automatic string trace; <%text>\\ `ifndef VERILATOR <%text>\\ - # 0; <%text>\\ + #0; <%text>\\ `endif <%text>\\ tf = $fopen(__out_f, "w"); <%text>\\ $display("[iDMA Tracer] Logging %s to %s", `"__backend_inst`", __out_f); <%text>\\ @@ -129,7 +129,8 @@ def render_tracer(prot_ids: dict, db: dict, tpl_file: str) -> str: # signals signals = '' - # Keys are direction-qualified: a protocol present on both sides (INIT) otherwise + # Keys are direction-qualified: a protocol present on both sides (INIT) would + # otherwise emit the same key twice and the write leg drop the read channel for read_prot in prot_ids[prot_id]['ar']: sig_dict = _flatten_dict(db[read_prot]['trace_signals']['read']) for signal in sig_dict: diff --git a/util/run_vlt_sim.py b/util/run_vlt_sim.py index 8cd4d8e0..59e8b4cb 100644 --- a/util/run_vlt_sim.py +++ b/util/run_vlt_sim.py @@ -16,29 +16,52 @@ A negated grep for "Error:" is deliberately not used: it passes when the log is missing, when the simulator died before writing anything, and when the token was never printed. Every condition here is stated positively. + +A hang is a failure in its own right, including for a negative test: several +testbenches in this repository hang rather than fail, so both stages run under a +timeout and a timed-out leg can never be reported as a guard that fired. """ import argparse import os import shlex +import signal import subprocess import sys +import threading import time -def run(cmd, log_path, cwd): - """Run cmd, tee to log_path, return (rc, wall_seconds).""" +def run(cmd, log_path, cwd, timeout=None): + """Run cmd, tee to log_path, return (rc, wall_seconds, timed_out).""" start = time.monotonic() + timed_out = [] with open(log_path, 'wb') as log: + # own process group: verilator and the simulation binary spawn children proc = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT) - for chunk in iter(lambda: proc.stdout.read(4096), b''): - log.write(chunk) - sys.stdout.buffer.write(chunk) - sys.stdout.flush() - proc.stdout.close() - rc = proc.wait() - return rc, time.monotonic() - start + stderr=subprocess.STDOUT, start_new_session=True) + + def expire(): + timed_out.append(True) + try: + os.killpg(os.getpgid(proc.pid), signal.SIGKILL) + except OSError: + pass + + timer = threading.Timer(timeout, expire) if timeout else None + if timer: + timer.start() + try: + for chunk in iter(lambda: proc.stdout.read(4096), b''): + log.write(chunk) + sys.stdout.buffer.write(chunk) + sys.stdout.flush() + proc.stdout.close() + rc = proc.wait() + finally: + if timer: + timer.cancel() + return rc, time.monotonic() - start, bool(timed_out) def parse_args(): @@ -61,6 +84,10 @@ def parse_args(): help='preprocessor define passed to verilator (-D)') par.add_argument('--vlt-arg', action='append', default=[], metavar='ARG', help='extra verilator argument; use --vlt-arg=-X for dashed values') + par.add_argument('--timeout', type=int, default=900, metavar='S', + help='wall-clock budget for the simulation run; 0 disables') + par.add_argument('--build-timeout', type=int, default=3600, metavar='S', + help='wall-clock budget for the verilator build; 0 disables') return par.parse_args() @@ -90,8 +117,12 @@ def main(): build += args.vlt_arg print('--- building {} [{}] ---'.format(args.top, tag), flush=True) - rc, secs = run(build, build_log, workdir) + rc, secs, expired = run(build, build_log, workdir, args.build_timeout or None) print('--- build {} rc={} ({:.1f} s) ---'.format(tag, rc, secs), flush=True) + if expired: + print('FAIL {}: build exceeded {} s (see {})'.format( + tag, args.build_timeout, build_log)) + return 1 if rc != 0: print('FAIL {}: verilator build failed (see {})'.format(tag, build_log)) return 1 @@ -100,9 +131,14 @@ def main(): return 1 print('--- running {} [{}] ---'.format(args.top, tag), flush=True) - rc, secs = run([binary] + args.plusarg, run_log, workdir) + rc, secs, expired = run([binary] + args.plusarg, run_log, workdir, args.timeout or None) print('--- run {} rc={} ({:.2f} s) ---'.format(tag, rc, secs), flush=True) + # 0. a hang is a failure, including for a negative test: it is not a guard firing + if expired: + print('FAIL {}: run exceeded {} s and was killed (see {})'.format( + tag, args.timeout, run_log)) + return 1 # 1. exit status if args.expect == 'pass' and rc != 0: print('FAIL {}: expected exit 0, got {}'.format(tag, rc)) From de964870ce97a54ccef2fb7356001169b356ff11 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 18:08:49 +0200 Subject: [PATCH 12/24] ci: Pin the simulation compiler and widen the mxquant sweep verilator --timing lowers to C++20 coroutines and g++ 11 miscompiles them, so the wide compute simulations SIGSEGV before time 0 with an empty log. Nothing recorded that, and the one green run only worked because CXX=g++-13.2.0 was typed on the command line. Pick the newest available g++ instead, and fold it into IDMA_VLT_MAKEFLAGS rather than replacing it, so passing -j4 no longer silently drops the toolchain. idma_verify_toolchain fails with the reason instead of leaving a segfault to interpret. DataWidth 512 and 1024 were excluded from the compute sweep as verilator crashes. They are not: with the compiler pinned, mxquant runs both. The widths are now per-testbench, because mxroundtrip does not: above 256 it dies in a heap that valgrind traces to writes inside the generated root constructor, before time 0, where the same run of tb_idma_mxquant is clean. That is not a width limit and is recorded as the open bug it is. mxneg cases 4 and 12 stay skipped with the measured reason: they SIGSEGV on verilator 5.020 and pass on 5.046. --- .github/actions/verify-setup/action.yml | 6 +++ .github/workflows/verify.yml | 21 ++++++---- idma.mk | 51 ++++++++++++++++++------- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/.github/actions/verify-setup/action.yml b/.github/actions/verify-setup/action.yml index 3007cc60..b7b31445 100644 --- a/.github/actions/verify-setup/action.yml +++ b/.github/actions/verify-setup/action.yml @@ -32,3 +32,9 @@ runs: verilator --version verilator --version | grep -qE '^Verilator 5\.' || { echo "expected Verilator 5.x from apt"; exit 1; } + # The C++ compiler decides whether the wide compute simulations run at all: + # verilator --timing lowers to C++20 coroutines and g++ 11 miscompiles them. + # ubuntu-24.04 defaults to g++ 13, so this asserts rather than installs. + - if: inputs.verilator == 'true' + shell: bash + run: uv run --locked make idma_verify_toolchain diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 2bb5b7b9..3b44930e 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -23,16 +23,23 @@ # tb_idma_mxrand testbench timeout at 400 ms of simulated time # tb_idma_mxperf hangs on the first compute transfer # tb_idma_rt_midend fails under --assert on cc_rr_arb_tree lock -# mxquant/mxroundtrip at DataWidth 512 and 1024 -# verilator 5.020 SIGSEGVs before time 0 (512 in the -# initial-block coroutine, 1024 in the model -# constructor); elaboration-only for now +# mxneg case 11 the request is never accepted, so +# ComputeMxdequantLengthFits is never sampled +# ComputeDstTilelink no TileLink backend variant exists to test it +# mxneg cases 4 and 12 DataWidth 1024 SIGSEGVs on verilator 5.020; both pass +# on 5.046, so this is a toolchain limit, not a gap +# mxroundtrip above 256 glibc heap abort, and 5.046 moves it to 256 instead of +# fixing it; a live bug, not a width limit # the 88 directed job files # verilator cannot simulate the idma_test class-based # driver (virtual interface, input combinational region # does not converge) -# The excluded mxneg cases and legalizer guards are named in idma.mk, next to the -# table that drives them, and util/check_jobs.py fails if that list drifts. +# Both are named in idma.mk next to the table that drives them, and +# util/check_jobs.py fails if either list drifts from the design. +# +# mxquant at DataWidth 512 and 1024 was on this list as a verilator crash. It was +# not: the C++ compiler was, and both widths run once idma.mk pins it. Never waive +# a leg on a toolchain symptom without testing another toolchain. name: verify @@ -161,7 +168,7 @@ jobs: name: Generate RTL run: uv run --locked make idma_hw_all - - name: Simulate tb_idma_mxquant at DataWidth 32, 64 and 256 + name: Simulate tb_idma_mxquant at DataWidth 32 to 1024 run: uv run --locked make idma_verify_sim_mxquant IDMA_VLT_MAKEFLAGS=-j4 sim-datapath-mxroundtrip: diff --git a/idma.mk b/idma.mk index 535fcdfc..5a394081 100644 --- a/idma.mk +++ b/idma.mk @@ -909,12 +909,35 @@ idma_verify_multihead: IDMA_VLT_SIM_T := -t rtl -t idma_test -t simulation -t synth IDMA_VLT_MAKEFLAGS ?= -IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" IDMA_VLT_MAKEFLAGS="$(IDMA_VLT_MAKEFLAGS)" \ + +# verilator --timing compiles to C++20 coroutines, which g++ 11 miscompiles: the +# wide compute testbenches then SIGSEGV before time 0. Pick the newest available +# g++ rather than whatever `g++` happens to be; ubuntu-24.04 defaults to 13. +IDMA_VLT_CXX ?= $(firstword $(foreach C,g++-14 g++-13 g++-13.2.0 g++-12 g++, \ + $(if $(shell command -v $C 2>/dev/null),$C))) +IDMA_VLT_CXX_MAJOR := $(shell $(IDMA_VLT_CXX) -dumpfullversion -dumpversion 2>/dev/null | cut -d. -f1) + +# Prepended, so a caller passing IDMA_VLT_MAKEFLAGS=-j4 adds to the toolchain +# rather than silently replacing it +IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" \ + IDMA_VLT_MAKEFLAGS="CXX=$(IDMA_VLT_CXX) LINK=$(IDMA_VLT_CXX) $(IDMA_VLT_MAKEFLAGS)" \ $(PYTHON) $(IDMA_UTIL_DIR)/run_vlt_sim.py --dir $(IDMA_VLT_DIR) -# DataWidth 512 and 1024 are elaboration-only: verilator 5.020 SIGSEGVs before -# time 0 (512 in the initial-block coroutine, 1024 in the model constructor). -IDMA_MX_WIDTHS ?= 32 64 256 +# Fail loudly here; the symptom otherwise is a SIGSEGV with an empty log +.PHONY: idma_verify_toolchain +idma_verify_toolchain: + @test -n "$(IDMA_VLT_CXX)" || { echo "error: no g++ found"; exit 1; } + @test "$(IDMA_VLT_CXX_MAJOR)" -ge 12 2>/dev/null || { \ + echo "error: $(IDMA_VLT_CXX) is g++ $(IDMA_VLT_CXX_MAJOR); the coroutine"; \ + echo " lowering needs g++ 12 or newer. Set IDMA_VLT_CXX=."; \ + exit 1; } + +# mxquant runs every width. mxroundtrip stops at 256: at 512 the run dies with a +# glibc heap abort that is not in the DPI golden (static, bounds-checked) and is +# invisible to ASAN; verilator 5.046 moves it to 256 rather than fixing it, so it +# is a live bug, not a width limit. Do not widen this without a fix. +IDMA_MXQUANT_WIDTHS ?= 32 64 256 512 1024 +IDMA_MXRT_WIDTHS ?= 32 64 256 # case:guard:DataWidth:EnDequant:EnFp16 IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ @@ -931,10 +954,10 @@ IDMA_MXNEG_CASES ?= 1 2 3 5 6 7 8 10 13 # Named rather than skipped, and checked by check_jobs.py: a case the testbench # defines must be here or in the table, and a legalizer guard must be proven to # fire by some case or be named here. case:reason, no spaces in the reason. -IDMA_MXNEG_SKIP := 4:needs-DataWidth-1024-verilator-SIGSEGV \ +IDMA_MXNEG_SKIP := 4:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 \ 11:request-never-accepted-guard-never-sampled \ - 12:needs-DataWidth-1024-verilator-SIGSEGV -IDMA_MXNEG_GUARD_SKIP := ComputeMxFp16Width:only-reachable-from-the-DataWidth-1024-cases \ + 12:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 +IDMA_MXNEG_GUARD_SKIP := ComputeMxFp16Width:only-reachable-from-the-skipped-1024-cases \ ComputeMxdequantLengthFits:case-11-never-accepted \ ComputeDstTilelink:no-tilelink-backend-variant-exists @@ -949,16 +972,16 @@ $(IDMA_VLT_DIR)/%_dpi.o: $(IDMA_ROOT)/test/%_dpi.c mkdir -p $(IDMA_VLT_DIR) $(CC) -c -O2 -fPIC $< -o $@ -idma_verify_sim_mxquant: $(IDMA_VLT_DIR)/tb_idma_mxquant.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; for dw in $(IDMA_MX_WIDTHS); do \ +idma_verify_sim_mxquant: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxquant.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; for dw in $(IDMA_MXQUANT_WIDTHS); do \ $(IDMA_VLT_SIM) --top tb_idma_mxquant --flist $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ --tag mxquant_$$dw --param DataWidth=$$dw \ --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXQ] ALL PASS"; \ done -idma_verify_sim_mxroundtrip: $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ +idma_verify_sim_mxroundtrip: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; for dw in $(IDMA_MX_WIDTHS); do \ + set -e; for dw in $(IDMA_MXRT_WIDTHS); do \ $(IDMA_VLT_SIM) --top tb_idma_mxroundtrip \ --flist $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ --tag mxroundtrip_$$dw --param DataWidth=$$dw \ @@ -966,7 +989,7 @@ idma_verify_sim_mxroundtrip: $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ done # +BP is a second stimulus (backpressured transpose), not a rerun -idma_verify_sim_transpose: $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ +idma_verify_sim_transpose: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ $(IDMA_VLT_DIR)/idma_transpose_dpi.o $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ @@ -986,14 +1009,14 @@ idma_verify_sim_transpose: $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ # Negative tests: the run must exit non-zero AND name its guard. --assert is # load-bearing; without it both testbenches exit 0 and the leg cannot fail. -idma_verify_sim_mxclear: $(IDMA_VLT_DIR)/tb_idma_mxclear.f +idma_verify_sim_mxclear: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxclear.f set -e; for q in 1 0; do \ $(IDMA_VLT_SIM) --top tb_idma_mxclear --flist $(IDMA_VLT_DIR)/tb_idma_mxclear.f \ --tag mxclear_$$q --param Quant=$$q --define INC_ASSERT --expect fail \ --token "clear with in-flight state"; \ done -idma_verify_sim_mxneg: $(IDMA_VLT_DIR)/tb_idma_mxneg.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o +idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o set -e; ran=0; \ for entry in $(IDMA_MXNEG_TABLE); do \ IFS=: read -r c guard dw deq fp <<< "$$entry"; \ From 921594cad40b3eaada2086ed39f5be0cfa45b074 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 18:16:14 +0200 Subject: [PATCH 13/24] build: Move the public verification gates into verify.mk idma.mk had grown from 703 to 1188 lines, and 376 of those were the public verification machinery: verilator flag tables, slang configurations, the simulation legs and the codegen consistency check. A third of the file that describes how to build iDMA was CI plumbing, and a downstream reader looking for idma_hw_all had to walk past all of it. The split follows what each file owns rather than the line count: idma.mk keeps every variable the build defines, verify.mk only consumes them and contributes no build rules, so building iDMA never reads it. Behaviour is unchanged; the include sits after the definitions it needs and idma_clean_all still reaches idma_verify_clean across the boundary. --- idma.mk | 379 +---------------------------------------------------- verify.mk | 385 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 388 insertions(+), 376 deletions(-) create mode 100644 verify.mk diff --git a/idma.mk b/idma.mk index 5a394081..296620cb 100644 --- a/idma.mk +++ b/idma.mk @@ -711,382 +711,6 @@ idma_lint_elab: idma_lint_all: idma_lint_elab idma_lint_inst64 -# --------------- -# Public verification -# --------------- - -# License-free elaboration and simulation gates, run on verilator and slang only, -# so fork PRs (which skip the proprietary EDA sims) still get a real signal. -# Every top-level target below is one CI leg and reproduces a red check locally. - -.PHONY: idma_verify_codegen idma_verify_backend idma_verify_shared idma_verify_multihead -.PHONY: idma_verify_tb_shared idma_verify_sim_mxquant idma_verify_sim_mxroundtrip -.PHONY: idma_verify_sim_transpose idma_verify_sim_mxclear idma_verify_sim_mxneg -.PHONY: idma_verify_all idma_lint_params idma_slang_elab idma_slang_tb idma_slang_report -.PHONY: idma_verify_clean - -# Module to elaborate; every per-top target takes it -IDMA_TOP ?= -# Backend variant a leg covers -IDMA_VERIFY_ID ?= rw_axi -# DataWidth points elaborated on top of the jobs.json parameter sets. Every -# jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise. -IDMA_ELAB_WIDTHS ?= 32 64 512 1024 - -# Compute-enabled configurations, as DataWidth:ComputeOps:ComputeTuning. No -# jobs.json entry enables the compute datapath, so its generate branches are -# unreachable otherwise. ComputeOps is {transpose, mxquant, mxdequant, mxfp16}. -# 64:15:1 every sub-unit, ping-pong transpose banks -# 512:15:1 the wide bus, where the pack/unpack loops are stressed -# 64:8:0 transpose only, single bank; also the no-mxquant/no-mxdequant branches -# 64:6:1 mx only, Fp16En=0; also the no-transpose branch -IDMA_ELAB_COMPUTE ?= 64:15:1 512:15:1 64:8:0 64:6:1 - -IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify -IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang -IDMA_SLANG_VERSION ?= 11.0.0 -# pyslang ships the slang driver itself. Never call a site `slang` wrapper: one -# turns a SIGSEGV into rc=0 with no output, which makes the gate vacuous. -SLANG ?= $(UV) run --with pyslang==$(IDMA_SLANG_VERSION) python \ - $(IDMA_UTIL_DIR)/slang_elab.py -IDMA_SLANG_ARGS := -Werror --error-limit 0 -# -Wno-finish-num covers 9 $fatal("") calls in common_verification; no -# iDMA file has one, and slang cannot scope a waiver to a dependency checkout. -IDMA_SLANG_TB_ARGS := --timescale=1ns/1ps -Wno-finish-num -IDMA_SLANG_SYNTH_T := -t rtl -t synth -IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ - -t snitch_cluster -t asic - -# Non-backend synthesis tops, elaborated with their own jobs.json parameters -IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synth \ - idma_rt_midend_synth - -# Out-of-tree multi-head variants; license-free but generated only on request -IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi - -# Generated register frontends, as RegVariant:module. They share a parameter and -# port list, so tb_idma_reg_frontend - the only public concrete binding - elaborates -# each of them; without this reg64_2d and reg64_1d are elaborated by nothing. Only -# variant 0 is simulated; the testbench refuses to run on the other two. -IDMA_REG_VARIANTS ?= 3:idma_reg32_3d 2:idma_reg64_2d 1:idma_reg64_1d - -# Testbench tops that are not per-backend. tb_idma_backend_* are covered by the -# matching idma_verify_backend leg; tb_idma_reg_frontend by IDMA_REG_VARIANTS. -IDMA_TB_SHARED_TOPS := tb_idma_desc64_top tb_idma_desc64_bench \ - tb_idma_nd_midend tb_idma_nd_midend_b2b tb_idma_rt_midend \ - tb_idma_transpose_midend tb_idma_otf_transpose \ - tb_idma_transpose_nd tb_idma_transpose_b2b tb_idma_mxquant \ - tb_idma_mxroundtrip tb_idma_mxrand tb_idma_mxneg \ - tb_idma_mxperf tb_idma_mxclear $(IDMA_INST64_TB) - -IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src/**/*.sv' \ - --source '$(IDMA_ROOT)/test/**/*.sv' - -# CI workflow that has to fan out over every backend id and negative-test case; -# a matrix leg that silently does not exist is worse than no leg -IDMA_MATRIX_FILE ?= $(IDMA_ROOT)/.github/workflows/verify.yml - -# Emit the elaboration configurations of $1 (jobs.json parameter sets + width sweep) -define idma_elab_cfg - mkdir -p $(IDMA_VERIFY_DIR) - $(PYTHON) $(IDMA_UTIL_DIR)/idma_params.py --top $1 \ - --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --widths "$(IDMA_ELAB_WIDTHS)" \ - --compute "$(IDMA_ELAB_COMPUTE)" \ - $(IDMA_SOURCE_GLOBS) > $(IDMA_VERIFY_DIR)/$1.cfg -endef - -# Filelists -$(IDMA_VLT_DIR)/idma_verify.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) - mkdir -p $(IDMA_VLT_DIR) - $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) > $@ - -$(IDMA_SLANG_DIR)/synth.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) - mkdir -p $(IDMA_SLANG_DIR) - $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) > $@ - -$(IDMA_SLANG_DIR)/tb.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) - mkdir -p $(IDMA_SLANG_DIR) - $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) > $@ - - -# Verilator elaboration of IDMA_TOP over every jobs.json configuration and width -# point. verilator rejects an unknown -G name; slang ignores it, which is why -# idma_params.py checks the names against the module header itself. -idma_lint_params: $(IDMA_VLT_DIR)/idma_verify.f - @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } - $(call idma_elab_cfg,$(IDMA_TOP)) - @test -s $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg || \ - { echo "error: no configurations for $(IDMA_TOP)"; exit 1; } - @rc=0; while read -r cfg flags; do \ - echo "--- verilator $(IDMA_TOP) [$$cfg] $$flags ---"; \ - $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_verify.f \ - --top-module $(IDMA_TOP) $$flags || rc=1; \ - done < $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg; \ - test $$rc -eq 0 && echo "idma_lint_params: $(IDMA_TOP) OK" || \ - echo "idma_lint_params: $(IDMA_TOP) FAILED"; exit $$rc - -# slang elaboration of IDMA_TOP over the same configurations -idma_slang_elab: $(IDMA_SLANG_DIR)/synth.f - @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } - $(call idma_elab_cfg,$(IDMA_TOP)) - @test -s $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg || \ - { echo "error: no configurations for $(IDMA_TOP)"; exit 1; } - @rc=0; while read -r cfg flags; do \ - echo "--- slang $(IDMA_TOP) [$$cfg] $$flags ---"; \ - $(SLANG) -f $(IDMA_SLANG_DIR)/synth.f --top $(IDMA_TOP) $(IDMA_SLANG_ARGS) $$flags \ - || rc=1; \ - done < $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg; \ - test $$rc -eq 0 && echo "idma_slang_elab: $(IDMA_TOP) OK" || \ - echo "idma_slang_elab: $(IDMA_TOP) FAILED"; exit $$rc - -# slang elaboration of one testbench top; verilator cannot parse the verification -# stack at all (axi_test/apb_test type-identifier errors, randomize()-with). -idma_slang_tb: $(IDMA_SLANG_DIR)/tb.f - @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } - $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $(IDMA_TOP) \ - $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS) - -# One backend variant: synthesis top under both front ends, plus its testbench -idma_verify_backend: - $(MAKE) idma_lint_params IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) - $(MAKE) idma_slang_elab IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) - $(MAKE) idma_slang_tb IDMA_TOP=tb_idma_backend_$(IDMA_VERIFY_ID) - -# The four non-backend synthesis tops with their jobs.json parameters, plus the -# default-parameter elaboration of every top from idma_lint_all -idma_verify_shared: idma_lint_all - set -e; for top in $(IDMA_SHARED_TOPS); do \ - $(MAKE) idma_lint_params IDMA_TOP=$$top; \ - $(MAKE) idma_slang_elab IDMA_TOP=$$top; \ - done - -idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f - @set -e; for id in $(IDMA_FE_IDS); do \ - case " $(IDMA_REG_VARIANTS) " in *":idma_$$id "*) ;; \ - *) echo "error: no IDMA_REG_VARIANTS entry elaborates idma_$$id"; exit 1;; esac; \ - done - set -e; for top in $(IDMA_TB_SHARED_TOPS); do \ - echo "--- slang $$top ---"; \ - $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $$top \ - $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ - done - set -e; for entry in $(IDMA_REG_VARIANTS); do \ - IFS=: read -r v mod <<< "$$entry"; \ - echo "--- slang tb_idma_reg_frontend [$$mod] ---"; \ - $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top tb_idma_reg_frontend -GRegVariant=$$v \ - $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ - done - -# Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the -# two extra synthesis tops and the two multi-head testbenches -idma_verify_multihead: - $(MAKE) idma_hw_all idma_add_all IDMA_ADD_IDS="$(IDMA_MULTIHEAD_IDS)" - mkdir -p $(IDMA_VLT_DIR) $(IDMA_SLANG_DIR) - $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_VLT_DIR)/idma_multihead.f - $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_SLANG_DIR)/mh_synth.f - $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead -t add_ids \ - > $(IDMA_SLANG_DIR)/mh_tb.f - set -e; for id in $(IDMA_MULTIHEAD_IDS); do \ - echo "--- verilator idma_backend_synth_$$id ---"; \ - $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_multihead.f \ - --top-module idma_backend_synth_$$id; \ - echo "--- slang idma_backend_synth_$$id ---"; \ - $(SLANG) -f $(IDMA_SLANG_DIR)/mh_synth.f --top idma_backend_synth_$$id \ - $(IDMA_SLANG_ARGS); \ - done - set -e; for tb in tb_idma_backend_multihead tb_idma_backend_multihead_rw; do \ - echo "--- slang $$tb ---"; \ - $(SLANG) -f $(IDMA_SLANG_DIR)/mh_tb.f --top $$tb \ - $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ - done - # the tracked aggregates never saw the extra ids, so this is a no-op assertion - $(MAKE) idma_hw_all - - -# --------------- -# Public simulation (verilator) -# --------------- - -IDMA_VLT_SIM_T := -t rtl -t idma_test -t simulation -t synth -IDMA_VLT_MAKEFLAGS ?= - -# verilator --timing compiles to C++20 coroutines, which g++ 11 miscompiles: the -# wide compute testbenches then SIGSEGV before time 0. Pick the newest available -# g++ rather than whatever `g++` happens to be; ubuntu-24.04 defaults to 13. -IDMA_VLT_CXX ?= $(firstword $(foreach C,g++-14 g++-13 g++-13.2.0 g++-12 g++, \ - $(if $(shell command -v $C 2>/dev/null),$C))) -IDMA_VLT_CXX_MAJOR := $(shell $(IDMA_VLT_CXX) -dumpfullversion -dumpversion 2>/dev/null | cut -d. -f1) - -# Prepended, so a caller passing IDMA_VLT_MAKEFLAGS=-j4 adds to the toolchain -# rather than silently replacing it -IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" \ - IDMA_VLT_MAKEFLAGS="CXX=$(IDMA_VLT_CXX) LINK=$(IDMA_VLT_CXX) $(IDMA_VLT_MAKEFLAGS)" \ - $(PYTHON) $(IDMA_UTIL_DIR)/run_vlt_sim.py --dir $(IDMA_VLT_DIR) - -# Fail loudly here; the symptom otherwise is a SIGSEGV with an empty log -.PHONY: idma_verify_toolchain -idma_verify_toolchain: - @test -n "$(IDMA_VLT_CXX)" || { echo "error: no g++ found"; exit 1; } - @test "$(IDMA_VLT_CXX_MAJOR)" -ge 12 2>/dev/null || { \ - echo "error: $(IDMA_VLT_CXX) is g++ $(IDMA_VLT_CXX_MAJOR); the coroutine"; \ - echo " lowering needs g++ 12 or newer. Set IDMA_VLT_CXX=."; \ - exit 1; } - -# mxquant runs every width. mxroundtrip stops at 256: at 512 the run dies with a -# glibc heap abort that is not in the DPI golden (static, bounds-checked) and is -# invisible to ASAN; verilator 5.046 moves it to 256 rather than fixing it, so it -# is a live bug, not a width limit. Do not widen this without a fix. -IDMA_MXQUANT_WIDTHS ?= 32 64 256 512 1024 -IDMA_MXRT_WIDTHS ?= 32 64 256 - -# case:guard:DataWidth:EnDequant:EnFp16 -IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ - 2:ComputeSrcAligned:64:1:1 \ - 3:ComputeDstAligned:64:1:1 \ - 5:ComputeMxdequantBeatAligned:64:1:1 \ - 6:ComputeOpUnsupported:64:0:1 \ - 7:ComputeMxSrcProtocol:64:1:1 \ - 8:ComputeMxDstProtocol:64:1:1 \ - 10:ComputeTransposeSingleBeat:64:1:1 \ - 13:ComputeOpUnsupported:64:1:0 -IDMA_MXNEG_CASES ?= 1 2 3 5 6 7 8 10 13 - -# Named rather than skipped, and checked by check_jobs.py: a case the testbench -# defines must be here or in the table, and a legalizer guard must be proven to -# fire by some case or be named here. case:reason, no spaces in the reason. -IDMA_MXNEG_SKIP := 4:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 \ - 11:request-never-accepted-guard-never-sampled \ - 12:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 -IDMA_MXNEG_GUARD_SKIP := ComputeMxFp16Width:only-reachable-from-the-skipped-1024-cases \ - ComputeMxdequantLengthFits:case-11-never-accepted \ - ComputeDstTilelink:no-tilelink-backend-variant-exists - -$(IDMA_VLT_DIR)/%.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) - mkdir -p $(IDMA_VLT_DIR) - $(BENDER) script verilator $(IDMA_VLT_SIM_T) --top $* > $@ - -# verilator hands a .c file to the C++ driver, so the DPI goldens are compiled -# here and linked in. idma_mxquant_dpi and idma_transpose_dpi both export -# gm_load/gm_get and must never end up in the same binary. -$(IDMA_VLT_DIR)/%_dpi.o: $(IDMA_ROOT)/test/%_dpi.c - mkdir -p $(IDMA_VLT_DIR) - $(CC) -c -O2 -fPIC $< -o $@ - -idma_verify_sim_mxquant: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxquant.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; for dw in $(IDMA_MXQUANT_WIDTHS); do \ - $(IDMA_VLT_SIM) --top tb_idma_mxquant --flist $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ - --tag mxquant_$$dw --param DataWidth=$$dw \ - --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXQ] ALL PASS"; \ - done - -idma_verify_sim_mxroundtrip: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ - $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; for dw in $(IDMA_MXRT_WIDTHS); do \ - $(IDMA_VLT_SIM) --top tb_idma_mxroundtrip \ - --flist $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ - --tag mxroundtrip_$$dw --param DataWidth=$$dw \ - --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXRT] ALL PASS"; \ - done - -# +BP is a second stimulus (backpressured transpose), not a rerun -idma_verify_sim_transpose: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ - $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ - $(IDMA_VLT_DIR)/idma_transpose_dpi.o - $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ - --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose \ - --param StrbWidth=8 --param FullDuplex=1 \ - --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" - $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ - --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose_bp \ - --param StrbWidth=8 --param FullDuplex=1 --plusarg +BP \ - --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" - set -e; for dw in 64 512; do \ - $(IDMA_VLT_SIM) --top tb_idma_transpose_midend \ - --flist $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f --tag transpose_midend_$$dw \ - --param DataWidth=$$dw --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o \ - --token "[MID] ALL PASS"; \ - done - -# Negative tests: the run must exit non-zero AND name its guard. --assert is -# load-bearing; without it both testbenches exit 0 and the leg cannot fail. -idma_verify_sim_mxclear: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxclear.f - set -e; for q in 1 0; do \ - $(IDMA_VLT_SIM) --top tb_idma_mxclear --flist $(IDMA_VLT_DIR)/tb_idma_mxclear.f \ - --tag mxclear_$$q --param Quant=$$q --define INC_ASSERT --expect fail \ - --token "clear with in-flight state"; \ - done - -idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; ran=0; \ - for entry in $(IDMA_MXNEG_TABLE); do \ - IFS=: read -r c guard dw deq fp <<< "$$entry"; \ - case " $(IDMA_MXNEG_CASES) " in *" $$c "*) ;; *) continue;; esac; \ - $(IDMA_VLT_SIM) --top tb_idma_mxneg --flist $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ - --tag mxneg_$$c --param NegCase=$$c --param DataWidth=$$dw \ - --param EnDequant=$$deq --param EnFp16=$$fp \ - --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --define INC_ASSERT --expect fail \ - --token "$$guard"; \ - ran=$$((ran+1)); \ - done; \ - want=$$(echo $(IDMA_MXNEG_CASES) | wc -w); \ - test $$ran -eq $$want || { echo "error: ran $$ran of $$want requested cases"; exit 1; } - - -# --------------- -# Codegen consistency and advisory report -# --------------- - -IDMA_GEN_FILES := $(IDMA_RTL_ALL) $(IDMA_TB_ALL) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) \ - $(IDMA_INCLUDE_ALL) $(IDMA_WAVE_ALL) - -# Not verification: jobs.json and the generated tree must still describe the same -# design, and a second generation must be byte-identical. The zero-git-diff form -# is deliberately not used; target/rtl is gitignored on source branches, so it -# would pass unconditionally. -idma_verify_codegen: - $(MAKE) idma_hw_all - $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ - --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --jobs-dir $(IDMA_ROOT)/jobs \ - --matrix-file $(IDMA_MATRIX_FILE) --mxneg-cases "$(IDMA_MXNEG_CASES)" \ - --mxneg-tb $(IDMA_ROOT)/test/tb_idma_mxneg.sv \ - --mxneg-skip "$(IDMA_MXNEG_SKIP)" \ - --mxneg-guard-src $(IDMA_ROOT)/src/backend/tpl/idma_legalizer.sv.tpl \ - --mxneg-guard-tested "$(IDMA_MXNEG_TABLE)" \ - --mxneg-guard-skip "$(IDMA_MXNEG_GUARD_SKIP)" \ - $(IDMA_SOURCE_GLOBS) - mkdir -p $(IDMA_VERIFY_DIR) - md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 - $(MAKE) idma_rtl_clean idma_reg_clean - $(MAKE) idma_hw_all - md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen2.md5 - diff -u $(IDMA_VERIFY_DIR)/gen1.md5 $(IDMA_VERIFY_DIR)/gen2.md5 - -# Advisory only: -Wextra without -Werror, so findings do not fail the target and -# a real compile error still does. Never add this to the required checks. -idma_slang_report: $(IDMA_SLANG_DIR)/synth.f - mkdir -p $(IDMA_VERIFY_DIR) - : > $(IDMA_VERIFY_DIR)/slang_extra.log - set -e; for top in $(IDMA_LINT_TOPS); do \ - $(SLANG) -f $(IDMA_SLANG_DIR)/synth.f --top $$top -Wextra --error-limit 0 \ - >> $(IDMA_VERIFY_DIR)/slang_extra.log 2>&1; \ - done - awk '/ (warning|error): /' $(IDMA_VERIFY_DIR)/slang_extra.log | \ - awk '!/^\.bender\//' | sort -u > $(IDMA_VERIFY_DIR)/slang_extra.txt - @echo "slang -Wextra: $$(wc -l < $(IDMA_VERIFY_DIR)/slang_extra.txt) unique iDMA findings" - -idma_verify_all: idma_verify_codegen idma_verify_shared idma_verify_tb_shared \ - idma_verify_multihead idma_verify_sim_mxquant \ - idma_verify_sim_mxroundtrip idma_verify_sim_transpose \ - idma_verify_sim_mxclear idma_verify_sim_mxneg - set -e; for id in $(IDMA_BACKEND_IDS); do \ - $(MAKE) idma_verify_backend IDMA_VERIFY_ID=$$id; \ - done - -idma_verify_clean: - rm -rf $(IDMA_VERIFY_DIR) - rm -rf $(IDMA_SLANG_DIR) - - # --------------- # Trace # --------------- @@ -1136,6 +760,9 @@ idma_nonfree_init: git clone $(IDMA_NONFREE_REMOTE) $(IDMA_NONFREE_DIR) cd $(IDMA_NONFREE_DIR) && git checkout $(IDMA_NONFREE_COMMIT) +# The public verification gates; see verify.mk +include $(IDMA_ROOT)/verify.mk + -include $(IDMA_NONFREE_DIR)/nonfree.mk idma_nonfree_clean: diff --git a/verify.mk b/verify.mk new file mode 100644 index 00000000..409dc513 --- /dev/null +++ b/verify.mk @@ -0,0 +1,385 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +# License-free verification: elaboration and simulation on verilator and slang +# only. Included by idma.mk, whose variables it uses; it adds no build rules, so +# building iDMA never reads it. + +# --------------- +# Public verification +# --------------- + +# License-free elaboration and simulation gates, run on verilator and slang only, +# so fork PRs (which skip the proprietary EDA sims) still get a real signal. +# Every top-level target below is one CI leg and reproduces a red check locally. + +.PHONY: idma_verify_codegen idma_verify_backend idma_verify_shared idma_verify_multihead +.PHONY: idma_verify_tb_shared idma_verify_sim_mxquant idma_verify_sim_mxroundtrip +.PHONY: idma_verify_sim_transpose idma_verify_sim_mxclear idma_verify_sim_mxneg +.PHONY: idma_verify_all idma_lint_params idma_slang_elab idma_slang_tb idma_slang_report +.PHONY: idma_verify_clean + +# Module to elaborate; every per-top target takes it +IDMA_TOP ?= +# Backend variant a leg covers +IDMA_VERIFY_ID ?= rw_axi +# DataWidth points elaborated on top of the jobs.json parameter sets. Every +# jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise. +IDMA_ELAB_WIDTHS ?= 32 64 512 1024 + +# Compute-enabled configurations, as DataWidth:ComputeOps:ComputeTuning. No +# jobs.json entry enables the compute datapath, so its generate branches are +# unreachable otherwise. ComputeOps is {transpose, mxquant, mxdequant, mxfp16}. +# 64:15:1 every sub-unit, ping-pong transpose banks +# 512:15:1 the wide bus, where the pack/unpack loops are stressed +# 64:8:0 transpose only, single bank; also the no-mxquant/no-mxdequant branches +# 64:6:1 mx only, Fp16En=0; also the no-transpose branch +IDMA_ELAB_COMPUTE ?= 64:15:1 512:15:1 64:8:0 64:6:1 + +IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify +IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang +IDMA_SLANG_VERSION ?= 11.0.0 +# pyslang ships the slang driver itself. Never call a site `slang` wrapper: one +# turns a SIGSEGV into rc=0 with no output, which makes the gate vacuous. +SLANG ?= $(UV) run --with pyslang==$(IDMA_SLANG_VERSION) python \ + $(IDMA_UTIL_DIR)/slang_elab.py +IDMA_SLANG_ARGS := -Werror --error-limit 0 +# -Wno-finish-num covers 9 $fatal("") calls in common_verification; no +# iDMA file has one, and slang cannot scope a waiver to a dependency checkout. +IDMA_SLANG_TB_ARGS := --timescale=1ns/1ps -Wno-finish-num +IDMA_SLANG_SYNTH_T := -t rtl -t synth +IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ + -t snitch_cluster -t asic + +# Non-backend synthesis tops, elaborated with their own jobs.json parameters +IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synth \ + idma_rt_midend_synth + +# Out-of-tree multi-head variants; license-free but generated only on request +IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi + +# Generated register frontends, as RegVariant:module. They share a parameter and +# port list, so tb_idma_reg_frontend - the only public concrete binding - elaborates +# each of them; without this reg64_2d and reg64_1d are elaborated by nothing. Only +# variant 0 is simulated; the testbench refuses to run on the other two. +IDMA_REG_VARIANTS ?= 3:idma_reg32_3d 2:idma_reg64_2d 1:idma_reg64_1d + +# Testbench tops that are not per-backend. tb_idma_backend_* are covered by the +# matching idma_verify_backend leg; tb_idma_reg_frontend by IDMA_REG_VARIANTS. +IDMA_TB_SHARED_TOPS := tb_idma_desc64_top tb_idma_desc64_bench \ + tb_idma_nd_midend tb_idma_nd_midend_b2b tb_idma_rt_midend \ + tb_idma_transpose_midend tb_idma_otf_transpose \ + tb_idma_transpose_nd tb_idma_transpose_b2b tb_idma_mxquant \ + tb_idma_mxroundtrip tb_idma_mxrand tb_idma_mxneg \ + tb_idma_mxperf tb_idma_mxclear $(IDMA_INST64_TB) + +IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src/**/*.sv' \ + --source '$(IDMA_ROOT)/test/**/*.sv' + +# CI workflow that has to fan out over every backend id and negative-test case; +# a matrix leg that silently does not exist is worse than no leg +IDMA_MATRIX_FILE ?= $(IDMA_ROOT)/.github/workflows/verify.yml + +# Emit the elaboration configurations of $1 (jobs.json parameter sets + width sweep) +define idma_elab_cfg + mkdir -p $(IDMA_VERIFY_DIR) + $(PYTHON) $(IDMA_UTIL_DIR)/idma_params.py --top $1 \ + --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --widths "$(IDMA_ELAB_WIDTHS)" \ + --compute "$(IDMA_ELAB_COMPUTE)" \ + $(IDMA_SOURCE_GLOBS) > $(IDMA_VERIFY_DIR)/$1.cfg +endef + +# Filelists +$(IDMA_VLT_DIR)/idma_verify.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_VLT_DIR) + $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) > $@ + +$(IDMA_SLANG_DIR)/synth.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_SLANG_DIR) + $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) > $@ + +$(IDMA_SLANG_DIR)/tb.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_SLANG_DIR) + $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) > $@ + + +# Verilator elaboration of IDMA_TOP over every jobs.json configuration and width +# point. verilator rejects an unknown -G name; slang ignores it, which is why +# idma_params.py checks the names against the module header itself. +idma_lint_params: $(IDMA_VLT_DIR)/idma_verify.f + @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } + $(call idma_elab_cfg,$(IDMA_TOP)) + @test -s $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg || \ + { echo "error: no configurations for $(IDMA_TOP)"; exit 1; } + @rc=0; while read -r cfg flags; do \ + echo "--- verilator $(IDMA_TOP) [$$cfg] $$flags ---"; \ + $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_verify.f \ + --top-module $(IDMA_TOP) $$flags || rc=1; \ + done < $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg; \ + test $$rc -eq 0 && echo "idma_lint_params: $(IDMA_TOP) OK" || \ + echo "idma_lint_params: $(IDMA_TOP) FAILED"; exit $$rc + +# slang elaboration of IDMA_TOP over the same configurations +idma_slang_elab: $(IDMA_SLANG_DIR)/synth.f + @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } + $(call idma_elab_cfg,$(IDMA_TOP)) + @test -s $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg || \ + { echo "error: no configurations for $(IDMA_TOP)"; exit 1; } + @rc=0; while read -r cfg flags; do \ + echo "--- slang $(IDMA_TOP) [$$cfg] $$flags ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/synth.f --top $(IDMA_TOP) $(IDMA_SLANG_ARGS) $$flags \ + || rc=1; \ + done < $(IDMA_VERIFY_DIR)/$(IDMA_TOP).cfg; \ + test $$rc -eq 0 && echo "idma_slang_elab: $(IDMA_TOP) OK" || \ + echo "idma_slang_elab: $(IDMA_TOP) FAILED"; exit $$rc + +# slang elaboration of one testbench top; verilator cannot parse the verification +# stack at all (axi_test/apb_test type-identifier errors, randomize()-with). +idma_slang_tb: $(IDMA_SLANG_DIR)/tb.f + @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } + $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $(IDMA_TOP) \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS) + +# One backend variant: synthesis top under both front ends, plus its testbench +idma_verify_backend: + $(MAKE) idma_lint_params IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) + $(MAKE) idma_slang_elab IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) + $(MAKE) idma_slang_tb IDMA_TOP=tb_idma_backend_$(IDMA_VERIFY_ID) + +# The four non-backend synthesis tops with their jobs.json parameters, plus the +# default-parameter elaboration of every top from idma_lint_all +idma_verify_shared: idma_lint_all + set -e; for top in $(IDMA_SHARED_TOPS); do \ + $(MAKE) idma_lint_params IDMA_TOP=$$top; \ + $(MAKE) idma_slang_elab IDMA_TOP=$$top; \ + done + +idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f + @set -e; for id in $(IDMA_FE_IDS); do \ + case " $(IDMA_REG_VARIANTS) " in *":idma_$$id "*) ;; \ + *) echo "error: no IDMA_REG_VARIANTS entry elaborates idma_$$id"; exit 1;; esac; \ + done + set -e; for top in $(IDMA_TB_SHARED_TOPS); do \ + echo "--- slang $$top ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $$top \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ + done + set -e; for entry in $(IDMA_REG_VARIANTS); do \ + IFS=: read -r v mod <<< "$$entry"; \ + echo "--- slang tb_idma_reg_frontend [$$mod] ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top tb_idma_reg_frontend -GRegVariant=$$v \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ + done + +# Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the +# two extra synthesis tops and the two multi-head testbenches +idma_verify_multihead: + $(MAKE) idma_hw_all idma_add_all IDMA_ADD_IDS="$(IDMA_MULTIHEAD_IDS)" + mkdir -p $(IDMA_VLT_DIR) $(IDMA_SLANG_DIR) + $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_VLT_DIR)/idma_multihead.f + $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_SLANG_DIR)/mh_synth.f + $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead -t add_ids \ + > $(IDMA_SLANG_DIR)/mh_tb.f + set -e; for id in $(IDMA_MULTIHEAD_IDS); do \ + echo "--- verilator idma_backend_synth_$$id ---"; \ + $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_multihead.f \ + --top-module idma_backend_synth_$$id; \ + echo "--- slang idma_backend_synth_$$id ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/mh_synth.f --top idma_backend_synth_$$id \ + $(IDMA_SLANG_ARGS); \ + done + set -e; for tb in tb_idma_backend_multihead tb_idma_backend_multihead_rw; do \ + echo "--- slang $$tb ---"; \ + $(SLANG) -f $(IDMA_SLANG_DIR)/mh_tb.f --top $$tb \ + $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ + done + # the tracked aggregates never saw the extra ids, so this is a no-op assertion + $(MAKE) idma_hw_all + + +# --------------- +# Public simulation (verilator) +# --------------- + +IDMA_VLT_SIM_T := -t rtl -t idma_test -t simulation -t synth +IDMA_VLT_MAKEFLAGS ?= + +# verilator --timing compiles to C++20 coroutines, which g++ 11 miscompiles: the +# wide compute testbenches then SIGSEGV before time 0. Pick the newest available +# g++ rather than whatever `g++` happens to be; ubuntu-24.04 defaults to 13. +IDMA_VLT_CXX ?= $(firstword $(foreach C,g++-14 g++-13 g++-13.2.0 g++-12 g++, \ + $(if $(shell command -v $C 2>/dev/null),$C))) +IDMA_VLT_CXX_MAJOR := $(shell $(IDMA_VLT_CXX) -dumpfullversion -dumpversion 2>/dev/null | cut -d. -f1) + +# Prepended, so a caller passing IDMA_VLT_MAKEFLAGS=-j4 adds to the toolchain +# rather than silently replacing it +IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" \ + IDMA_VLT_MAKEFLAGS="CXX=$(IDMA_VLT_CXX) LINK=$(IDMA_VLT_CXX) $(IDMA_VLT_MAKEFLAGS)" \ + $(PYTHON) $(IDMA_UTIL_DIR)/run_vlt_sim.py --dir $(IDMA_VLT_DIR) + +# Fail loudly here; the symptom otherwise is a SIGSEGV with an empty log +.PHONY: idma_verify_toolchain +idma_verify_toolchain: + @test -n "$(IDMA_VLT_CXX)" || { echo "error: no g++ found"; exit 1; } + @test "$(IDMA_VLT_CXX_MAJOR)" -ge 12 2>/dev/null || { \ + echo "error: $(IDMA_VLT_CXX) is g++ $(IDMA_VLT_CXX_MAJOR); the coroutine"; \ + echo " lowering needs g++ 12 or newer. Set IDMA_VLT_CXX=."; \ + exit 1; } + +# mxquant runs every width. mxroundtrip stops at 256: at 512 the run dies with a +# glibc heap abort that is not in the DPI golden (static, bounds-checked) and is +# invisible to ASAN; verilator 5.046 moves it to 256 rather than fixing it, so it +# is a live bug, not a width limit. Do not widen this without a fix. +IDMA_MXQUANT_WIDTHS ?= 32 64 256 512 1024 +IDMA_MXRT_WIDTHS ?= 32 64 256 + +# case:guard:DataWidth:EnDequant:EnFp16 +IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ + 2:ComputeSrcAligned:64:1:1 \ + 3:ComputeDstAligned:64:1:1 \ + 5:ComputeMxdequantBeatAligned:64:1:1 \ + 6:ComputeOpUnsupported:64:0:1 \ + 7:ComputeMxSrcProtocol:64:1:1 \ + 8:ComputeMxDstProtocol:64:1:1 \ + 10:ComputeTransposeSingleBeat:64:1:1 \ + 13:ComputeOpUnsupported:64:1:0 +IDMA_MXNEG_CASES ?= 1 2 3 5 6 7 8 10 13 + +# Named rather than skipped, and checked by check_jobs.py: a case the testbench +# defines must be here or in the table, and a legalizer guard must be proven to +# fire by some case or be named here. case:reason, no spaces in the reason. +IDMA_MXNEG_SKIP := 4:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 \ + 11:request-never-accepted-guard-never-sampled \ + 12:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 +IDMA_MXNEG_GUARD_SKIP := ComputeMxFp16Width:only-reachable-from-the-skipped-1024-cases \ + ComputeMxdequantLengthFits:case-11-never-accepted \ + ComputeDstTilelink:no-tilelink-backend-variant-exists + +$(IDMA_VLT_DIR)/%.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) + mkdir -p $(IDMA_VLT_DIR) + $(BENDER) script verilator $(IDMA_VLT_SIM_T) --top $* > $@ + +# verilator hands a .c file to the C++ driver, so the DPI goldens are compiled +# here and linked in. idma_mxquant_dpi and idma_transpose_dpi both export +# gm_load/gm_get and must never end up in the same binary. +$(IDMA_VLT_DIR)/%_dpi.o: $(IDMA_ROOT)/test/%_dpi.c + mkdir -p $(IDMA_VLT_DIR) + $(CC) -c -O2 -fPIC $< -o $@ + +idma_verify_sim_mxquant: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxquant.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; for dw in $(IDMA_MXQUANT_WIDTHS); do \ + $(IDMA_VLT_SIM) --top tb_idma_mxquant --flist $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ + --tag mxquant_$$dw --param DataWidth=$$dw \ + --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXQ] ALL PASS"; \ + done + +idma_verify_sim_mxroundtrip: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ + $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; for dw in $(IDMA_MXRT_WIDTHS); do \ + $(IDMA_VLT_SIM) --top tb_idma_mxroundtrip \ + --flist $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ + --tag mxroundtrip_$$dw --param DataWidth=$$dw \ + --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXRT] ALL PASS"; \ + done + +# +BP is a second stimulus (backpressured transpose), not a rerun +idma_verify_sim_transpose: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ + $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ + $(IDMA_VLT_DIR)/idma_transpose_dpi.o + $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ + --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose \ + --param StrbWidth=8 --param FullDuplex=1 \ + --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" + $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ + --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose_bp \ + --param StrbWidth=8 --param FullDuplex=1 --plusarg +BP \ + --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" + set -e; for dw in 64 512; do \ + $(IDMA_VLT_SIM) --top tb_idma_transpose_midend \ + --flist $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f --tag transpose_midend_$$dw \ + --param DataWidth=$$dw --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o \ + --token "[MID] ALL PASS"; \ + done + +# Negative tests: the run must exit non-zero AND name its guard. --assert is +# load-bearing; without it both testbenches exit 0 and the leg cannot fail. +idma_verify_sim_mxclear: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxclear.f + set -e; for q in 1 0; do \ + $(IDMA_VLT_SIM) --top tb_idma_mxclear --flist $(IDMA_VLT_DIR)/tb_idma_mxclear.f \ + --tag mxclear_$$q --param Quant=$$q --define INC_ASSERT --expect fail \ + --token "clear with in-flight state"; \ + done + +idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + set -e; ran=0; \ + for entry in $(IDMA_MXNEG_TABLE); do \ + IFS=: read -r c guard dw deq fp <<< "$$entry"; \ + case " $(IDMA_MXNEG_CASES) " in *" $$c "*) ;; *) continue;; esac; \ + $(IDMA_VLT_SIM) --top tb_idma_mxneg --flist $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ + --tag mxneg_$$c --param NegCase=$$c --param DataWidth=$$dw \ + --param EnDequant=$$deq --param EnFp16=$$fp \ + --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --define INC_ASSERT --expect fail \ + --token "$$guard"; \ + ran=$$((ran+1)); \ + done; \ + want=$$(echo $(IDMA_MXNEG_CASES) | wc -w); \ + test $$ran -eq $$want || { echo "error: ran $$ran of $$want requested cases"; exit 1; } + + +# --------------- +# Codegen consistency and advisory report +# --------------- + +IDMA_GEN_FILES := $(IDMA_RTL_ALL) $(IDMA_TB_ALL) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) \ + $(IDMA_INCLUDE_ALL) $(IDMA_WAVE_ALL) + +# Not verification: jobs.json and the generated tree must still describe the same +# design, and a second generation must be byte-identical. The zero-git-diff form +# is deliberately not used; target/rtl is gitignored on source branches, so it +# would pass unconditionally. +idma_verify_codegen: + $(MAKE) idma_hw_all + $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ + --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --jobs-dir $(IDMA_ROOT)/jobs \ + --matrix-file $(IDMA_MATRIX_FILE) --mxneg-cases "$(IDMA_MXNEG_CASES)" \ + --mxneg-tb $(IDMA_ROOT)/test/tb_idma_mxneg.sv \ + --mxneg-skip "$(IDMA_MXNEG_SKIP)" \ + --mxneg-guard-src $(IDMA_ROOT)/src/backend/tpl/idma_legalizer.sv.tpl \ + --mxneg-guard-tested "$(IDMA_MXNEG_TABLE)" \ + --mxneg-guard-skip "$(IDMA_MXNEG_GUARD_SKIP)" \ + $(IDMA_SOURCE_GLOBS) + mkdir -p $(IDMA_VERIFY_DIR) + md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 + $(MAKE) idma_rtl_clean idma_reg_clean + $(MAKE) idma_hw_all + md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen2.md5 + diff -u $(IDMA_VERIFY_DIR)/gen1.md5 $(IDMA_VERIFY_DIR)/gen2.md5 + +# Advisory only: -Wextra without -Werror, so findings do not fail the target and +# a real compile error still does. Never add this to the required checks. +idma_slang_report: $(IDMA_SLANG_DIR)/synth.f + mkdir -p $(IDMA_VERIFY_DIR) + : > $(IDMA_VERIFY_DIR)/slang_extra.log + set -e; for top in $(IDMA_LINT_TOPS); do \ + $(SLANG) -f $(IDMA_SLANG_DIR)/synth.f --top $$top -Wextra --error-limit 0 \ + >> $(IDMA_VERIFY_DIR)/slang_extra.log 2>&1; \ + done + awk '/ (warning|error): /' $(IDMA_VERIFY_DIR)/slang_extra.log | \ + awk '!/^\.bender\//' | sort -u > $(IDMA_VERIFY_DIR)/slang_extra.txt + @echo "slang -Wextra: $$(wc -l < $(IDMA_VERIFY_DIR)/slang_extra.txt) unique iDMA findings" + +idma_verify_all: idma_verify_codegen idma_verify_shared idma_verify_tb_shared \ + idma_verify_multihead idma_verify_sim_mxquant \ + idma_verify_sim_mxroundtrip idma_verify_sim_transpose \ + idma_verify_sim_mxclear idma_verify_sim_mxneg + set -e; for id in $(IDMA_BACKEND_IDS); do \ + $(MAKE) idma_verify_backend IDMA_VERIFY_ID=$$id; \ + done + +idma_verify_clean: + rm -rf $(IDMA_VERIFY_DIR) + rm -rf $(IDMA_SLANG_DIR) From 9670abca7645c9b5db8afc8459b70ecf59a38985 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 18:21:14 +0200 Subject: [PATCH 14/24] tracer: Fix the copyright year and authors on the new template --- src/include/idma/tpl/tracer_id.svh.tpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/include/idma/tpl/tracer_id.svh.tpl b/src/include/idma/tpl/tracer_id.svh.tpl index 8fb9845b..d4aa673c 100644 --- a/src/include/idma/tpl/tracer_id.svh.tpl +++ b/src/include/idma/tpl/tracer_id.svh.tpl @@ -1,9 +1,10 @@ -// Copyright 2023 ETH Zurich and University of Bologna. +// Copyright 2026 ETH Zurich and University of Bologna. // Solderpad Hardware License, Version 0.51, see LICENSE for details. // SPDX-License-Identifier: SHL-0.51 // Authors: // - Thomas Benz +// - Daniel Keller // Tracer macro of the ${identifier} iDMA backend `ifndef IDMA_TRACER_${identifier_cap}_SVH_ From 673a0ca8d2e6c0949d341b087ccaa04b3ccf8de8 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 18:31:14 +0200 Subject: [PATCH 15/24] ci: Drive the verification matrix from a database instead of make variables The run set lived in make variables and the fan-out in the workflow, so the same fact was written twice and check_jobs.py existed to notice when the two copies disagreed. A drift checker is what you build once you have already lost a single source of truth, and adding two negative cases this week meant editing both files by hand. src/db/verify.yml now holds every leg, next to the protocol databases that already drive generation. util/run_verify.py runs a suite from it and emits the CI matrix from the same entries, and the workflow fans out through fromJSON rather than listing suites, so a suite added to the database gets a CI leg with no workflow edit and nothing generated is committed. Testbench tops come from bender instead of a hand-kept list of fifteen module names; it already owns the file set. The derived set covers the same tops plus the inst64 testbench the list had missed. The simulation exclusions are deliberately not subtracted from it: those tops elaborate, only their runs hang. check_jobs.py loses the matrix comparison, which policed the duplication that is now gone, and keeps the one that compares the run set against the legalizer guards, which is a real second source. --- .github/workflows/verify.yml | 88 ++++------------- src/db/verify.yml | 112 ++++++++++++++++++++++ util/check_jobs.py | 93 +++++++----------- util/run_verify.py | 180 +++++++++++++++++++++++++++++++++++ verify.mk | 119 ++++++----------------- 5 files changed, 372 insertions(+), 220 deletions(-) create mode 100644 src/db/verify.yml create mode 100644 util/run_verify.py diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 3b44930e..f98bad41 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -150,11 +150,13 @@ jobs: name: Elaborate the multi-head variants and testbenches run: uv run --locked make idma_verify_multihead - # Real simulation, byte-exact against the DPI-C goldens; the destination is - # pre-poisoned, so a backend that writes nothing fails - sim-datapath-mxquant: + # The suite list comes from src/db/verify.yml, so a suite added there gets a CI + # leg with no edit here; the workflow cannot fan out over a stale set. + sim-matrix: needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 + outputs: + suites: ${{ steps.legs.outputs.suites }} steps: - name: Checkout @@ -162,70 +164,20 @@ jobs: - name: Set up toolchain uses: ./.github/actions/verify-setup - with: - verilator: 'true' - - - name: Generate RTL - run: uv run --locked make idma_hw_all - - - name: Simulate tb_idma_mxquant at DataWidth 32 to 1024 - run: uv run --locked make idma_verify_sim_mxquant IDMA_VLT_MAKEFLAGS=-j4 - - sim-datapath-mxroundtrip: - needs: [elab-backend, elab-shared-tops, elab-tb-shared] - runs-on: ubuntu-24.04 - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up toolchain - uses: ./.github/actions/verify-setup - with: - verilator: 'true' - - - name: Generate RTL - run: uv run --locked make idma_hw_all - - name: Simulate tb_idma_mxroundtrip at DataWidth 32, 64 and 256 - run: uv run --locked make idma_verify_sim_mxroundtrip IDMA_VLT_MAKEFLAGS=-j4 - - sim-datapath-transpose: - needs: [elab-backend, elab-shared-tops, elab-tb-shared] - runs-on: ubuntu-24.04 - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up toolchain - uses: ./.github/actions/verify-setup - with: - verilator: 'true' - - - name: Generate RTL - run: uv run --locked make idma_hw_all - - - name: Simulate the transpose datapath, plain and backpressured - run: uv run --locked make idma_verify_sim_transpose IDMA_VLT_MAKEFLAGS=-j4 + name: Read the suites from the verification database + id: legs + run: | + echo "suites=$(uv run --locked python util/run_verify.py \ + --emit-matrix suites)" >> "$GITHUB_OUTPUT" - # Negative tests, inverted polarity: the run must exit non-zero AND name its guard. - # --assert is load-bearing; without it both testbenches exit 0 and cannot fail - sim-negative: - needs: [elab-backend, elab-shared-tops, elab-tb-shared] + # Real simulation, byte-exact against the DPI-C goldens for the datapath suites + # and inverted-polarity for the negative ones; run_verify.py knows which is which + simulate: + needs: sim-matrix strategy: fail-fast: false - matrix: - include: - - name: mxclear-and-cases-1-3 - clear: 'yes' - cases: 1 2 3 - - name: cases-5-7 - clear: 'no' - cases: 5 6 7 - - name: cases-8-13 - clear: 'no' - cases: 8 10 13 + matrix: ${{ fromJSON(needs.sim-matrix.outputs.suites) }} runs-on: ubuntu-24.04 steps: - @@ -240,14 +192,8 @@ jobs: name: Generate RTL run: uv run --locked make idma_hw_all - - name: Check the clear-with-in-flight-state guard - if: matrix.clear == 'yes' - run: uv run --locked make idma_verify_sim_mxclear IDMA_VLT_MAKEFLAGS=-j4 - - - name: Check legalizer compute guards ${{ matrix.name }} - run: | - uv run --locked make idma_verify_sim_mxneg IDMA_VLT_MAKEFLAGS=-j4 \ - IDMA_MXNEG_CASES="${{ matrix.cases }}" + name: Simulate ${{ matrix.suite }} + run: uv run --locked make idma_verify_sim_${{ matrix.suite }} IDMA_VLT_MAKEFLAGS=-j4 # A report, not a gate: -Wextra without -Werror, so findings do not fail the job and # a real compile error still does. Never add it to the required checks diff --git a/src/db/verify.yml b/src/db/verify.yml new file mode 100644 index 00000000..5ff1f563 --- /dev/null +++ b/src/db/verify.yml @@ -0,0 +1,112 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +# The public verification matrix, in one place. util/run_verify.py runs a suite +# from this file and emits the CI matrix from the same entries, so a leg cannot +# exist in the workflow and not locally, or the reverse. +# +# Every excluded leg is an `exclude:` entry with a reason. A leg that is simply +# absent is a bug: util/check_jobs.py fails when a negative-test case or a +# legalizer guard is neither exercised here nor excluded here. + +# DataWidth points elaborated on top of the jobs.json parameter sets; every +# jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise +elab_widths: [32, 64, 512, 1024] + +# No jobs.json entry enables the compute datapath, so its generate branches are +# unreachable without these. ComputeOps is {transpose, mxquant, mxdequant, mxfp16} +elab_compute: + - {width: 64, ops: 15, tuning: 1, why: every sub-unit, ping-pong transpose banks} + - {width: 512, ops: 15, tuning: 1, why: the wide bus, where pack/unpack is stressed} + - {width: 64, ops: 8, tuning: 0, why: transpose only, single bank} + - {width: 64, ops: 6, tuning: 1, why: mx only, Fp16En=0, the no-transpose branch} + +# Elaborated by a dedicated leg, so the shared testbench sweep skips them: +# tb_idma_backend_* by the matching per-backend leg (handled by prefix), and the +# register frontend once per RegVariant. +elab_covered_elsewhere: [tb_idma_reg_frontend] + +# Simulation suites. Each is one CI job and one make target; each run is one leg. +# `expect: fail` inverts the polarity: the run must exit non-zero AND print token. +suites: + + mxquant: + top: tb_idma_mxquant + dpi: idma_mxquant_dpi + token: "[MXQ] ALL PASS" + sweep: {param: DataWidth, values: [32, 64, 256, 512, 1024]} + + mxroundtrip: + top: tb_idma_mxroundtrip + dpi: idma_mxquant_dpi + token: "[MXRT] ALL PASS" + sweep: {param: DataWidth, values: [32, 64, 256]} + exclude: + - {values: [512, 1024], why: "heap abort during model construction, see #196"} + + transpose: + top: tb_idma_otf_transpose + dpi: idma_transpose_dpi + token: "[TB] ALL PASS" + runs: + - {tag: otf_transpose, params: {StrbWidth: 8, FullDuplex: 1}} + # a second stimulus, backpressured, not a rerun + - {tag: otf_transpose_bp, params: {StrbWidth: 8, FullDuplex: 1}, plusargs: ["+BP"]} + + transpose_midend: + top: tb_idma_transpose_midend + dpi: idma_transpose_dpi + token: "[MID] ALL PASS" + sweep: {param: DataWidth, values: [64, 512]} + + mxclear: + top: tb_idma_mxclear + token: "clear with in-flight state" + expect: fail + defines: [INC_ASSERT] + sweep: {param: Quant, values: [1, 0]} + + # Negative tests against the legalizer compute guards. token is the guard name, + # so a case that fires the wrong guard fails instead of passing quietly. + mxneg: + top: tb_idma_mxneg + dpi: idma_mxquant_dpi + expect: fail + defines: [INC_ASSERT] + runs: + - {tag: mxneg_1, token: ComputeSizeAligned, params: {NegCase: 1, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_2, token: ComputeSrcAligned, params: {NegCase: 2, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_3, token: ComputeDstAligned, params: {NegCase: 3, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_5, token: ComputeMxdequantBeatAligned, params: {NegCase: 5, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_6, token: ComputeOpUnsupported, params: {NegCase: 6, DataWidth: 64, EnDequant: 0, EnFp16: 1}} + - {tag: mxneg_7, token: ComputeMxSrcProtocol, params: {NegCase: 7, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_8, token: ComputeMxDstProtocol, params: {NegCase: 8, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_10, token: ComputeTransposeSingleBeat, params: {NegCase: 10, DataWidth: 64, EnDequant: 1, EnFp16: 1}} + - {tag: mxneg_13, token: ComputeOpUnsupported, params: {NegCase: 13, DataWidth: 64, EnDequant: 1, EnFp16: 0}} + exclude: + - {case: 4, why: "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046"} + - {case: 11, why: "the request is never accepted, so the guard is never sampled"} + - {case: 12, why: "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046"} + +# Legalizer compute guards with no firing negative test. check_jobs.py fails if a +# guard is neither claimed by a mxneg run above nor named here. +guards_untested: + - {guard: ComputeMxFp16Width, why: "only reachable from the excluded 1024 cases"} + - {guard: ComputeMxdequantLengthFits, why: "case 11 is never accepted"} + - {guard: ComputeDstTilelink, why: "no TileLink backend variant exists"} + +# Testbench tops excluded from the simulation tier entirely. Dropped coverage, +# not silent skips; all need triage and none may be waived to make a leg green. +tops_untested: + - {top: tb_idma_nd_midend_b2b, why: "hangs ([B2B] timeout); latent reset/LFSR race"} + - {top: tb_idma_transpose_nd, why: "AW outside the destination allocation at 80 ns"} + - {top: tb_idma_transpose_b2b, why: "stream_watchdog trip after 4000 idle cycles"} + - {top: tb_idma_mxrand, why: "testbench timeout at 400 ms of simulated time"} + - {top: tb_idma_mxperf, why: "hangs on the first compute transfer"} + - {top: tb_idma_rt_midend, why: "fails under --assert on cc_rr_arb_tree lock"} + - {top: the 88 directed job files, + why: "verilator cannot drive the idma_test class-based stimulus"} diff --git a/util/check_jobs.py b/util/check_jobs.py index 1294c242..4dc02a97 100644 --- a/util/check_jobs.py +++ b/util/check_jobs.py @@ -13,12 +13,14 @@ a) every backend id has a jobs.json entry, b) every job path referenced by jobs.json exists, c) every testbench and synth_top named by jobs.json exists in the sources, - d) the CI matrix fans out over every backend id and negative-test case, - e) every negative-test case the testbench defines is either run or named as + d) every negative-test case the testbench defines is either run or named as skipped, and every legalizer compute guard is either proven to fire by some - case or named as untested. Without (e) a new case or a new guard is added to + case or named as untested. Without (d) a new case or a new guard is added to the design and silently exercised by nothing. +The CI matrix is not checked here any more: it fans out over src/db/verify.yml, +the same file that drives the local runs, so the two cannot disagree. + The run list is always taken from jobs.json, never from `ls jobs/*`: four error_*.txt files exist on disk for variants built with ErrorHandling=0 and must not be run. They are listed in UNWIRED_JOBS so the reverse check (a file @@ -32,6 +34,8 @@ import re import sys +import yaml + # Directed job files that exist on disk and are deliberately not wired into # jobs.json: the r_axi_w_obi and r_obi_w_axi backends elaborate with # ErrorHandling=0, so the error tests do not apply to them. @@ -58,20 +62,12 @@ def main(): par.add_argument('--jobs-dir', default='jobs') par.add_argument('--ids', required=True, help='space-separated IDMA_BACKEND_IDS') par.add_argument('--source', action='append', default=[], metavar='GLOB') - par.add_argument('--matrix-file', required=True, - help='CI workflow that must fan out over every backend id') - par.add_argument('--mxneg-cases', default='', - help='negative-test cases the workflow legs must request between them') + par.add_argument('--verify-db', default=None, + help='src/db/verify.yml; the run set and its named exclusions') par.add_argument('--mxneg-tb', default=None, help='negative-test testbench; its case labels must all be accounted for') - par.add_argument('--mxneg-skip', default='', - help='cases deliberately not run, as case:reason') par.add_argument('--mxneg-guard-src', default=None, help='source declaring the compute guards, e.g. the legalizer template') - par.add_argument('--mxneg-guard-tested', default='', - help='IDMA_MXNEG_TABLE entries, as case:guard:...') - par.add_argument('--mxneg-guard-skip', default='', - help='guards with no firing test, as guard:reason') args = par.parse_args() patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] @@ -119,33 +115,19 @@ def main(): errors.append('{}: {} "{}" is not defined in the sources'.format( name, field, module)) - # (d) the CI matrix must name every backend id and request every negative - # test case; a leg that silently does not exist is worse than no leg - with open(args.matrix_file, 'r') as handle: - matrix = handle.read() - for backend_id in args.ids.split(): - if not re.search(r'^\s*-\s*' + re.escape(backend_id) + r'\s*$', matrix, re.M): - errors.append('{} has no matrix leg for backend id {}'.format( - args.matrix_file, backend_id)) - if args.mxneg_cases: - requested = set() - for line in re.findall(r'^\s*cases:\s*(.+?)\s*$', matrix, re.M): - requested.update(line.split()) - for case in args.mxneg_cases.split(): - if case not in requested: - errors.append('{} requests no negative-test case {}'.format( - args.matrix_file, case)) - - # (e) no negative-test case and no compute guard may go silently unexercised - def tagged(spec): - """Parse a 'key:reason' or 'case:guard:...' list into {key: rest}.""" - out = {} - for entry in spec.split(): - key, _, rest = entry.partition(':') - out[key] = rest - return out - - if args.mxneg_tb: + # (d) no negative-test case and no compute guard may go silently unexercised. + # Both sides come from src/db/verify.yml, the same file that drives the runs, + # so this compares the matrix against the design rather than against a copy. + db = {} + if args.verify_db: + with open(args.verify_db, 'r') as handle: + db = yaml.safe_load(handle) or {} + + mxneg = db.get('suites', {}).get('mxneg', {}) + run_cases = {str(r['params']['NegCase']) for r in mxneg.get('runs', [])} + skipped = {str(e['case']): e['why'] for e in mxneg.get('exclude', [])} + + if args.mxneg_tb and db: with open(args.mxneg_tb, 'r', errors='replace') as handle: tb_text = handle.read() body = re.search(r'\bcase\s*\(\s*NegCase\s*\)(.*?)\bendcase', tb_text, re.S) @@ -153,32 +135,29 @@ def tagged(spec): errors.append('{}: no case (NegCase) block found'.format(args.mxneg_tb)) else: defined = set(re.findall(r'^\s*(\d+)\s*:', body.group(1), re.M)) - run_cases = set(args.mxneg_cases.split()) - skipped = tagged(args.mxneg_skip) for case in sorted(defined - run_cases - set(skipped), key=int): - errors.append('{}: case {} is defined but neither run nor named as ' - 'skipped'.format(args.mxneg_tb, case)) + errors.append('{}: case {} is defined but neither run nor excluded in ' + '{}'.format(args.mxneg_tb, case, os.path.basename(args.verify_db))) for case in sorted(set(skipped) - defined, key=int): - errors.append('case {} is named as skipped but the testbench does not ' - 'define it'.format(case)) + errors.append('case {} is excluded but the testbench does not define ' + 'it'.format(case)) for case in sorted(run_cases - defined, key=int): errors.append('case {} is run but the testbench does not define it'.format(case)) - if args.mxneg_guard_src: + if args.mxneg_guard_src and db: with open(args.mxneg_guard_src, 'r', errors='replace') as handle: guard_text = handle.read() declared = set(re.findall(r'`ASSERT_NEVER\(\s*(Compute\w+)', guard_text)) - tested = {entry.split(':')[1] for entry in args.mxneg_guard_tested.split() - if len(entry.split(':')) > 1} - guard_skip = tagged(args.mxneg_guard_skip) - for guard in sorted(declared - tested - set(guard_skip)): - errors.append('{}: guard {} has no negative test and is not named as ' - 'untested'.format(args.mxneg_guard_src, guard)) - for guard in sorted(set(guard_skip) - declared): - errors.append('guard {} is named as untested but is not declared in {}'.format( - guard, args.mxneg_guard_src)) + tested = {r['token'] for r in mxneg.get('runs', []) if r.get('token')} + waived = {e['guard'] for e in db.get('guards_untested', [])} + for guard in sorted(declared - tested - waived): + errors.append('{}: guard {} has no negative test and is not named in ' + 'guards_untested'.format(args.mxneg_guard_src, guard)) + for guard in sorted(waived - declared): + errors.append('guard {} is named in guards_untested but is not declared in ' + '{}'.format(guard, args.mxneg_guard_src)) for guard in sorted(tested - declared): - errors.append('guard {} is claimed by the case table but is not declared in ' + errors.append('guard {} is claimed by a mxneg run but is not declared in ' '{}'.format(guard, args.mxneg_guard_src)) for message in errors: diff --git a/util/run_verify.py b/util/run_verify.py new file mode 100644 index 00000000..481f3344 --- /dev/null +++ b/util/run_verify.py @@ -0,0 +1,180 @@ +# Copyright 2026 ETH Zurich and University of Bologna. +# Solderpad Hardware License, Version 0.51, see LICENSE for details. +# SPDX-License-Identifier: SHL-0.51 + +# Authors: +# - Daniel Keller + +"""Run a verification suite, or emit the CI matrix, from src/db/verify.yml. + +Both come from the same entries, so a leg cannot exist in CI and not locally, or +the reverse. The old form kept the run set in make variables and the fan-out in +the workflow, and needed a drift checker to notice when they disagreed. + + run_verify.py --suite mxneg run every leg of one suite + run_verify.py --emit-matrix suites print the suite names as a CI matrix + run_verify.py --list print every leg, one per line + run_verify.py --tb-tops print the testbench tops bender knows of + +Testbench tops are asked of bender rather than listed by hand: it already owns +the file set, so a testbench added to Bender.yml is elaborated without touching +this file, and one that is not in Bender.yml cannot hide behind a stale list. +""" + +import argparse +import json +import os +import re +import subprocess +import sys + +import yaml + +HERE = os.path.dirname(os.path.abspath(__file__)) +ROOT = os.path.dirname(HERE) +DB = os.path.join(ROOT, 'src', 'db', 'verify.yml') + + +def load(path=None): + with open(path or DB, 'r') as handle: + return yaml.safe_load(handle) + + +def legs(suite_name, suite): + """Expand one suite into its runs: an explicit list, or a swept parameter.""" + if 'runs' in suite: + for run in suite['runs']: + yield { + 'tag': run['tag'], + 'params': dict(run.get('params', {})), + 'token': run.get('token', suite.get('token')), + 'plusargs': run.get('plusargs', []), + } + return + sweep = suite['sweep'] + for value in sweep['values']: + yield { + 'tag': '{}_{}'.format(suite_name, value), + 'params': {sweep['param']: value}, + 'token': suite.get('token'), + 'plusargs': [], + } + + +def bender_sources(bender, targets): + """The .sv files bender selects for *targets*, in compile order.""" + cmd = [bender, 'script', 'flist'] + targets + out = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True) + if out.returncode != 0: + raise SystemExit('bender failed: ' + out.stderr.strip()) + files = [] + for line in out.stdout.splitlines(): + line = line.strip() + if line and not line.startswith(('+incdir+', '-', '+define+')): + files.append(line) + return files + + +def tb_tops(db, bender, targets): + """Every testbench module bender knows about, for the elaboration tier. + + `tops_untested` is deliberately not subtracted: those tops elaborate fine and + only their simulation is excluded, so dropping them here would quietly lose + the elaboration coverage that still works. + """ + covered = set(db.get('elab_covered_elsewhere', [])) + found = [] + decl = re.compile(r'^\s*module\s+(tb_\w+)', re.M) + for path in bender_sources(bender, targets): + # only iDMA's own testbenches; dependencies bring their own tb_* modules + if '.bender/' in path or '/deps/' in path: + continue + try: + with open(path, 'r', errors='replace') as handle: + text = handle.read() + except OSError: + continue + for name in decl.findall(text): + if name.startswith('tb_idma_backend_'): + continue # covered by the matching per-backend leg + if name in covered or name in found: + continue + found.append(name) + return sorted(found) + + +def run_suite(name, db, args): + suite = db['suites'][name] + vlt_dir = args.vlt_dir + flist = os.path.join(vlt_dir, suite['top'] + '.f') + failures = [] + planned = list(legs(name, suite)) + if not planned: + print('error: suite {} has no legs'.format(name)) + return 1 + for leg in planned: + cmd = [sys.executable, os.path.join(HERE, 'run_vlt_sim.py'), + '--dir', vlt_dir, '--top', suite['top'], '--flist', flist, + '--tag', leg['tag'], '--token', leg['token']] + for key, value in leg['params'].items(): + cmd += ['--param', '{}={}'.format(key, value)] + for define in suite.get('defines', []): + cmd += ['--define', define] + for plusarg in leg['plusargs']: + cmd += ['--plusarg', plusarg] + if suite.get('dpi'): + cmd += ['--dpi', os.path.join(vlt_dir, suite['dpi'] + '.o')] + if suite.get('expect') == 'fail': + cmd += ['--expect', 'fail'] + if args.verilator: + cmd += ['--verilator', args.verilator] + if args.makeflags: + cmd += ['--makeflags', args.makeflags] + if subprocess.call(cmd) != 0: + failures.append(leg['tag']) + # A suite that silently ran nothing is a pass under any per-leg check alone + print('{}: ran {} leg(s), {} failed'.format(name, len(planned), len(failures))) + return 1 if failures else 0 + + +def main(): + par = argparse.ArgumentParser(description=__doc__) + par.add_argument('--db', default=DB) + par.add_argument('--suite') + par.add_argument('--emit-matrix', choices=['suites']) + par.add_argument('--list', action='store_true') + par.add_argument('--tb-tops', action='store_true', + help='testbench tops, from bender rather than a hand-kept list') + par.add_argument('--bender', default=os.environ.get('BENDER', 'bender')) + par.add_argument('--target', action='append', default=[], + help='bender target; repeat, e.g. --target rtl --target idma_test') + par.add_argument('--vlt-dir', default=os.path.join(ROOT, 'target/sim/verilator')) + par.add_argument('--verilator', default=os.environ.get('VERILATOR')) + par.add_argument('--makeflags', default=os.environ.get('IDMA_VLT_MAKEFLAGS')) + args = par.parse_args() + db = load(args.db) + + if args.emit_matrix == 'suites': + print(json.dumps({'suite': sorted(db['suites'])})) + return 0 + if args.tb_tops: + targets = [] + for target in args.target: + targets += ['-t', target] + print(' '.join(tb_tops(db, args.bender, targets))) + return 0 + if args.list: + for name in sorted(db['suites']): + for leg in legs(name, db['suites'][name]): + print('{}\t{}\t{}'.format(name, leg['tag'], leg['token'])) + return 0 + if args.suite: + if args.suite not in db['suites']: + print('error: no suite named {}'.format(args.suite)) + return 1 + return run_suite(args.suite, db, args) + par.error('pass --suite, --emit-matrix or --list') + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/verify.mk b/verify.mk index 409dc513..4fba76d2 100644 --- a/verify.mk +++ b/verify.mk @@ -19,7 +19,8 @@ .PHONY: idma_verify_codegen idma_verify_backend idma_verify_shared idma_verify_multihead .PHONY: idma_verify_tb_shared idma_verify_sim_mxquant idma_verify_sim_mxroundtrip -.PHONY: idma_verify_sim_transpose idma_verify_sim_mxclear idma_verify_sim_mxneg +.PHONY: idma_verify_sim_transpose idma_verify_sim_transpose_midend +.PHONY: idma_verify_sim_mxclear idma_verify_sim_mxneg .PHONY: idma_verify_all idma_lint_params idma_slang_elab idma_slang_tb idma_slang_report .PHONY: idma_verify_clean @@ -68,22 +69,15 @@ IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi # variant 0 is simulated; the testbench refuses to run on the other two. IDMA_REG_VARIANTS ?= 3:idma_reg32_3d 2:idma_reg64_2d 1:idma_reg64_1d -# Testbench tops that are not per-backend. tb_idma_backend_* are covered by the -# matching idma_verify_backend leg; tb_idma_reg_frontend by IDMA_REG_VARIANTS. -IDMA_TB_SHARED_TOPS := tb_idma_desc64_top tb_idma_desc64_bench \ - tb_idma_nd_midend tb_idma_nd_midend_b2b tb_idma_rt_midend \ - tb_idma_transpose_midend tb_idma_otf_transpose \ - tb_idma_transpose_nd tb_idma_transpose_b2b tb_idma_mxquant \ - tb_idma_mxroundtrip tb_idma_mxrand tb_idma_mxneg \ - tb_idma_mxperf tb_idma_mxclear $(IDMA_INST64_TB) +# Testbench tops, asked of bender rather than listed here: it already owns the +# file set, so a testbench added to Bender.yml is elaborated without editing this +# file. Deferred (=), so bender only runs when a leg actually needs the list. +IDMA_TB_SHARED_TOPS = $(shell BENDER=$(BENDER) $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py \ + --tb-tops $(patsubst -t%,--target %,$(IDMA_SLANG_TB_T))) IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src/**/*.sv' \ --source '$(IDMA_ROOT)/test/**/*.sv' -# CI workflow that has to fan out over every backend id and negative-test case; -# a matrix leg that silently does not exist is worse than no leg -IDMA_MATRIX_FILE ?= $(IDMA_ROOT)/.github/workflows/verify.yml - # Emit the elaboration configurations of $1 (jobs.json parameter sets + width sweep) define idma_elab_cfg mkdir -p $(IDMA_VERIFY_DIR) @@ -230,34 +224,10 @@ idma_verify_toolchain: echo " lowering needs g++ 12 or newer. Set IDMA_VLT_CXX=."; \ exit 1; } -# mxquant runs every width. mxroundtrip stops at 256: at 512 the run dies with a -# glibc heap abort that is not in the DPI golden (static, bounds-checked) and is -# invisible to ASAN; verilator 5.046 moves it to 256 rather than fixing it, so it -# is a live bug, not a width limit. Do not widen this without a fix. -IDMA_MXQUANT_WIDTHS ?= 32 64 256 512 1024 -IDMA_MXRT_WIDTHS ?= 32 64 256 - -# case:guard:DataWidth:EnDequant:EnFp16 -IDMA_MXNEG_TABLE := 1:ComputeSizeAligned:64:1:1 \ - 2:ComputeSrcAligned:64:1:1 \ - 3:ComputeDstAligned:64:1:1 \ - 5:ComputeMxdequantBeatAligned:64:1:1 \ - 6:ComputeOpUnsupported:64:0:1 \ - 7:ComputeMxSrcProtocol:64:1:1 \ - 8:ComputeMxDstProtocol:64:1:1 \ - 10:ComputeTransposeSingleBeat:64:1:1 \ - 13:ComputeOpUnsupported:64:1:0 -IDMA_MXNEG_CASES ?= 1 2 3 5 6 7 8 10 13 - -# Named rather than skipped, and checked by check_jobs.py: a case the testbench -# defines must be here or in the table, and a legalizer guard must be proven to -# fire by some case or be named here. case:reason, no spaces in the reason. -IDMA_MXNEG_SKIP := 4:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 \ - 11:request-never-accepted-guard-never-sampled \ - 12:DataWidth-1024-SIGSEGVs-on-verilator-5.020-passes-on-5.046 -IDMA_MXNEG_GUARD_SKIP := ComputeMxFp16Width:only-reachable-from-the-skipped-1024-cases \ - ComputeMxdequantLengthFits:case-11-never-accepted \ - ComputeDstTilelink:no-tilelink-backend-variant-exists +IDMA_VERIFY_DB := $(IDMA_ROOT)/src/db/verify.yml +IDMA_VERIFY_RUN := VERILATOR="$(VERILATOR)" \ + IDMA_VLT_MAKEFLAGS="CXX=$(IDMA_VLT_CXX) LINK=$(IDMA_VLT_CXX) $(IDMA_VLT_MAKEFLAGS)" \ + $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --vlt-dir $(IDMA_VLT_DIR) $(IDMA_VLT_DIR)/%.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDMA_INCLUDE_ALL) mkdir -p $(IDMA_VLT_DIR) @@ -270,64 +240,31 @@ $(IDMA_VLT_DIR)/%_dpi.o: $(IDMA_ROOT)/test/%_dpi.c mkdir -p $(IDMA_VLT_DIR) $(CC) -c -O2 -fPIC $< -o $@ -idma_verify_sim_mxquant: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxquant.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; for dw in $(IDMA_MXQUANT_WIDTHS); do \ - $(IDMA_VLT_SIM) --top tb_idma_mxquant --flist $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ - --tag mxquant_$$dw --param DataWidth=$$dw \ - --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXQ] ALL PASS"; \ - done +idma_verify_sim_mxquant: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ + $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + $(IDMA_VERIFY_RUN) --suite mxquant idma_verify_sim_mxroundtrip: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; for dw in $(IDMA_MXRT_WIDTHS); do \ - $(IDMA_VLT_SIM) --top tb_idma_mxroundtrip \ - --flist $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ - --tag mxroundtrip_$$dw --param DataWidth=$$dw \ - --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --token "[MXRT] ALL PASS"; \ - done + $(IDMA_VERIFY_RUN) --suite mxroundtrip -# +BP is a second stimulus (backpressured transpose), not a rerun idma_verify_sim_transpose: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ - $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ $(IDMA_VLT_DIR)/idma_transpose_dpi.o - $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ - --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose \ - --param StrbWidth=8 --param FullDuplex=1 \ - --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" - $(IDMA_VLT_SIM) --top tb_idma_otf_transpose \ - --flist $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f --tag otf_transpose_bp \ - --param StrbWidth=8 --param FullDuplex=1 --plusarg +BP \ - --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o --token "[TB] ALL PASS" - set -e; for dw in 64 512; do \ - $(IDMA_VLT_SIM) --top tb_idma_transpose_midend \ - --flist $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f --tag transpose_midend_$$dw \ - --param DataWidth=$$dw --dpi $(IDMA_VLT_DIR)/idma_transpose_dpi.o \ - --token "[MID] ALL PASS"; \ - done + $(IDMA_VERIFY_RUN) --suite transpose + +idma_verify_sim_transpose_midend: idma_verify_toolchain \ + $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ + $(IDMA_VLT_DIR)/idma_transpose_dpi.o + $(IDMA_VERIFY_RUN) --suite transpose_midend # Negative tests: the run must exit non-zero AND name its guard. --assert is # load-bearing; without it both testbenches exit 0 and the leg cannot fail. idma_verify_sim_mxclear: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxclear.f - set -e; for q in 1 0; do \ - $(IDMA_VLT_SIM) --top tb_idma_mxclear --flist $(IDMA_VLT_DIR)/tb_idma_mxclear.f \ - --tag mxclear_$$q --param Quant=$$q --define INC_ASSERT --expect fail \ - --token "clear with in-flight state"; \ - done + $(IDMA_VERIFY_RUN) --suite mxclear -idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - set -e; ran=0; \ - for entry in $(IDMA_MXNEG_TABLE); do \ - IFS=: read -r c guard dw deq fp <<< "$$entry"; \ - case " $(IDMA_MXNEG_CASES) " in *" $$c "*) ;; *) continue;; esac; \ - $(IDMA_VLT_SIM) --top tb_idma_mxneg --flist $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ - --tag mxneg_$$c --param NegCase=$$c --param DataWidth=$$dw \ - --param EnDequant=$$deq --param EnFp16=$$fp \ - --dpi $(IDMA_VLT_DIR)/idma_mxquant_dpi.o --define INC_ASSERT --expect fail \ - --token "$$guard"; \ - ran=$$((ran+1)); \ - done; \ - want=$$(echo $(IDMA_MXNEG_CASES) | wc -w); \ - test $$ran -eq $$want || { echo "error: ran $$ran of $$want requested cases"; exit 1; } +idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ + $(IDMA_VLT_DIR)/idma_mxquant_dpi.o + $(IDMA_VERIFY_RUN) --suite mxneg # --------------- @@ -345,12 +282,9 @@ idma_verify_codegen: $(MAKE) idma_hw_all $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --jobs-dir $(IDMA_ROOT)/jobs \ - --matrix-file $(IDMA_MATRIX_FILE) --mxneg-cases "$(IDMA_MXNEG_CASES)" \ + --verify-db $(IDMA_VERIFY_DB) \ --mxneg-tb $(IDMA_ROOT)/test/tb_idma_mxneg.sv \ - --mxneg-skip "$(IDMA_MXNEG_SKIP)" \ --mxneg-guard-src $(IDMA_ROOT)/src/backend/tpl/idma_legalizer.sv.tpl \ - --mxneg-guard-tested "$(IDMA_MXNEG_TABLE)" \ - --mxneg-guard-skip "$(IDMA_MXNEG_GUARD_SKIP)" \ $(IDMA_SOURCE_GLOBS) mkdir -p $(IDMA_VERIFY_DIR) md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 @@ -375,6 +309,7 @@ idma_slang_report: $(IDMA_SLANG_DIR)/synth.f idma_verify_all: idma_verify_codegen idma_verify_shared idma_verify_tb_shared \ idma_verify_multihead idma_verify_sim_mxquant \ idma_verify_sim_mxroundtrip idma_verify_sim_transpose \ + idma_verify_sim_transpose_midend \ idma_verify_sim_mxclear idma_verify_sim_mxneg set -e; for id in $(IDMA_BACKEND_IDS); do \ $(MAKE) idma_verify_backend IDMA_VERIFY_ID=$$id; \ From 5405c08a96ec17202556c6000d59ae2e8b7f0700 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 18:59:34 +0200 Subject: [PATCH 16/24] ci: Close the gaps a critical review of this branch found The testbench top list was a $(shell), whose exit status make discards, so any bender or uv failure emptied it and idma_verify_tb_shared passed having elaborated none of its sixteen tops. It is a file target now, checked non-empty at both ends. That was a vacuous gate of exactly the kind this branch adds checks to forbid. The compiler probe ran twenty-six subshells at parse time on every make in the repository, including builds that run no simulation, because the variable was recursive and referenced five times. One subshell now, and only from a recipe. The database was half inert: elab_widths and elab_compute were read by nothing while the real values sat in make variables, and a suite's exclude entries were never checked. Both are wired up, and re-adding an excluded width without dropping its note now fails. The per-suite simulation targets are one pattern rule taking each suite's prerequisites from the database, so a suite added there no longer gets a CI leg that fails with "No rule to make target". Also: idma_lint_elab hand-wrote a weaker verilator flag set than the one beside it, so two elaboration paths disagreed on -Werror-LATCH, -Werror-MULTIDRIVEN and -Werror-IMPLICIT; the pre-existing per-top verilator path had no references left; the slang -Wextra report was a non-gating job inside a gating workflow; and Verible was pinned to a moving branch. Comments are cut back to one line, and the exclusion inventory duplicated in the workflow header is deleted: the database carries it with the reasons. --- .github/workflows/lint.yml | 1 + .github/workflows/verify.yml | 78 ++-------------------- idma.mk | 22 +------ src/db/verify.yml | 32 +++------ util/check_jobs.py | 19 ++++-- util/idma_params.py | 13 ++++ util/run_verify.py | 13 ++++ verify.mk | 121 ++++++++++------------------------- 8 files changed, 95 insertions(+), 204 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index e52c4e8b..0727bc26 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -45,6 +45,7 @@ jobs: uses: chipsalliance/verible-actions-common/install-verible@main with: github_token: ${{ secrets.GITHUB_TOKEN }} + verible_version: "v0.0-3318-g8d254167" - name: Lint all of src run: make idma_lint_sv diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index f98bad41..499c24d6 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -5,41 +5,7 @@ # Author: # - Daniel Keller -# License-free verification matrix: elaboration gates the simulation tier. -# -# House rules, enforced in review: -# - no `|| true` and no `continue-on-error` on any step of a gating job; -# - no negated grep as a pass criterion; every simulation leg checks exit status -# AND a non-empty log AND a positive token (util/run_vlt_sim.py); -# - no numeric warning-count thresholds; -# - every excluded top or configuration is named below with its reason; -# - `needs:` orders jobs, it does not share workspaces; every job regenerates. -# -# NAMED EXCLUSIONS - dropped coverage, not silent skips. All need triage; none may -# be flagged away to make a leg green. -# tb_idma_nd_midend_b2b hangs ([B2B] timeout); latent reset/LFSR race -# tb_idma_transpose_nd AW outside the destination allocation at 80 ns -# tb_idma_transpose_b2b stream_watchdog trip after 4000 idle cycles -# tb_idma_mxrand testbench timeout at 400 ms of simulated time -# tb_idma_mxperf hangs on the first compute transfer -# tb_idma_rt_midend fails under --assert on cc_rr_arb_tree lock -# mxneg case 11 the request is never accepted, so -# ComputeMxdequantLengthFits is never sampled -# ComputeDstTilelink no TileLink backend variant exists to test it -# mxneg cases 4 and 12 DataWidth 1024 SIGSEGVs on verilator 5.020; both pass -# on 5.046, so this is a toolchain limit, not a gap -# mxroundtrip above 256 glibc heap abort, and 5.046 moves it to 256 instead of -# fixing it; a live bug, not a width limit -# the 88 directed job files -# verilator cannot simulate the idma_test class-based -# driver (virtual interface, input combinational region -# does not converge) -# Both are named in idma.mk next to the table that drives them, and -# util/check_jobs.py fails if either list drifts from the design. -# -# mxquant at DataWidth 512 and 1024 was on this list as a verilator crash. It was -# not: the C++ compiler was, and both widths run once idma.mk pins it. Never waive -# a leg on a toolchain symptom without testing another toolchain. +# License-free verification matrix; the run set is src/db/verify.yml name: verify @@ -49,8 +15,7 @@ on: jobs: - # Codegen hygiene, not verification: jobs.json and the generated tree must still - # describe the same design, and a second generation must be identical + # jobs.json and the generated tree must still describe the same design codegen-consistency: runs-on: ubuntu-24.04 steps: @@ -64,8 +29,7 @@ jobs: name: Check jobs.json, the CI matrix and codegen determinism run: uv run --locked make idma_verify_codegen - # One backend variant under both front ends, with its real jobs.json parameters, - # a DataWidth sweep and slang elaboration of that variant's testbench + # One backend variant: its jobs.json parameters, a width sweep, its testbench elab-backend: strategy: fail-fast: false @@ -96,8 +60,7 @@ jobs: name: Elaborate ${{ matrix.id }} run: uv run --locked make idma_verify_backend IDMA_VERIFY_ID=${{ matrix.id }} - # The four non-backend synthesis tops with their own jobs.json parameters, plus the - # default-parameter elaboration of all 12 tops and of the inst64 testbench + # The non-backend synthesis tops, plus the inst64 testbench elab-shared-tops: runs-on: ubuntu-24.04 steps: @@ -116,8 +79,7 @@ jobs: name: Elaborate the shared synthesis tops run: uv run --locked make idma_verify_shared - # Testbench tops that are not per-backend, including the three generated register - # frontends; verilator cannot parse the verification stack, so slang is the only tool + # Testbench tops; verilator cannot parse the verification stack, so slang only elab-tb-shared: runs-on: ubuntu-24.04 steps: @@ -150,8 +112,7 @@ jobs: name: Elaborate the multi-head variants and testbenches run: uv run --locked make idma_verify_multihead - # The suite list comes from src/db/verify.yml, so a suite added there gets a CI - # leg with no edit here; the workflow cannot fan out over a stale set. + # The suite list comes from the database, so a new suite needs no edit here sim-matrix: needs: [elab-backend, elab-shared-tops, elab-tb-shared] runs-on: ubuntu-24.04 @@ -171,8 +132,7 @@ jobs: echo "suites=$(uv run --locked python util/run_verify.py \ --emit-matrix suites)" >> "$GITHUB_OUTPUT" - # Real simulation, byte-exact against the DPI-C goldens for the datapath suites - # and inverted-polarity for the negative ones; run_verify.py knows which is which + # Real simulation; run_verify.py knows which suites are negative tests simulate: needs: sim-matrix strategy: @@ -194,27 +154,3 @@ jobs: - name: Simulate ${{ matrix.suite }} run: uv run --locked make idma_verify_sim_${{ matrix.suite }} IDMA_VLT_MAKEFLAGS=-j4 - - # A report, not a gate: -Wextra without -Werror, so findings do not fail the job and - # a real compile error still does. Never add it to the required checks - report-slang-extra: - runs-on: ubuntu-24.04 - steps: - - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up toolchain - uses: ./.github/actions/verify-setup - - - name: Generate RTL - run: uv run --locked make idma_hw_all - - - name: Collect slang -Wextra findings - run: uv run --locked make idma_slang_report - - - name: Upload the report - uses: actions/upload-artifact@v4 - with: - name: slang-extra - path: target/verify/slang_extra.txt diff --git a/idma.mk b/idma.mk index 296620cb..3442fe1d 100644 --- a/idma.mk +++ b/idma.mk @@ -629,17 +629,7 @@ idma_vcs_clean: .PHONY: idma_verilator_clean IDMA_VLT_DIR := $(IDMA_ROOT)/target/sim/verilator -IDMA_VLT_ARGS := --cc \ - --Wall \ - --Wno-fatal \ - +1800-2017ext+ \ - --assert \ - --error-limit 1000 \ - --hierarchical \ - --no-skip-identical - -IDMA_VLT_TOP ?= -IDMA_VLT_PARAMS ?= + # Warning classes measured at 0 occurrences over all 12 synth tops, so they gate. # Not promoted, with the measurement: UNOPTFLAT (31, includes the known @@ -651,13 +641,6 @@ IDMA_VLT_WERROR := -Werror-LATCH -Werror-MULTIDRIVEN -Werror-IMPLICIT IDMA_VLT_LINT_ARGS := --lint-only -Wno-fatal --timing $(IDMA_VLT_WERROR) \ --unroll-count 4096 --unroll-stmts 200000 -.PRECIOUS: $(IDMA_VLT_DIR)/%_elab.log - -$(IDMA_VLT_DIR)/%_elab.log: $(IDMA_BENDER_FILES) $(IDMA_FULL_TB) $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) - mkdir -p $(IDMA_VLT_DIR) - # We need a dedicated pickle here to set the defines - $(BENDER) pickle $(IDMA_PICKLE_TARGETS) --top $(IDMA_VLT_TOP) -D VERILATOR --expand-macros -o $(IDMA_VLT_DIR)/$(IDMA_VLT_TOP).sv - cd $(IDMA_VLT_DIR); $(VERILATOR) $(IDMA_VLT_ARGS) $(IDMA_VLT_PARAMS) -Mdir obj_$* $(IDMA_VLT_TOP).sv --top-module $(IDMA_VLT_TOP) 2> $*_elab.log idma_verilator_clean: rm -rf $(IDMA_VLT_DIR) @@ -704,7 +687,8 @@ idma_lint_elab: $(BENDER) script verilator -t rtl -t synth > $(IDMA_VLT_DIR)/idma_elab.f @rc=0; for top in $(IDMA_LINT_TOPS); do \ echo "--- elaborating $$top ---"; \ - $(VERILATOR) --lint-only -Wno-fatal --timing -f $(IDMA_VLT_DIR)/idma_elab.f --top-module $$top || rc=1; \ + $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_elab.f \ + --top-module $$top || rc=1; \ done; exit $$rc .PHONY: idma_lint_all diff --git a/src/db/verify.yml b/src/db/verify.yml index 5ff1f563..8f7e23cb 100644 --- a/src/db/verify.yml +++ b/src/db/verify.yml @@ -5,33 +5,24 @@ # Authors: # - Daniel Keller -# The public verification matrix, in one place. util/run_verify.py runs a suite -# from this file and emits the CI matrix from the same entries, so a leg cannot -# exist in the workflow and not locally, or the reverse. -# -# Every excluded leg is an `exclude:` entry with a reason. A leg that is simply -# absent is a bug: util/check_jobs.py fails when a negative-test case or a -# legalizer guard is neither exercised here nor excluded here. +# The public verification matrix. util/run_verify.py runs a suite from here and +# emits the CI matrix from the same entries. Excluded legs are `exclude:` entries +# with a reason; check_jobs.py fails on anything neither run nor excluded. -# DataWidth points elaborated on top of the jobs.json parameter sets; every -# jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise +# Every jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise elab_widths: [32, 64, 512, 1024] -# No jobs.json entry enables the compute datapath, so its generate branches are -# unreachable without these. ComputeOps is {transpose, mxquant, mxdequant, mxfp16} +# No jobs.json entry enables compute, so its branches are unreachable without these elab_compute: - {width: 64, ops: 15, tuning: 1, why: every sub-unit, ping-pong transpose banks} - {width: 512, ops: 15, tuning: 1, why: the wide bus, where pack/unpack is stressed} - {width: 64, ops: 8, tuning: 0, why: transpose only, single bank} - {width: 64, ops: 6, tuning: 1, why: mx only, Fp16En=0, the no-transpose branch} -# Elaborated by a dedicated leg, so the shared testbench sweep skips them: -# tb_idma_backend_* by the matching per-backend leg (handled by prefix), and the -# register frontend once per RegVariant. +# Elaborated by a dedicated leg, so the shared sweep skips them elab_covered_elsewhere: [tb_idma_reg_frontend] -# Simulation suites. Each is one CI job and one make target; each run is one leg. -# `expect: fail` inverts the polarity: the run must exit non-zero AND print token. +# One CI leg per run. `expect: fail` inverts the polarity: non-zero AND the token. suites: mxquant: @@ -70,8 +61,7 @@ suites: defines: [INC_ASSERT] sweep: {param: Quant, values: [1, 0]} - # Negative tests against the legalizer compute guards. token is the guard name, - # so a case that fires the wrong guard fails instead of passing quietly. + # token is the guard name, so a case firing the wrong guard fails mxneg: top: tb_idma_mxneg dpi: idma_mxquant_dpi @@ -92,15 +82,13 @@ suites: - {case: 11, why: "the request is never accepted, so the guard is never sampled"} - {case: 12, why: "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046"} -# Legalizer compute guards with no firing negative test. check_jobs.py fails if a -# guard is neither claimed by a mxneg run above nor named here. +# Guards with no firing negative test; check_jobs.py fails on any not named here guards_untested: - {guard: ComputeMxFp16Width, why: "only reachable from the excluded 1024 cases"} - {guard: ComputeMxdequantLengthFits, why: "case 11 is never accepted"} - {guard: ComputeDstTilelink, why: "no TileLink backend variant exists"} -# Testbench tops excluded from the simulation tier entirely. Dropped coverage, -# not silent skips; all need triage and none may be waived to make a leg green. +# Excluded from the simulation tier. Dropped coverage, not silent skips; needs triage. tops_untested: - {top: tb_idma_nd_midend_b2b, why: "hangs ([B2B] timeout); latent reset/LFSR race"} - {top: tb_idma_transpose_nd, why: "AW outside the destination allocation at 80 ns"} diff --git a/util/check_jobs.py b/util/check_jobs.py index 4dc02a97..b92f4177 100644 --- a/util/check_jobs.py +++ b/util/check_jobs.py @@ -36,9 +36,7 @@ import yaml -# Directed job files that exist on disk and are deliberately not wired into -# jobs.json: the r_axi_w_obi and r_obi_w_axi backends elaborate with -# ErrorHandling=0, so the error tests do not apply to them. +# On disk but deliberately unwired: these backends elaborate with ErrorHandling=0 UNWIRED_JOBS = { 'backend_r_axi_w_obi/error_simple.txt', 'backend_r_axi_w_obi/error_mixed.txt', @@ -115,9 +113,7 @@ def main(): errors.append('{}: {} "{}" is not defined in the sources'.format( name, field, module)) - # (d) no negative-test case and no compute guard may go silently unexercised. - # Both sides come from src/db/verify.yml, the same file that drives the runs, - # so this compares the matrix against the design rather than against a copy. + # (d) compares the run set against the design, not against a copy of itself db = {} if args.verify_db: with open(args.verify_db, 'r') as handle: @@ -144,6 +140,17 @@ def main(): for case in sorted(run_cases - defined, key=int): errors.append('case {} is run but the testbench does not define it'.format(case)) + # An exclude entry must name a value the sweep does not run + for name, suite in sorted(db.get('suites', {}).items()): + sweep = suite.get('sweep') + for entry in suite.get('exclude', []): + for value in entry.get('values', []): + if sweep and value in sweep.get('values', []): + errors.append('suite {}: {} is both run and excluded'.format(name, value)) + if not entry.get('values') and not entry.get('case'): + errors.append('suite {}: an exclude entry names neither values nor a ' + 'case'.format(name)) + if args.mxneg_guard_src and db: with open(args.mxneg_guard_src, 'r', errors='replace') as handle: guard_text = handle.read() diff --git a/util/idma_params.py b/util/idma_params.py index 908b993c..be4c822d 100644 --- a/util/idma_params.py +++ b/util/idma_params.py @@ -99,6 +99,8 @@ def entries_for(jobs, top): def main(): par = argparse.ArgumentParser(description=__doc__) + par.add_argument('--verify-db', default=None, + help='src/db/verify.yml; supplies the width and compute sweeps') par.add_argument('--top', required=True) par.add_argument('--jobs', default='jobs/jobs.json') par.add_argument('--source', action='append', default=[], metavar='GLOB', @@ -110,6 +112,17 @@ def main(): 'disables the sweep. Skipped for tops without the parameters') args = par.parse_args() + # The database is the source when supplied; the flags stay for one-off runs. + if args.verify_db: + import yaml + with open(args.verify_db, 'r') as handle: + db = yaml.safe_load(handle) or {} + if db.get('elab_widths'): + args.widths = ' '.join(str(w) for w in db['elab_widths']) + if db.get('elab_compute'): + args.compute = ' '.join('{}:{}:{}'.format(c['width'], c['ops'], c['tuning']) + for c in db['elab_compute']) + patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] sources = [] for pattern in patterns: diff --git a/util/run_verify.py b/util/run_verify.py index 481f3344..06bf875c 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -15,6 +15,7 @@ run_verify.py --emit-matrix suites print the suite names as a CI matrix run_verify.py --list print every leg, one per line run_verify.py --tb-tops print the testbench tops bender knows of + run_verify.py --prereqs mxneg print the files that suite needs built Testbench tops are asked of bender rather than listed by hand: it already owns the file set, so a testbench added to Bender.yml is elaborated without touching @@ -143,6 +144,8 @@ def main(): par.add_argument('--suite') par.add_argument('--emit-matrix', choices=['suites']) par.add_argument('--list', action='store_true') + par.add_argument('--prereqs', metavar='SUITE', + help='files the suite needs built, so make needs no per-suite rule') par.add_argument('--tb-tops', action='store_true', help='testbench tops, from bender rather than a hand-kept list') par.add_argument('--bender', default=os.environ.get('BENDER', 'bender')) @@ -157,6 +160,16 @@ def main(): if args.emit_matrix == 'suites': print(json.dumps({'suite': sorted(db['suites'])})) return 0 + if args.prereqs: + if args.prereqs not in db['suites']: + print('error: no suite named {}'.format(args.prereqs), file=sys.stderr) + return 1 + suite = db['suites'][args.prereqs] + needed = [os.path.join(args.vlt_dir, suite['top'] + '.f')] + if suite.get('dpi'): + needed.append(os.path.join(args.vlt_dir, suite['dpi'] + '.o')) + print(' '.join(needed)) + return 0 if args.tb_tops: targets = [] for target in args.target: diff --git a/verify.mk b/verify.mk index 4fba76d2..9fbfc0c6 100644 --- a/verify.mk +++ b/verify.mk @@ -13,14 +13,10 @@ # Public verification # --------------- -# License-free elaboration and simulation gates, run on verilator and slang only, -# so fork PRs (which skip the proprietary EDA sims) still get a real signal. -# Every top-level target below is one CI leg and reproduces a red check locally. +# One CI leg per top-level target; each reproduces a red check locally. .PHONY: idma_verify_codegen idma_verify_backend idma_verify_shared idma_verify_multihead -.PHONY: idma_verify_tb_shared idma_verify_sim_mxquant idma_verify_sim_mxroundtrip -.PHONY: idma_verify_sim_transpose idma_verify_sim_transpose_midend -.PHONY: idma_verify_sim_mxclear idma_verify_sim_mxneg +.PHONY: idma_verify_tb_shared .PHONY: idma_verify_all idma_lint_params idma_slang_elab idma_slang_tb idma_slang_report .PHONY: idma_verify_clean @@ -28,24 +24,11 @@ IDMA_TOP ?= # Backend variant a leg covers IDMA_VERIFY_ID ?= rw_axi -# DataWidth points elaborated on top of the jobs.json parameter sets. Every -# jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise. -IDMA_ELAB_WIDTHS ?= 32 64 512 1024 - -# Compute-enabled configurations, as DataWidth:ComputeOps:ComputeTuning. No -# jobs.json entry enables the compute datapath, so its generate branches are -# unreachable otherwise. ComputeOps is {transpose, mxquant, mxdequant, mxfp16}. -# 64:15:1 every sub-unit, ping-pong transpose banks -# 512:15:1 the wide bus, where the pack/unpack loops are stressed -# 64:8:0 transpose only, single bank; also the no-mxquant/no-mxdequant branches -# 64:6:1 mx only, Fp16En=0; also the no-transpose branch -IDMA_ELAB_COMPUTE ?= 64:15:1 512:15:1 64:8:0 64:6:1 - +IDMA_VERIFY_DB := $(IDMA_ROOT)/src/db/verify.yml IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang IDMA_SLANG_VERSION ?= 11.0.0 -# pyslang ships the slang driver itself. Never call a site `slang` wrapper: one -# turns a SIGSEGV into rc=0 with no output, which makes the gate vacuous. +# pyslang ships the driver; a site `slang` wrapper can turn a SIGSEGV into rc=0 SLANG ?= $(UV) run --with pyslang==$(IDMA_SLANG_VERSION) python \ $(IDMA_UTIL_DIR)/slang_elab.py IDMA_SLANG_ARGS := -Werror --error-limit 0 @@ -63,17 +46,17 @@ IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synt # Out-of-tree multi-head variants; license-free but generated only on request IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi -# Generated register frontends, as RegVariant:module. They share a parameter and -# port list, so tb_idma_reg_frontend - the only public concrete binding - elaborates -# each of them; without this reg64_2d and reg64_1d are elaborated by nothing. Only -# variant 0 is simulated; the testbench refuses to run on the other two. +# RegVariant:module; without this reg64_2d and reg64_1d are elaborated by nothing IDMA_REG_VARIANTS ?= 3:idma_reg32_3d 2:idma_reg64_2d 1:idma_reg64_1d -# Testbench tops, asked of bender rather than listed here: it already owns the -# file set, so a testbench added to Bender.yml is elaborated without editing this -# file. Deferred (=), so bender only runs when a leg actually needs the list. -IDMA_TB_SHARED_TOPS = $(shell BENDER=$(BENDER) $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py \ - --tb-tops $(patsubst -t%,--target %,$(IDMA_SLANG_TB_T))) +# Testbench tops from bender. A file target, not $(shell): make discards a +# $(shell) exit status, so a failure would empty the list and the leg pass. +$(IDMA_VERIFY_DIR)/tb_tops.txt: $(IDMA_ROOT)/Bender.yml $(IDMA_VERIFY_DB) + mkdir -p $(@D) + BENDER=$(BENDER) $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --tb-tops \ + $(patsubst -t%,--target %,$(IDMA_SLANG_TB_T)) > $@.tmp + test -s $@.tmp + mv $@.tmp $@ IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src/**/*.sv' \ --source '$(IDMA_ROOT)/test/**/*.sv' @@ -82,8 +65,7 @@ IDMA_SOURCE_GLOBS := --source '$(IDMA_RTL_DIR)/*.sv' --source '$(IDMA_ROOT)/src define idma_elab_cfg mkdir -p $(IDMA_VERIFY_DIR) $(PYTHON) $(IDMA_UTIL_DIR)/idma_params.py --top $1 \ - --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --widths "$(IDMA_ELAB_WIDTHS)" \ - --compute "$(IDMA_ELAB_COMPUTE)" \ + --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --verify-db $(IDMA_VERIFY_DB) \ $(IDMA_SOURCE_GLOBS) > $(IDMA_VERIFY_DIR)/$1.cfg endef @@ -101,9 +83,7 @@ $(IDMA_SLANG_DIR)/tb.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $( $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) > $@ -# Verilator elaboration of IDMA_TOP over every jobs.json configuration and width -# point. verilator rejects an unknown -G name; slang ignores it, which is why -# idma_params.py checks the names against the module header itself. +# slang ignores an unknown -G, so idma_params.py checks names against the header idma_lint_params: $(IDMA_VLT_DIR)/idma_verify.f @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } $(call idma_elab_cfg,$(IDMA_TOP)) @@ -152,12 +132,14 @@ idma_verify_shared: idma_lint_all $(MAKE) idma_slang_elab IDMA_TOP=$$top; \ done -idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f +idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt @set -e; for id in $(IDMA_FE_IDS); do \ case " $(IDMA_REG_VARIANTS) " in *":idma_$$id "*) ;; \ *) echo "error: no IDMA_REG_VARIANTS entry elaborates idma_$$id"; exit 1;; esac; \ done - set -e; for top in $(IDMA_TB_SHARED_TOPS); do \ + @test -s $(IDMA_VERIFY_DIR)/tb_tops.txt || \ + { echo "error: no testbench tops; bender returned nothing"; exit 1; } + set -e; for top in $$(cat $(IDMA_VERIFY_DIR)/tb_tops.txt); do \ echo "--- slang $$top ---"; \ $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $$top \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ @@ -202,20 +184,13 @@ idma_verify_multihead: IDMA_VLT_SIM_T := -t rtl -t idma_test -t simulation -t synth IDMA_VLT_MAKEFLAGS ?= -# verilator --timing compiles to C++20 coroutines, which g++ 11 miscompiles: the -# wide compute testbenches then SIGSEGV before time 0. Pick the newest available -# g++ rather than whatever `g++` happens to be; ubuntu-24.04 defaults to 13. -IDMA_VLT_CXX ?= $(firstword $(foreach C,g++-14 g++-13 g++-13.2.0 g++-12 g++, \ - $(if $(shell command -v $C 2>/dev/null),$C))) -IDMA_VLT_CXX_MAJOR := $(shell $(IDMA_VLT_CXX) -dumpfullversion -dumpversion 2>/dev/null | cut -d. -f1) +# verilator --timing lowers to C++20 coroutines; g++ 11 miscompiles them. +# Recursive on purpose: every reference is in a recipe, so a plain build never probes. +IDMA_VLT_CXX = $(shell for c in g++-14 g++-13 g++-13.2.0 g++-12 g++; do \ + command -v $$c >/dev/null 2>&1 && { echo $$c; break; }; done) +IDMA_VLT_CXX_MAJOR = $(shell $(IDMA_VLT_CXX) -dumpversion 2>/dev/null | cut -d. -f1) -# Prepended, so a caller passing IDMA_VLT_MAKEFLAGS=-j4 adds to the toolchain -# rather than silently replacing it -IDMA_VLT_SIM := VERILATOR="$(VERILATOR)" \ - IDMA_VLT_MAKEFLAGS="CXX=$(IDMA_VLT_CXX) LINK=$(IDMA_VLT_CXX) $(IDMA_VLT_MAKEFLAGS)" \ - $(PYTHON) $(IDMA_UTIL_DIR)/run_vlt_sim.py --dir $(IDMA_VLT_DIR) - -# Fail loudly here; the symptom otherwise is a SIGSEGV with an empty log +# Fail here; the symptom otherwise is a SIGSEGV with an empty log .PHONY: idma_verify_toolchain idma_verify_toolchain: @test -n "$(IDMA_VLT_CXX)" || { echo "error: no g++ found"; exit 1; } @@ -224,8 +199,7 @@ idma_verify_toolchain: echo " lowering needs g++ 12 or newer. Set IDMA_VLT_CXX=."; \ exit 1; } -IDMA_VERIFY_DB := $(IDMA_ROOT)/src/db/verify.yml -IDMA_VERIFY_RUN := VERILATOR="$(VERILATOR)" \ +IDMA_VERIFY_RUN = VERILATOR="$(VERILATOR)" \ IDMA_VLT_MAKEFLAGS="CXX=$(IDMA_VLT_CXX) LINK=$(IDMA_VLT_CXX) $(IDMA_VLT_MAKEFLAGS)" \ $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --vlt-dir $(IDMA_VLT_DIR) @@ -233,38 +207,16 @@ $(IDMA_VLT_DIR)/%.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(IDM mkdir -p $(IDMA_VLT_DIR) $(BENDER) script verilator $(IDMA_VLT_SIM_T) --top $* > $@ -# verilator hands a .c file to the C++ driver, so the DPI goldens are compiled -# here and linked in. idma_mxquant_dpi and idma_transpose_dpi both export -# gm_load/gm_get and must never end up in the same binary. +# idma_mxquant_dpi and idma_transpose_dpi both export gm_load/gm_get; never link both $(IDMA_VLT_DIR)/%_dpi.o: $(IDMA_ROOT)/test/%_dpi.c mkdir -p $(IDMA_VLT_DIR) $(CC) -c -O2 -fPIC $< -o $@ -idma_verify_sim_mxquant: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxquant.f \ - $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - $(IDMA_VERIFY_RUN) --suite mxquant - -idma_verify_sim_mxroundtrip: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxroundtrip.f \ - $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - $(IDMA_VERIFY_RUN) --suite mxroundtrip - -idma_verify_sim_transpose: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_otf_transpose.f \ - $(IDMA_VLT_DIR)/idma_transpose_dpi.o - $(IDMA_VERIFY_RUN) --suite transpose - -idma_verify_sim_transpose_midend: idma_verify_toolchain \ - $(IDMA_VLT_DIR)/tb_idma_transpose_midend.f \ - $(IDMA_VLT_DIR)/idma_transpose_dpi.o - $(IDMA_VERIFY_RUN) --suite transpose_midend - -# Negative tests: the run must exit non-zero AND name its guard. --assert is -# load-bearing; without it both testbenches exit 0 and the leg cannot fail. -idma_verify_sim_mxclear: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxclear.f - $(IDMA_VERIFY_RUN) --suite mxclear - -idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ - $(IDMA_VLT_DIR)/idma_mxquant_dpi.o - $(IDMA_VERIFY_RUN) --suite mxneg +# One rule for every suite; the database names the top and the DPI object +idma_verify_sim_%: idma_verify_toolchain + @p=$$($(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --prereqs $* --vlt-dir $(IDMA_VLT_DIR)) && \ + test -n "$$p" && $(MAKE) $$p + $(IDMA_VERIFY_RUN) --suite $* # --------------- @@ -274,10 +226,8 @@ idma_verify_sim_mxneg: idma_verify_toolchain $(IDMA_VLT_DIR)/tb_idma_mxneg.f \ IDMA_GEN_FILES := $(IDMA_RTL_ALL) $(IDMA_TB_ALL) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) \ $(IDMA_INCLUDE_ALL) $(IDMA_WAVE_ALL) -# Not verification: jobs.json and the generated tree must still describe the same -# design, and a second generation must be byte-identical. The zero-git-diff form -# is deliberately not used; target/rtl is gitignored on source branches, so it -# would pass unconditionally. +# Not verification. No zero-git-diff form: target/rtl is gitignored on source +# branches, so it would pass unconditionally. idma_verify_codegen: $(MAKE) idma_hw_all $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ @@ -293,8 +243,7 @@ idma_verify_codegen: md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen2.md5 diff -u $(IDMA_VERIFY_DIR)/gen1.md5 $(IDMA_VERIFY_DIR)/gen2.md5 -# Advisory only: -Wextra without -Werror, so findings do not fail the target and -# a real compile error still does. Never add this to the required checks. +# Advisory only; never add to the required checks idma_slang_report: $(IDMA_SLANG_DIR)/synth.f mkdir -p $(IDMA_VERIFY_DIR) : > $(IDMA_VERIFY_DIR)/slang_extra.log From 5ab56865a5b00bd16ff186cb091fdcb9b72988d8 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Thu, 13 Aug 2026 19:12:25 +0200 Subject: [PATCH 17/24] ci: Move the elaboration lists into the database too The simulation half read from src/db/verify.yml while the elaboration half still kept its work in make: the shared synthesis tops, the multi-head ids, and the register variants as RegVariant:module records unpacked with IFS=: in a shell loop. That is the colon-packed form this branch claimed to have removed, so the claim was only true of half the file. All three are database entries now, read through the same file-target pattern as the testbench tops. What is left in verify.mk is how to invoke the tools, not what to run: paths, flag strings and bender target sets. Every list consumer now depends on its list and checks it is non-empty. Without that, a missing list makes cat fail, the loop runs zero times and make exits 0: the shared-tops leg did exactly that while this commit was being written, losing eight elaborations behind a green run, and only the OK count showed it. --- src/db/verify.yml | 14 ++++++++++++++ util/run_verify.py | 15 +++++++++++++++ verify.mk | 44 +++++++++++++++++++++++--------------------- 3 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/db/verify.yml b/src/db/verify.yml index 8f7e23cb..13bae355 100644 --- a/src/db/verify.yml +++ b/src/db/verify.yml @@ -19,6 +19,20 @@ elab_compute: - {width: 64, ops: 8, tuning: 0, why: transpose only, single bank} - {width: 64, ops: 6, tuning: 1, why: mx only, Fp16En=0, the no-transpose branch} +# Non-backend synthesis tops, elaborated with their own jobs.json parameters +elab_shared_tops: [idma_desc64_synth, idma_nd_midend_synth, idma_mp_midend_synth, + idma_rt_midend_synth] + +# Out-of-tree multi-head variants; license-free but generated only on request +multihead_ids: [2r_axi_w_axi, 2rw_axi] + +# The generated register frontends share a parameter and port list, so the one +# public concrete binding elaborates each. Without this, 2d and 1d have no leg. +reg_variants: + - {variant: 3, module: idma_reg32_3d} + - {variant: 2, module: idma_reg64_2d} + - {variant: 1, module: idma_reg64_1d} + # Elaborated by a dedicated leg, so the shared sweep skips them elab_covered_elsewhere: [tb_idma_reg_frontend] diff --git a/util/run_verify.py b/util/run_verify.py index 06bf875c..620f0d5c 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -16,6 +16,7 @@ run_verify.py --list print every leg, one per line run_verify.py --tb-tops print the testbench tops bender knows of run_verify.py --prereqs mxneg print the files that suite needs built + run_verify.py --emit reg_variants print a list the make recipes loop over Testbench tops are asked of bender rather than listed by hand: it already owns the file set, so a testbench added to Bender.yml is elaborated without touching @@ -144,6 +145,9 @@ def main(): par.add_argument('--suite') par.add_argument('--emit-matrix', choices=['suites']) par.add_argument('--list', action='store_true') + par.add_argument('--emit', metavar='KEY', + choices=['elab_shared_tops', 'multihead_ids', 'reg_variants'], + help='print a database list for a make recipe to loop over') par.add_argument('--prereqs', metavar='SUITE', help='files the suite needs built, so make needs no per-suite rule') par.add_argument('--tb-tops', action='store_true', @@ -160,6 +164,17 @@ def main(): if args.emit_matrix == 'suites': print(json.dumps({'suite': sorted(db['suites'])})) return 0 + if args.emit: + entries = db.get(args.emit) or [] + if not entries: + print('error: {} is empty'.format(args.emit), file=sys.stderr) + return 1 + if args.emit == 'reg_variants': + for entry in entries: + print('{} {}'.format(entry['variant'], entry['module'])) + else: + print(' '.join(str(e) for e in entries)) + return 0 if args.prereqs: if args.prereqs not in db['suites']: print('error: no suite named {}'.format(args.prereqs), file=sys.stderr) diff --git a/verify.mk b/verify.mk index 9fbfc0c6..03dcb92a 100644 --- a/verify.mk +++ b/verify.mk @@ -39,18 +39,14 @@ IDMA_SLANG_SYNTH_T := -t rtl -t synth IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ -t snitch_cluster -t asic -# Non-backend synthesis tops, elaborated with their own jobs.json parameters -IDMA_SHARED_TOPS := idma_desc64_synth idma_nd_midend_synth idma_mp_midend_synth \ - idma_rt_midend_synth - -# Out-of-tree multi-head variants; license-free but generated only on request -IDMA_MULTIHEAD_IDS ?= 2r_axi_w_axi 2rw_axi - -# RegVariant:module; without this reg64_2d and reg64_1d are elaborated by nothing -IDMA_REG_VARIANTS ?= 3:idma_reg32_3d 2:idma_reg64_2d 1:idma_reg64_1d - # Testbench tops from bender. A file target, not $(shell): make discards a # $(shell) exit status, so a failure would empty the list and the leg pass. +$(IDMA_VERIFY_DIR)/%.list: $(IDMA_VERIFY_DB) + mkdir -p $(@D) + $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --emit $* > $@.tmp + test -s $@.tmp + mv $@.tmp $@ + $(IDMA_VERIFY_DIR)/tb_tops.txt: $(IDMA_ROOT)/Bender.yml $(IDMA_VERIFY_DB) mkdir -p $(@D) BENDER=$(BENDER) $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --tb-tops \ @@ -126,16 +122,19 @@ idma_verify_backend: # The four non-backend synthesis tops with their jobs.json parameters, plus the # default-parameter elaboration of every top from idma_lint_all -idma_verify_shared: idma_lint_all - set -e; for top in $(IDMA_SHARED_TOPS); do \ +idma_verify_shared: idma_lint_all $(IDMA_VERIFY_DIR)/elab_shared_tops.list + @test -s $(IDMA_VERIFY_DIR)/elab_shared_tops.list || \ + { echo "error: no shared tops listed"; exit 1; } + set -e; for top in $$(cat $(IDMA_VERIFY_DIR)/elab_shared_tops.list); do \ $(MAKE) idma_lint_params IDMA_TOP=$$top; \ $(MAKE) idma_slang_elab IDMA_TOP=$$top; \ done -idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt +idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt \ + $(IDMA_VERIFY_DIR)/reg_variants.list @set -e; for id in $(IDMA_FE_IDS); do \ - case " $(IDMA_REG_VARIANTS) " in *":idma_$$id "*) ;; \ - *) echo "error: no IDMA_REG_VARIANTS entry elaborates idma_$$id"; exit 1;; esac; \ + grep -q " idma_$$id$$" $(IDMA_VERIFY_DIR)/reg_variants.list || \ + { echo "error: no reg_variants entry elaborates idma_$$id"; exit 1; }; \ done @test -s $(IDMA_VERIFY_DIR)/tb_tops.txt || \ { echo "error: no testbench tops; bender returned nothing"; exit 1; } @@ -144,23 +143,26 @@ idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $$top \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done - set -e; for entry in $(IDMA_REG_VARIANTS); do \ - IFS=: read -r v mod <<< "$$entry"; \ + @test -s $(IDMA_VERIFY_DIR)/reg_variants.list || \ + { echo "error: no register variants listed"; exit 1; } + set -e; while read -r v mod; do \ echo "--- slang tb_idma_reg_frontend [$$mod] ---"; \ $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top tb_idma_reg_frontend -GRegVariant=$$v \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ - done + done < $(IDMA_VERIFY_DIR)/reg_variants.list # Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the # two extra synthesis tops and the two multi-head testbenches -idma_verify_multihead: - $(MAKE) idma_hw_all idma_add_all IDMA_ADD_IDS="$(IDMA_MULTIHEAD_IDS)" +idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list + @test -s $(IDMA_VERIFY_DIR)/multihead_ids.list || \ + { echo "error: no multi-head ids listed"; exit 1; } + $(MAKE) idma_hw_all idma_add_all IDMA_ADD_IDS="$$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list)" mkdir -p $(IDMA_VLT_DIR) $(IDMA_SLANG_DIR) $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_VLT_DIR)/idma_multihead.f $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_SLANG_DIR)/mh_synth.f $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead -t add_ids \ > $(IDMA_SLANG_DIR)/mh_tb.f - set -e; for id in $(IDMA_MULTIHEAD_IDS); do \ + set -e; for id in $$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list); do \ echo "--- verilator idma_backend_synth_$$id ---"; \ $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_multihead.f \ --top-module idma_backend_synth_$$id; \ From 5f473fe91b6345a593c857df5dba6f005ac40116 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Fri, 14 Aug 2026 12:41:05 +0200 Subject: [PATCH 18/24] ci: Take the local suite list from the database as well The CI fan-out read the suites from src/db/verify.yml while idma_verify_all still named its six, so adding a suite gave it a CI leg and no local run. That is the duplication this branch removed from the workflow, recreated on the make side and pointing the other way. Both sides read the database now. Verified by adding a suite to a scratch copy: it appears in the CI matrix, in the local list and in its prerequisites without any other edit. --- util/run_verify.py | 4 ++-- verify.mk | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/util/run_verify.py b/util/run_verify.py index 620f0d5c..1b12a367 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -146,7 +146,7 @@ def main(): par.add_argument('--emit-matrix', choices=['suites']) par.add_argument('--list', action='store_true') par.add_argument('--emit', metavar='KEY', - choices=['elab_shared_tops', 'multihead_ids', 'reg_variants'], + choices=['elab_shared_tops', 'multihead_ids', 'reg_variants', 'suites'], help='print a database list for a make recipe to loop over') par.add_argument('--prereqs', metavar='SUITE', help='files the suite needs built, so make needs no per-suite rule') @@ -165,7 +165,7 @@ def main(): print(json.dumps({'suite': sorted(db['suites'])})) return 0 if args.emit: - entries = db.get(args.emit) or [] + entries = sorted(db['suites']) if args.emit == 'suites' else (db.get(args.emit) or []) if not entries: print('error: {} is empty'.format(args.emit), file=sys.stderr) return 1 diff --git a/verify.mk b/verify.mk index 03dcb92a..c32c6974 100644 --- a/verify.mk +++ b/verify.mk @@ -257,11 +257,15 @@ idma_slang_report: $(IDMA_SLANG_DIR)/synth.f awk '!/^\.bender\//' | sort -u > $(IDMA_VERIFY_DIR)/slang_extra.txt @echo "slang -Wextra: $$(wc -l < $(IDMA_VERIFY_DIR)/slang_extra.txt) unique iDMA findings" +# The suite list comes from the database, like the CI fan-out, so the local entry +# point cannot run a different set from the one CI runs idma_verify_all: idma_verify_codegen idma_verify_shared idma_verify_tb_shared \ - idma_verify_multihead idma_verify_sim_mxquant \ - idma_verify_sim_mxroundtrip idma_verify_sim_transpose \ - idma_verify_sim_transpose_midend \ - idma_verify_sim_mxclear idma_verify_sim_mxneg + idma_verify_multihead $(IDMA_VERIFY_DIR)/suites.list + @test -s $(IDMA_VERIFY_DIR)/suites.list || \ + { echo "error: no suites listed"; exit 1; } + set -e; for s in $$(cat $(IDMA_VERIFY_DIR)/suites.list); do \ + $(MAKE) idma_verify_sim_$$s; \ + done set -e; for id in $(IDMA_BACKEND_IDS); do \ $(MAKE) idma_verify_backend IDMA_VERIFY_ID=$$id; \ done From d9ac810b86d10c05f703f350d694fd4ad1c275b4 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Fri, 14 Aug 2026 18:44:06 +0200 Subject: [PATCH 19/24] ci: Close the gaps a second review of this branch found Two more vacuous gates. --tb-tops and --emit-matrix lacked the non-empty guard --emit already had, and ' '.join([]) still prints a newline, so test -s passed on an empty list and the testbench leg elaborated nothing while reporting success. The workflow then discarded the emitter's exit status inside $( ), which is what verify.mk warns against for $(shell): an empty suite list fanned out to zero jobs and the simulation tier read as green. The tb-top filter also matched /deps/ anywhere in an absolute path, so a checkout under such a directory filtered out every source in the repository. The backend-id matrix check was deleted on a false premise. CI fans the simulation matrix out over the database, but the backend ids are still a literal list in the workflow, so they can drift from IDMA_BACKEND_IDS; check_jobs.py compares them again. Three transpose geometries were dropped with no exclusion entry: StrbWidth 8 FullDuplex 0, and both 64-wide cases. All three pass under verilator, so they are legs now rather than a silent gap, and the suite goes from 23 to 26. idma_inst64_top traced a rw_axi_rw_init_rw_obi backend with the rw_axi macro, so the INIT and OBI channels were never traced. It uses its own variant's macro now. Also: IDMA_HJSON_ALL and IDMA_REG_CUST_ALL were used but never defined; .PHONY named a rule that does not exist while idma_hw_all was in no .PHONY; GIT and PRINTF were dead; md5sum | sort hid a missing file without pipefail; the multi-head "no-op assertion" now asserts; the generated lists rebuild when their emitter changes; SLANG resolves against the same environment as every other python call; and verify.mk's header claimed it adds no build rules while defining seven. --- .github/actions/verify-setup/action.yml | 5 ---- .github/workflows/lint.yml | 3 +-- .github/workflows/verify.yml | 5 ++-- idma.mk | 10 +++---- src/db/verify.yml | 12 +++++---- src/frontend/inst64/idma_inst64_top.sv | 7 ++--- test/frontend/tb_idma_reg_frontend.sv | 7 ++--- util/check_jobs.py | 26 +++++++++++++----- util/idma_params.py | 10 ++----- util/run_verify.py | 20 +++++++++++--- verify.mk | 36 ++++++++++++------------- 11 files changed, 75 insertions(+), 66 deletions(-) diff --git a/.github/actions/verify-setup/action.yml b/.github/actions/verify-setup/action.yml index b7b31445..1a9d2901 100644 --- a/.github/actions/verify-setup/action.yml +++ b/.github/actions/verify-setup/action.yml @@ -24,17 +24,12 @@ runs: - if: inputs.verilator == 'true' shell: bash run: sudo apt-get update && sudo apt-get install -y verilator - # Assert the major version: a silently older Verilator would still exit 0 on - # the elaboration legs while missing everything the 5.x front end catches. - if: inputs.verilator == 'true' shell: bash run: | verilator --version verilator --version | grep -qE '^Verilator 5\.' || { echo "expected Verilator 5.x from apt"; exit 1; } - # The C++ compiler decides whether the wide compute simulations run at all: - # verilator --timing lowers to C++20 coroutines and g++ 11 miscompiles them. - # ubuntu-24.04 defaults to g++ 13, so this asserts rather than installs. - if: inputs.verilator == 'true' shell: bash run: uv run --locked make idma_verify_toolchain diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0727bc26..05abfaa1 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -32,8 +32,7 @@ jobs: exclude_paths: | target/sim/vsim/wave/tpl/*.do.tpl - # Tree-scoped counterpart to lint-sv: that job reports through reviewdog with -diff, so - # it only sees lines a PR touched. This one checks all of src/ so the backlog stays at 0 + # lint-sv is -diff scoped; this one checks all of src/ lint-sv-tree: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 499c24d6..72e3fb7d 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -129,12 +129,13 @@ jobs: name: Read the suites from the verification database id: legs run: | - echo "suites=$(uv run --locked python util/run_verify.py \ - --emit-matrix suites)" >> "$GITHUB_OUTPUT" + suites=$(uv run --locked python util/run_verify.py --emit-matrix suites) + echo "suites=$suites" >> "$GITHUB_OUTPUT" # Real simulation; run_verify.py knows which suites are negative tests simulate: needs: sim-matrix + if: needs.sim-matrix.outputs.suites != '' strategy: fail-fast: false matrix: ${{ fromJSON(needs.sim-matrix.outputs.suites) }} diff --git a/idma.mk b/idma.mk index 3442fe1d..a7bc4a7a 100644 --- a/idma.mk +++ b/idma.mk @@ -289,7 +289,6 @@ idma_reg_clean: rm -f $(IDMA_RTL_DIR)/*_reg_top.sv rm -f $(IDMA_RTL_DIR)/*_reg_pkg.sv rm -f $(IDMA_RTL_DIR)/Bender.yml - rm -f $(IDMA_REG_CUST_ALL) # assemble the required files IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) @@ -631,7 +630,7 @@ idma_vcs_clean: IDMA_VLT_DIR := $(IDMA_ROOT)/target/sim/verilator -# Warning classes measured at 0 occurrences over all 12 synth tops, so they gate. +# Warning classes measured at 0 occurrences over the synth tops, so they gate. # Not promoted, with the measurement: UNOPTFLAT (31, includes the known # idma_nd_midend stage_done loop) and PINMISSING (12, all in axi_stream and # common_cells, none iDMA-owned). @@ -649,7 +648,7 @@ idma_verilator_clean: # the only public concrete binding of the snitch_cluster-gated idma_inst64_top. -t test # adds obi_sim_mem; --top-module is load-bearing, it trims the flat filelist to the # reachable cone. Run after idma_hw_all. slang covers the same top via -# IDMA_TB_SHARED_TOPS. +# the shared testbench leg. IDMA_INST64_TB := tb_idma_inst64_axi_copy IDMA_INST64_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ -t snitch_cluster @@ -778,7 +777,8 @@ idma_sw_clean: # Phony Targets # -------------- -.PHONY: idma_all idma_add_all idma_doc_all idma_pickle_all idma_rtl_all idma_sim_all +.PHONY: idma_all idma_add_all idma_doc_all idma_pickle_all idma_sim_all +.PHONY: idma_hw_all idma_sw_all idma_nuke # Build the Starlight/Astro site (output in doc/site/dist) after staging the graphs idma_doc_all: idma_doc_site @@ -786,7 +786,7 @@ idma_doc_all: idma_doc_site idma_pickle_all: $(IDMA_PICKLE_ALL) -idma_hw_all: $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) $(IDMA_FULL_TB) $(IDMA_HJSON_ALL) \ +idma_hw_all: $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) $(IDMA_FULL_TB) \ $(IDMA_WAVE_ALL) # The IDMA_ADD_IDS aggregates; ask for it next to idma_hw_all on an add-id build diff --git a/src/db/verify.yml b/src/db/verify.yml index 13bae355..9e8ff4a0 100644 --- a/src/db/verify.yml +++ b/src/db/verify.yml @@ -26,8 +26,7 @@ elab_shared_tops: [idma_desc64_synth, idma_nd_midend_synth, idma_mp_midend_synth # Out-of-tree multi-head variants; license-free but generated only on request multihead_ids: [2r_axi_w_axi, 2rw_axi] -# The generated register frontends share a parameter and port list, so the one -# public concrete binding elaborates each. Without this, 2d and 1d have no leg. +# The one public concrete binding; without this, 2d and 1d have no leg reg_variants: - {variant: 3, module: idma_reg32_3d} - {variant: 2, module: idma_reg64_2d} @@ -58,9 +57,12 @@ suites: dpi: idma_transpose_dpi token: "[TB] ALL PASS" runs: - - {tag: otf_transpose, params: {StrbWidth: 8, FullDuplex: 1}} - # a second stimulus, backpressured, not a rerun - - {tag: otf_transpose_bp, params: {StrbWidth: 8, FullDuplex: 1}, plusargs: ["+BP"]} + - {tag: otf_transpose, params: {StrbWidth: 8, FullDuplex: 1}} + # +BP is a second stimulus, backpressured, not a rerun + - {tag: otf_transpose_bp, params: {StrbWidth: 8, FullDuplex: 1}, plusargs: ["+BP"]} + - {tag: otf_transpose_8_0, params: {StrbWidth: 8, FullDuplex: 0}, plusargs: ["+BP"]} + - {tag: otf_transpose_64_1, params: {StrbWidth: 64, FullDuplex: 1}, plusargs: ["+BP"]} + - {tag: otf_transpose_64_0, params: {StrbWidth: 64, FullDuplex: 0}, plusargs: ["+BP"]} transpose_midend: top: tb_idma_transpose_midend diff --git a/src/frontend/inst64/idma_inst64_top.sv b/src/frontend/inst64/idma_inst64_top.sv index 7702f3df..7b2c3542 100644 --- a/src/frontend/inst64/idma_inst64_top.sv +++ b/src/frontend/inst64/idma_inst64_top.sv @@ -8,10 +8,7 @@ `include "common_cells/registers.svh" `include "common_cells/assertions.svh" `include "idma/typedef.svh" -// the tracer macro is only expanded below the `ifndef SYNTHESIS guard -`ifndef SYNTHESIS -`include "idma/tracer_rw_axi.svh" -`endif +`include "idma/tracer_rw_axi_rw_init_rw_obi.svh" /// Implements the tightly-coupled frontend. This module can directly be connected /// to an accelerator bus in the snitch system @@ -801,7 +798,7 @@ module idma_inst64_top #( $sformat(trace_file, "dma_trace_%05x_%05x.log", hart_id_i, c); end // attach the tracer - `IDMA_TRACER_RW_AXI(gen_backend[c].i_idma_backend_rw_axi_rw_init_rw_obi, trace_file); + `IDMA_TRACER_RW_AXI_RW_INIT_RW_OBI(gen_backend[c].i_idma_backend_rw_axi_rw_init_rw_obi, trace_file); end end `endif diff --git a/test/frontend/tb_idma_reg_frontend.sv b/test/frontend/tb_idma_reg_frontend.sv index 350986de..7bc02134 100644 --- a/test/frontend/tb_idma_reg_frontend.sv +++ b/test/frontend/tb_idma_reg_frontend.sv @@ -54,8 +54,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // config-clock cycles is a hang (the non-blocking read completes immediately). localparam int unsigned DeadlockCycles = 32'd2000; - // register map (idma_reg32_3d_addrmap_pkg): base + per-stream stride 0x4; the reg64 - // variants have their own map and are elaboration-only, so it is not modelled here + // register map (idma_reg32_3d_addrmap_pkg): base + per-stream stride 0x4 localparam logic [31:0] REG_CONF = 32'h0000_0000; localparam logic [31:0] REG_STATUS0 = 32'h0000_0004; localparam logic [31:0] REG_NEXT_ID0 = 32'h0000_0044; @@ -184,9 +183,7 @@ module tb_idma_reg_frontend import idma_pkg::*; import apb_test::apb_driver; #( // -------------------------------------------------------------------------- // DUT // -------------------------------------------------------------------------- - // All three generated frontends share the parameter and port list; only the module name - // and the request type differ. reg64_1d (NumDim=1) emits a flat idma_req_t, widened - // back to the ND shape the stimulus works on + // All three share the parameter and port list; reg64_1d emits a flat idma_req_t if (RegVariant == 32'd3) begin : gen_reg32_3d idma_reg32_3d #( .NumRegs ( NumRegs ), diff --git a/util/check_jobs.py b/util/check_jobs.py index b92f4177..d6e32d8c 100644 --- a/util/check_jobs.py +++ b/util/check_jobs.py @@ -13,13 +13,15 @@ a) every backend id has a jobs.json entry, b) every job path referenced by jobs.json exists, c) every testbench and synth_top named by jobs.json exists in the sources, - d) every negative-test case the testbench defines is either run or named as + d) the workflow fans out over every backend id, + e) every negative-test case the testbench defines is either run or named as skipped, and every legalizer compute guard is either proven to fire by some - case or named as untested. Without (d) a new case or a new guard is added to + case or named as untested. Without (e) a new case or a new guard is added to the design and silently exercised by nothing. -The CI matrix is not checked here any more: it fans out over src/db/verify.yml, -the same file that drives the local runs, so the two cannot disagree. +The simulation matrix is not checked: CI fans it out over src/db/verify.yml, the +same file that drives the local runs. The backend-id matrix is a literal list in +the workflow, so (e) still compares it against the ids. The run list is always taken from jobs.json, never from `ls jobs/*`: four error_*.txt files exist on disk for variants built with ErrorHandling=0 and @@ -60,6 +62,8 @@ def main(): par.add_argument('--jobs-dir', default='jobs') par.add_argument('--ids', required=True, help='space-separated IDMA_BACKEND_IDS') par.add_argument('--source', action='append', default=[], metavar='GLOB') + par.add_argument('--matrix-file', default=None, + help='CI workflow that must fan out over every backend id') par.add_argument('--verify-db', default=None, help='src/db/verify.yml; the run set and its named exclusions') par.add_argument('--mxneg-tb', default=None, @@ -91,8 +95,7 @@ def main(): errors.append('{}: job "{}" references missing file {}/{}'.format( name, job, args.jobs_dir, rel)) - # (b, inverse) a job file on disk that nothing references, and is not a - # known-unwired file, is either dead or a forgotten entry + # (b, inverse) an unreferenced job file on disk is dead or a forgotten entry on_disk = set() for path in glob.glob(os.path.join(args.jobs_dir, '**', '*.txt'), recursive=True): on_disk.add(os.path.relpath(path, args.jobs_dir)) @@ -113,7 +116,16 @@ def main(): errors.append('{}: {} "{}" is not defined in the sources'.format( name, field, module)) - # (d) compares the run set against the design, not against a copy of itself + # (d) the backend-id matrix is a literal list in the workflow, so it can drift + if args.matrix_file: + with open(args.matrix_file, 'r') as handle: + matrix = handle.read() + for backend_id in args.ids.split(): + if not re.search(r'^\s*-\s*' + re.escape(backend_id) + r'\s*$', matrix, re.M): + errors.append('{} has no matrix leg for backend id {}'.format( + args.matrix_file, backend_id)) + + # (e) compares the run set against the design, not against a copy of itself db = {} if args.verify_db: with open(args.verify_db, 'r') as handle: diff --git a/util/idma_params.py b/util/idma_params.py index be4c822d..c055832a 100644 --- a/util/idma_params.py +++ b/util/idma_params.py @@ -24,13 +24,7 @@ import re import sys -# Configurations that sweep the bus width on top of the jobs.json parameters. -# Every jobs.json entry pins DataWidth=32, so nothing wider is elaborated by -# the directed matrix. -DEFAULT_WIDTHS = [32, 64, 512, 1024] - -# Parameters a compute-enabled configuration sets. jobs.json never enables the -# compute datapath, so its generate branches are unreachable without this sweep. +# Parameters a compute-enabled configuration sets COMPUTE_PARAMS = ('EnableCompute', 'ComputeOps', 'ComputeTuning') @@ -105,7 +99,7 @@ def main(): par.add_argument('--jobs', default='jobs/jobs.json') par.add_argument('--source', action='append', default=[], metavar='GLOB', help='glob of SystemVerilog sources to search for the module header') - par.add_argument('--widths', default=' '.join(str(w) for w in DEFAULT_WIDTHS), + par.add_argument('--widths', default='', help='space-separated DataWidth sweep; empty disables the sweep') par.add_argument('--compute', default='', metavar='WIDTH:OPS:TUNING', help='space-separated compute-enabled configurations; empty ' diff --git a/util/run_verify.py b/util/run_verify.py index 1b12a367..674b4c2b 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -53,6 +53,8 @@ def legs(suite_name, suite): 'plusargs': run.get('plusargs', []), } return + if 'sweep' not in suite: + raise KeyError('suite {} has neither runs nor sweep'.format(suite_name)) sweep = suite['sweep'] for value in sweep['values']: yield { @@ -88,8 +90,10 @@ def tb_tops(db, bender, targets): found = [] decl = re.compile(r'^\s*module\s+(tb_\w+)', re.M) for path in bender_sources(bender, targets): - # only iDMA's own testbenches; dependencies bring their own tb_* modules - if '.bender/' in path or '/deps/' in path: + # only iDMA's own testbenches; relative, so a checkout under a path + # containing deps/ does not filter out the whole repository + rel = os.path.relpath(path, ROOT) + if rel.startswith('..') or rel.startswith(('.bender/', 'deps/')): continue try: with open(path, 'r', errors='replace') as handle: @@ -115,6 +119,9 @@ def run_suite(name, db, args): print('error: suite {} has no legs'.format(name)) return 1 for leg in planned: + if not leg['token']: + print('error: leg {} has no token'.format(leg['tag']), file=sys.stderr) + return 1 cmd = [sys.executable, os.path.join(HERE, 'run_vlt_sim.py'), '--dir', vlt_dir, '--top', suite['top'], '--flist', flist, '--tag', leg['tag'], '--token', leg['token']] @@ -162,6 +169,9 @@ def main(): db = load(args.db) if args.emit_matrix == 'suites': + if not db.get('suites'): + print('error: the database lists no suites', file=sys.stderr) + return 1 print(json.dumps({'suite': sorted(db['suites'])})) return 0 if args.emit: @@ -189,7 +199,11 @@ def main(): targets = [] for target in args.target: targets += ['-t', target] - print(' '.join(tb_tops(db, args.bender, targets))) + tops = tb_tops(db, args.bender, targets) + if not tops: + print('error: bender returned no testbench tops', file=sys.stderr) + return 1 + print(' '.join(tops)) return 0 if args.list: for name in sorted(db['suites']): diff --git a/verify.mk b/verify.mk index c32c6974..8b38c10a 100644 --- a/verify.mk +++ b/verify.mk @@ -5,9 +5,7 @@ # Authors: # - Daniel Keller -# License-free verification: elaboration and simulation on verilator and slang -# only. Included by idma.mk, whose variables it uses; it adds no build rules, so -# building iDMA never reads it. +# License-free verification (verilator + slang); no build target depends on it # --------------- # Public verification @@ -28,8 +26,8 @@ IDMA_VERIFY_DB := $(IDMA_ROOT)/src/db/verify.yml IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang IDMA_SLANG_VERSION ?= 11.0.0 -# pyslang ships the driver; a site `slang` wrapper can turn a SIGSEGV into rc=0 -SLANG ?= $(UV) run --with pyslang==$(IDMA_SLANG_VERSION) python \ +SLANG ?= $(UV) run --locked --project $(IDMA_ROOT) \ + --with pyslang==$(IDMA_SLANG_VERSION) python \ $(IDMA_UTIL_DIR)/slang_elab.py IDMA_SLANG_ARGS := -Werror --error-limit 0 # -Wno-finish-num covers 9 $fatal("") calls in common_verification; no @@ -39,15 +37,15 @@ IDMA_SLANG_SYNTH_T := -t rtl -t synth IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ -t snitch_cluster -t asic -# Testbench tops from bender. A file target, not $(shell): make discards a -# $(shell) exit status, so a failure would empty the list and the leg pass. -$(IDMA_VERIFY_DIR)/%.list: $(IDMA_VERIFY_DB) +# A file target, not $(shell): make discards a $(shell) exit status +$(IDMA_VERIFY_DIR)/%.list: $(IDMA_VERIFY_DB) $(IDMA_UTIL_DIR)/run_verify.py mkdir -p $(@D) $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --emit $* > $@.tmp test -s $@.tmp mv $@.tmp $@ -$(IDMA_VERIFY_DIR)/tb_tops.txt: $(IDMA_ROOT)/Bender.yml $(IDMA_VERIFY_DB) +$(IDMA_VERIFY_DIR)/tb_tops.txt: $(IDMA_ROOT)/Bender.yml $(IDMA_VERIFY_DB) \ + $(IDMA_UTIL_DIR)/run_verify.py mkdir -p $(@D) BENDER=$(BENDER) $(PYTHON) $(IDMA_UTIL_DIR)/run_verify.py --tb-tops \ $(patsubst -t%,--target %,$(IDMA_SLANG_TB_T)) > $@.tmp @@ -79,7 +77,6 @@ $(IDMA_SLANG_DIR)/tb.f: $(IDMA_BENDER_FILES) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $( $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) > $@ -# slang ignores an unknown -G, so idma_params.py checks names against the header idma_lint_params: $(IDMA_VLT_DIR)/idma_verify.f @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } $(call idma_elab_cfg,$(IDMA_TOP)) @@ -120,8 +117,7 @@ idma_verify_backend: $(MAKE) idma_slang_elab IDMA_TOP=idma_backend_synth_$(IDMA_VERIFY_ID) $(MAKE) idma_slang_tb IDMA_TOP=tb_idma_backend_$(IDMA_VERIFY_ID) -# The four non-backend synthesis tops with their jobs.json parameters, plus the -# default-parameter elaboration of every top from idma_lint_all +# Non-backend synthesis tops, plus every top from idma_lint_all idma_verify_shared: idma_lint_all $(IDMA_VERIFY_DIR)/elab_shared_tops.list @test -s $(IDMA_VERIFY_DIR)/elab_shared_tops.list || \ { echo "error: no shared tops listed"; exit 1; } @@ -151,8 +147,7 @@ idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done < $(IDMA_VERIFY_DIR)/reg_variants.list -# Out-of-tree multi-head build: regenerate with the extra ids, then elaborate the -# two extra synthesis tops and the two multi-head testbenches +# Out-of-tree multi-head build idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list @test -s $(IDMA_VERIFY_DIR)/multihead_ids.list || \ { echo "error: no multi-head ids listed"; exit 1; } @@ -175,8 +170,11 @@ idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list $(SLANG) -f $(IDMA_SLANG_DIR)/mh_tb.f --top $$tb \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done - # the tracked aggregates never saw the extra ids, so this is a no-op assertion $(MAKE) idma_hw_all + @for id in $$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list); do \ + grep -q "$$id" $(IDMA_FULL_RTL) && \ + { echo "error: $$id leaked into $(IDMA_FULL_RTL)"; exit 1; }; \ + done; echo "add-ids stayed out of the tracked aggregate" # --------------- @@ -228,21 +226,21 @@ idma_verify_sim_%: idma_verify_toolchain IDMA_GEN_FILES := $(IDMA_RTL_ALL) $(IDMA_TB_ALL) $(IDMA_FULL_RTL) $(IDMA_FULL_TB) \ $(IDMA_INCLUDE_ALL) $(IDMA_WAVE_ALL) -# Not verification. No zero-git-diff form: target/rtl is gitignored on source -# branches, so it would pass unconditionally. +# Not verification; no zero-git-diff form, target/rtl is gitignored here idma_verify_codegen: $(MAKE) idma_hw_all $(PYTHON) $(IDMA_UTIL_DIR)/check_jobs.py --ids "$(IDMA_BACKEND_IDS)" \ --jobs $(IDMA_ROOT)/$(IDMA_JOBS_JSON) --jobs-dir $(IDMA_ROOT)/jobs \ --verify-db $(IDMA_VERIFY_DB) \ + --matrix-file $(IDMA_ROOT)/.github/workflows/verify.yml \ --mxneg-tb $(IDMA_ROOT)/test/tb_idma_mxneg.sv \ --mxneg-guard-src $(IDMA_ROOT)/src/backend/tpl/idma_legalizer.sv.tpl \ $(IDMA_SOURCE_GLOBS) mkdir -p $(IDMA_VERIFY_DIR) - md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 + set -o pipefail; md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen1.md5 $(MAKE) idma_rtl_clean idma_reg_clean $(MAKE) idma_hw_all - md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen2.md5 + set -o pipefail; md5sum $(IDMA_GEN_FILES) | sort -k2 > $(IDMA_VERIFY_DIR)/gen2.md5 diff -u $(IDMA_VERIFY_DIR)/gen1.md5 $(IDMA_VERIFY_DIR)/gen2.md5 # Advisory only; never add to the required checks From 2e7f4826bf51009e09d5c453edbb87d4e5560b0f Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Mon, 17 Aug 2026 09:30:02 +0200 Subject: [PATCH 20/24] frontend: Wrap the inst64 tracer invocation to the line limit The per-variant macro name is longer than the rw_axi one it replaced, which pushed the call to 112 characters and failed the tree-scoped Verible gate. --- src/frontend/inst64/idma_inst64_top.sv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/frontend/inst64/idma_inst64_top.sv b/src/frontend/inst64/idma_inst64_top.sv index 7b2c3542..0ce1b005 100644 --- a/src/frontend/inst64/idma_inst64_top.sv +++ b/src/frontend/inst64/idma_inst64_top.sv @@ -798,7 +798,8 @@ module idma_inst64_top #( $sformat(trace_file, "dma_trace_%05x_%05x.log", hart_id_i, c); end // attach the tracer - `IDMA_TRACER_RW_AXI_RW_INIT_RW_OBI(gen_backend[c].i_idma_backend_rw_axi_rw_init_rw_obi, trace_file); + `IDMA_TRACER_RW_AXI_RW_INIT_RW_OBI( + gen_backend[c].i_idma_backend_rw_axi_rw_init_rw_obi, trace_file); end end `endif From 6df8c79ffdd35bb06ae2409297e91c44a0181019 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Mon, 17 Aug 2026 10:53:48 +0200 Subject: [PATCH 21/24] build: Drop the out-of-tree aggregate rework Splitting IDMA_ADD_IDS into its own aggregate, adding an add_ids bender target and freezing IDMA_BACKEND_IDS was a build-system redesign with two breaking downstream changes, and it landed here only because the multi-head verify leg this branch adds leaves its extra ids in idma_generated.sv. Nothing on devel builds with IDMA_ADD_IDS and back, so devel never had the problem; this branch created it and then redesigned around it. The id lists, the file lists and the aggregate rules are byte-identical to devel again, and both breaking CHANGELOG entries are gone. The tracer entry stays: the per-id split is real and is what lets the recorded id stamp go. The contamination it was addressing is real, so the leg cleans up after itself instead: it drops the aggregates, regenerates, and asserts the add ids are not in the tracked file. Without that, every simulation after the multi-head leg compiles a larger model, and mxneg_6 exits 0 with its guard never firing under g++ 13 while passing under g++ 11. --- Bender.yml | 9 -------- CHANGELOG.md | 2 -- idma.mk | 63 ++++++++++++---------------------------------------- verify.mk | 19 ++++++++-------- 4 files changed, 24 insertions(+), 69 deletions(-) diff --git a/Bender.yml b/Bender.yml index 8f72c4df..b2b62ee9 100644 --- a/Bender.yml +++ b/Bender.yml @@ -62,11 +62,6 @@ sources: files: - target/rtl/idma_generated.sv - # Out-of-tree IDMA_ADD_IDS variants; empty unless the tree was generated with them - - target: all(rtl,add_ids) - files: - - target/rtl/idma_generated_add.sv - # Midends - target: rtl files: @@ -151,10 +146,6 @@ sources: - test/tb_idma_mxperf.sv - test/tb_idma_mxclear.sv - - target: all(idma_test,add_ids) - files: - - target/rtl/tb_idma_generated_add.sv - # Tightly-coupled inst64 frontend testbenches (needs the snitch_cluster-gated RTL) - target: all(idma_test, snitch_cluster) files: diff --git a/CHANGELOG.md b/CHANGELOG.md index bcf54064..587dd236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Changed - **Breaking**: the tracer is generated one header per backend id. `idma/tracer.svh` now only holds the id-independent helpers; the `IDMA_TRACER_` macro of a variant lives in `idma/tracer_.svh`, which includes `idma/tracer.svh` itself. Downstream users replace `` `include "idma/tracer.svh" `` with the per-id header of the variant they trace. -- **Breaking**: out-of-tree `IDMA_ADD_IDS` / `IDMA_ADD_FE_IDS` variants no longer land in `target/rtl/idma_generated.sv`. They are built by the new `idma_add_all` target into `target/rtl/idma_generated_add.sv` and `target/rtl/tb_idma_generated_add.sv`, and reach Bender through the new `add_ids` target; a build using `IDMA_ADD_IDS` has to select `-t add_ids`. The tracked aggregates now cover the literal `IDMA_TREE_IDS`, which is what lets make see an id-list change without a recorded stamp. -- **Breaking**: overriding `IDMA_BACKEND_IDS` or `IDMA_TREE_IDS` from the command line is no longer supported without a preceding `make idma_rtl_clean`; make cannot see a command-line id set shrink. `IDMA_ADD_IDS` is the supported extension knob. ## 0.6.5 - 2025-07-15 diff --git a/idma.mk b/idma.mk index a7bc4a7a..49f9e262 100644 --- a/idma.mk +++ b/idma.mk @@ -42,13 +42,8 @@ IDMA_OCCAMY_IDS := \ r_obi_rw_init_w_axi \ r_axi_rw_init_rw_obi \ rw_axi_rw_init_rw_obi -# The ids the tracked aggregates cover. Literal on purpose: growing or shrinking the -# tracked set is an edit to this file, which every aggregate depends on, so make sees -# it. Overriding it - or IDMA_BACKEND_IDS - from the command line is not supported -# without a preceding idma_rtl_clean; IDMA_ADD_IDS is the supported extension knob. -IDMA_TREE_IDS := $(IDMA_BASE_IDS) $(IDMA_OCCAMY_IDS) IDMA_ADD_IDS ?= -IDMA_BACKEND_IDS ?= $(IDMA_TREE_IDS) $(IDMA_ADD_IDS) +IDMA_BACKEND_IDS ?= $(IDMA_BASE_IDS) $(IDMA_OCCAMY_IDS) $(IDMA_ADD_IDS) # generated frontends IDMA_BASE_FE_IDS := reg32_3d reg64_2d reg64_1d @@ -92,8 +87,6 @@ IDMA_RTL_DOC_ALL := # Generated cumulative RTL files IDMA_FULL_RTL := $(IDMA_RTL_DIR)/idma_generated.sv IDMA_FULL_TB := $(IDMA_RTL_DIR)/tb_idma_generated.sv -IDMA_ADD_RTL := $(IDMA_RTL_DIR)/idma_generated_add.sv -IDMA_ADD_TB := $(IDMA_RTL_DIR)/tb_idma_generated_add.sv IDMA_GEN := $(IDMA_UTIL_DIR)/gen_idma.py IDMA_GEN_SRC := $(IDMA_UTIL_DIR)/mario/backend.py \ @@ -174,10 +167,8 @@ IDMA_INCLUDE_ALL += $(IDMA_INC_DIR)/compute.svh # The tracked aggregates below concatenate the tree ids only; out-of-tree ids are # collected separately so no command-line variable can change what they contain. -IDMA_TREE_RTL_ALL := $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_TREE_IDS),$X_$Y.sv)) -IDMA_ADD_RTL_ALL := $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_ADD_IDS),$X_$Y.sv)) -IDMA_TREE_TB_ALL := $(foreach Y,$(IDMA_TREE_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) -IDMA_ADD_TB_ALL := $(foreach Y,$(IDMA_ADD_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) +IDMA_RTL_ALL += $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_BACKEND_IDS),$X_$Y.sv)) +IDMA_TB_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) IDMA_WAVE_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_VSIM_DIR)/wave/backend_$Y.do) @@ -191,9 +182,8 @@ IDMA_DOC_FIG_DIR := $(IDMA_ROOT)/doc/fig IDMA_DOC_OUT_DIR := $(IDMA_ROOT)/target/doc IDMA_HTML_DIR := $(IDMA_DOC_OUT_DIR)/html IDMA_FE_DIR := $(IDMA_ROOT)/src/frontend -IDMA_FE_REGS := desc64 -IDMA_FE_REGS += $(IDMA_FE_IDS) -IDMA_TREE_FE_REGS := desc64 $(IDMA_BASE_FE_IDS) +IDMA_FE_REGS := desc64 +IDMA_FE_REGS += $(IDMA_FE_IDS) # Config-bus CPUIF for the register frontend: PeakRDL regblock --cpuif + matching wrapper # packing. apb4-flat (default, industry standard); also obi-flat / passthrough / axi4-lite-flat. @@ -291,14 +281,10 @@ idma_reg_clean: rm -f $(IDMA_RTL_DIR)/Bender.yml # assemble the required files -IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) -IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_top.sv) -IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_addrmap_pkg.sv) -IDMA_TREE_RTL_ALL += $(foreach Y,$(IDMA_TREE_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_top.sv) -IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) -IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_reg_top.sv) -IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_addrmap_pkg.sv) -IDMA_ADD_RTL_ALL += $(foreach Y,$(IDMA_ADD_FE_IDS),$(IDMA_RTL_DIR)/idma_$Y_top.sv) +IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_pkg.sv) +IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_reg_top.sv) +IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_addrmap_pkg.sv) +IDMA_RTL_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_RTL_DIR)/idma_$Y_top.sv) IDMA_RTL_DOC_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_HTML_DIR)/regs/idma_$Y_reg/index.html) # C headers @@ -312,30 +298,12 @@ IDMA_SW_ALL += $(foreach Y,$(IDMA_FE_REGS),$(IDMA_SW_DIR)/idma_$Y_raw_regs. # RTL assembly # --------------- -IDMA_RTL_ALL += $(IDMA_TREE_RTL_ALL) $(IDMA_ADD_RTL_ALL) -IDMA_TB_ALL += $(IDMA_TREE_TB_ALL) $(IDMA_ADD_TB_ALL) -# Bender hardcodes the two names below, so both files stay a cat of a per-id set. A -# set that shrinks is invisible to make - every remaining part is older than the -# target - hence the tracked aggregates take their id list from the literal -# IDMA_TREE_IDS and depend on the file that spells it out. -$(IDMA_FULL_RTL): $(IDMA_TREE_RTL_ALL) $(IDMA_ROOT)/idma.mk - $(CAT) $(IDMA_TREE_RTL_ALL) > $@ +$(IDMA_FULL_RTL): $(IDMA_RTL_ALL) + $(CAT) $(IDMA_RTL_ALL) > $@ -$(IDMA_FULL_TB): $(IDMA_TREE_TB_ALL) $(IDMA_ROOT)/idma.mk - $(CAT) $(IDMA_TREE_TB_ALL) > $@ - -# Out-of-tree IDMA_ADD_IDS variants, reached through the add_ids bender target only. -# Not part of idma_hw_all: IDMA_ADD_IDS is a command-line knob, so make cannot see -# its set shrink and this pair carries the one staleness make cannot express. Ask for -# it explicitly (idma_add_all) and it is exact; a set that shrinks between two such -# builds leaves the dropped variant behind until idma_rtl_clean. Nothing a tracked -# build parses can reach these files. -$(IDMA_ADD_RTL): $(IDMA_ADD_RTL_ALL) $(IDMA_ROOT)/idma.mk - $(CAT) /dev/null $(IDMA_ADD_RTL_ALL) > $@ - -$(IDMA_ADD_TB): $(IDMA_ADD_TB_ALL) $(IDMA_ROOT)/idma.mk - $(CAT) /dev/null $(IDMA_ADD_TB_ALL) > $@ +$(IDMA_FULL_TB): $(IDMA_TB_ALL) + $(CAT) $(IDMA_TB_ALL) > $@ # --------------- @@ -777,7 +745,7 @@ idma_sw_clean: # Phony Targets # -------------- -.PHONY: idma_all idma_add_all idma_doc_all idma_pickle_all idma_sim_all +.PHONY: idma_all idma_doc_all idma_pickle_all idma_sim_all .PHONY: idma_hw_all idma_sw_all idma_nuke # Build the Starlight/Astro site (output in doc/site/dist) after staging the graphs @@ -789,9 +757,6 @@ idma_pickle_all: $(IDMA_PICKLE_ALL) idma_hw_all: $(IDMA_FULL_RTL) $(IDMA_INCLUDE_ALL) $(IDMA_FULL_TB) \ $(IDMA_WAVE_ALL) -# The IDMA_ADD_IDS aggregates; ask for it next to idma_hw_all on an add-id build -idma_add_all: $(IDMA_ADD_RTL) $(IDMA_ADD_TB) - idma_sw_all: $(IDMA_SW_ALL) idma_sim_all: $(IDMA_VCS_DIR)/compile.sh $(IDMA_VSIM_DIR)/compile.tcl diff --git a/verify.mk b/verify.mk index 8b38c10a..b9b8c56a 100644 --- a/verify.mk +++ b/verify.mk @@ -22,6 +22,7 @@ IDMA_TOP ?= # Backend variant a leg covers IDMA_VERIFY_ID ?= rw_axi +IDMA_MULTIHEAD_PAT := 2r_axi_w_axi 2rw_axi IDMA_VERIFY_DB := $(IDMA_ROOT)/src/db/verify.yml IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang @@ -147,15 +148,16 @@ idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done < $(IDMA_VERIFY_DIR)/reg_variants.list -# Out-of-tree multi-head build +# Out-of-tree multi-head build; the aggregate is rebuilt after, so a following +# leg compiles the tracked id set and not this one idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list @test -s $(IDMA_VERIFY_DIR)/multihead_ids.list || \ { echo "error: no multi-head ids listed"; exit 1; } - $(MAKE) idma_hw_all idma_add_all IDMA_ADD_IDS="$$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list)" + $(MAKE) idma_hw_all IDMA_ADD_IDS="$$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list)" mkdir -p $(IDMA_VLT_DIR) $(IDMA_SLANG_DIR) - $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_VLT_DIR)/idma_multihead.f - $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) -t add_ids > $(IDMA_SLANG_DIR)/mh_synth.f - $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead -t add_ids \ + $(BENDER) script verilator $(IDMA_SLANG_SYNTH_T) > $(IDMA_VLT_DIR)/idma_multihead.f + $(BENDER) script flist-plus $(IDMA_SLANG_SYNTH_T) > $(IDMA_SLANG_DIR)/mh_synth.f + $(BENDER) script flist-plus $(IDMA_SLANG_TB_T) -t multihead \ > $(IDMA_SLANG_DIR)/mh_tb.f set -e; for id in $$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list); do \ echo "--- verilator idma_backend_synth_$$id ---"; \ @@ -170,11 +172,10 @@ idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list $(SLANG) -f $(IDMA_SLANG_DIR)/mh_tb.f --top $$tb \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done + rm -f $(IDMA_FULL_RTL) $(IDMA_FULL_TB) $(MAKE) idma_hw_all - @for id in $$(cat $(IDMA_VERIFY_DIR)/multihead_ids.list); do \ - grep -q "$$id" $(IDMA_FULL_RTL) && \ - { echo "error: $$id leaked into $(IDMA_FULL_RTL)"; exit 1; }; \ - done; echo "add-ids stayed out of the tracked aggregate" + @! grep -qE '$(subst $() ,|,$(IDMA_MULTIHEAD_PAT))' $(IDMA_FULL_RTL) || \ + { echo 'error: add ids leaked into $(IDMA_FULL_RTL)'; exit 1; } # --------------- From a228a91cc0bd120c6c602d6717dcfeb8126f0819 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Mon, 17 Aug 2026 11:13:02 +0200 Subject: [PATCH 22/24] ci: Cut every multi-line comment this branch added The house rule is one line and the commit message owns the why, but reviewing a diff hunk by hunk never shows a comment block as a block, so eighteen of them survived two passes. Seven were in idma.mk. One is deleted outright, having described the reverted aggregate rework; the rest are one line each, including the eight-line header on the register frontend testbench. --- idma.mk | 28 ++++++--------------------- src/backend/tpl/idma_legalizer.sv.tpl | 1 - test/frontend/tb_idma_reg_frontend.sv | 10 ++-------- util/idma_params.py | 3 +-- util/mario/tracer.py | 3 +-- util/run_verify.py | 3 +-- util/slang_elab.py | 3 +-- verify.mk | 15 +++++--------- 8 files changed, 17 insertions(+), 49 deletions(-) diff --git a/idma.mk b/idma.mk index 49f9e262..019ecb02 100644 --- a/idma.mk +++ b/idma.mk @@ -142,9 +142,7 @@ $(IDMA_INC_DIR)/tracer.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_INC_TPL)/tracer.s mkdir -p $(@D) $(call idma_gen,tracer_common,$(IDMA_INC_TPL)/tracer.svh.tpl,,,,$@) -# One tracer macro per backend id. The content is a function of the id in the target -# name, the databases and the template only, so "up to date" implies "correct" and no -# id-list state has to be recorded anywhere. +# One tracer macro per backend id, a function of the id in the target name $(IDMA_INC_DIR)/tracer_%.svh: $(IDMA_GEN) $(IDMA_GEN_SRC) $(IDMA_INC_TPL)/tracer_id.svh.tpl $(IDMA_DB_FILES) mkdir -p $(@D) $(call idma_gen,tracer,$(IDMA_INC_TPL)/tracer_id.svh.tpl,$(IDMA_DB_FILES),$*,,$@) @@ -165,8 +163,6 @@ IDMA_INCLUDE_ALL += $(IDMA_INC_DIR)/tracer.svh IDMA_INCLUDE_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_INC_DIR)/tracer_$Y.svh) IDMA_INCLUDE_ALL += $(IDMA_INC_DIR)/compute.svh -# The tracked aggregates below concatenate the tree ids only; out-of-tree ids are -# collected separately so no command-line variable can change what they contain. IDMA_RTL_ALL += $(foreach X,$(IDMA_RTL_FILES),$(foreach Y,$(IDMA_BACKEND_IDS),$X_$Y.sv)) IDMA_TB_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_RTL_DIR)/tb_idma_backend_$Y.sv) IDMA_WAVE_ALL += $(foreach Y,$(IDMA_BACKEND_IDS),$(IDMA_VSIM_DIR)/wave/backend_$Y.do) @@ -598,13 +594,9 @@ idma_vcs_clean: IDMA_VLT_DIR := $(IDMA_ROOT)/target/sim/verilator -# Warning classes measured at 0 occurrences over the synth tops, so they gate. -# Not promoted, with the measurement: UNOPTFLAT (31, includes the known -# idma_nd_midend stage_done loop) and PINMISSING (12, all in axi_stream and -# common_cells, none iDMA-owned). +# Measured at 0 occurrences over the synth tops, so they gate IDMA_VLT_WERROR := -Werror-LATCH -Werror-MULTIDRIVEN -Werror-IMPLICIT -# The unroll budget matches util/run_vlt_sim.py; without it verilator 5.020 reports -# BLKLOOPINIT on the compute pack/unpack loops at DataWidth >= 256. +# Unroll budget matches util/run_vlt_sim.py; 5.020 reports BLKLOOPINIT without it IDMA_VLT_LINT_ARGS := --lint-only -Wno-fatal --timing $(IDMA_VLT_WERROR) \ --unroll-count 4096 --unroll-stmts 200000 @@ -612,11 +604,7 @@ IDMA_VLT_LINT_ARGS := --lint-only -Wno-fatal --timing $(IDMA_VLT_WERROR) \ idma_verilator_clean: rm -rf $(IDMA_VLT_DIR) -# inst64 frontend elaboration gate: verilator --lint-only over tb_idma_inst64_axi_copy, -# the only public concrete binding of the snitch_cluster-gated idma_inst64_top. -t test -# adds obi_sim_mem; --top-module is load-bearing, it trims the flat filelist to the -# reachable cone. Run after idma_hw_all. slang covers the same top via -# the shared testbench leg. +# inst64 gate: the only public concrete binding of idma_inst64_top IDMA_INST64_TB := tb_idma_inst64_axi_copy IDMA_INST64_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ -t snitch_cluster @@ -629,18 +617,14 @@ idma_lint_inst64: $(VERILATOR) $(IDMA_VLT_LINT_ARGS) -f $(IDMA_VLT_DIR)/idma_inst64_tb.f \ --top-module $(IDMA_INST64_TB) -# Synthesis-wrapper elaboration gate: verilator elaborates every synth top so a -# port, parameter or connectivity break is caught without the proprietary EDA -# sims (which fork PRs never run). Run after idma_hw_all. +# verilator elaborates every synth top, so fork PRs catch port and param breaks IDMA_LINT_TOPS ?= $(addprefix idma_backend_synth_,$(IDMA_BACKEND_IDS)) \ idma_desc64_synth \ idma_nd_midend_synth \ idma_mp_midend_synth \ idma_rt_midend_synth -# Tree-scoped SystemVerilog style lint. The PR-annotating CI job reports through -# reviewdog with -diff, so it only ever sees lines a PR touched; this target -# checks all of src/ so pre-existing violations cannot accumulate unseen. +# lint-sv is -diff scoped; this checks all of src/ VERIBLE ?= verible-verilog-lint .PHONY: idma_lint_sv diff --git a/src/backend/tpl/idma_legalizer.sv.tpl b/src/backend/tpl/idma_legalizer.sv.tpl index bd56cf3e..4806a320 100644 --- a/src/backend/tpl/idma_legalizer.sv.tpl +++ b/src/backend/tpl/idma_legalizer.sv.tpl @@ -726,7 +726,6 @@ ${database[protocol]['legalizer_write_data_path']} (req_i.opt.compute.op == idma_pkg::COMPUTE_MXDEQUANT_FP16)) & (req_i.length % (idma_pkg::MxBlockBytes*StrbWidth) != 0)), clk_i, !rst_ni) // NOT IMPLEMENTED: dequant output length that overflows the length field - // 65-bit literal: a 64-bit length shifts by 64, which overflows a 64-bit operand `ASSERT_NEVER(ComputeMxdequantLengthFits, (ready_o & valid_i & req_i.opt.compute.enable & ((req_i.opt.compute.op == idma_pkg::COMPUTE_MXDEQUANT) | (req_i.opt.compute.op == idma_pkg::COMPUTE_MXDEQUANT_FP16)) & diff --git a/test/frontend/tb_idma_reg_frontend.sv b/test/frontend/tb_idma_reg_frontend.sv index 7bc02134..0da1ff32 100644 --- a/test/frontend/tb_idma_reg_frontend.sv +++ b/test/frontend/tb_idma_reg_frontend.sv @@ -5,14 +5,8 @@ // Authors: // - Daniel Keller -// Self-checking testbench for the iDMA register frontend (idma_reg32_3d, apb4-flat). -// `RegVariant` also binds idma_reg64_2d and idma_reg64_1d, elaboration-only and -// covered by nothing else. -// Drives the APB config slave with the standard apb_test::apb_driver against a -// controllable backend stub and checks the non-blocking next_id launch contract: -// the config read completes promptly (even under backend backpressure) and the -// launch fires exactly once when the arbiter grants. A per-read watchdog guards -// against any read that hangs. +// Self-checking testbench for the iDMA register frontend; checks the non-blocking +// next_id launch contract. RegVariant 2 and 1 are elaboration-only. `include "apb/typedef.svh" `include "apb/assign.svh" diff --git a/util/idma_params.py b/util/idma_params.py index c055832a..baf92fe6 100644 --- a/util/idma_params.py +++ b/util/idma_params.py @@ -180,8 +180,7 @@ def main(): if not ops_fields or not tuning_fields: raise SystemExit('error: cannot read compute_enable_t/compute_tuning_t; ' 'the compute sweep cannot be validated') - # Fields are declared MSB first, and mxfp16 only gates the FP16 paths of - # the MX ops, so it enables no datapath on its own. + # MSB first; mxfp16 gates FP16 paths only, so it enables no datapath alone real_ops = [f for f in ops_fields if f != 'mxfp16'] real_mask = 0 for pos, name in enumerate(reversed(ops_fields)): diff --git a/util/mario/tracer.py b/util/mario/tracer.py index 0b5783a1..15961e80 100644 --- a/util/mario/tracer.py +++ b/util/mario/tracer.py @@ -129,8 +129,7 @@ def render_tracer(prot_ids: dict, db: dict, tpl_file: str) -> str: # signals signals = '' - # Keys are direction-qualified: a protocol present on both sides (INIT) would - # otherwise emit the same key twice and the write leg drop the read channel + # direction-qualified: INIT is on both sides and would emit the key twice for read_prot in prot_ids[prot_id]['ar']: sig_dict = _flatten_dict(db[read_prot]['trace_signals']['read']) for signal in sig_dict: diff --git a/util/run_verify.py b/util/run_verify.py index 674b4c2b..eabdd8b0 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -90,8 +90,7 @@ def tb_tops(db, bender, targets): found = [] decl = re.compile(r'^\s*module\s+(tb_\w+)', re.M) for path in bender_sources(bender, targets): - # only iDMA's own testbenches; relative, so a checkout under a path - # containing deps/ does not filter out the whole repository + # relative: an absolute match on deps/ would filter out the whole repo rel = os.path.relpath(path, ROOT) if rel.startswith('..') or rel.startswith(('.bender/', 'deps/')): continue diff --git a/util/slang_elab.py b/util/slang_elab.py index 1682808c..9b89804a 100644 --- a/util/slang_elab.py +++ b/util/slang_elab.py @@ -33,8 +33,7 @@ def main(argv): if not driver.processOptions(): return 1 - # Both stages must run; `&` rather than `and` so parse failures are reported - # together with the elaboration diagnostics instead of masking them. + # `&` not `and`: both stages must run so parse errors are not masked ok = driver.parseAllSources() ok = driver.runFullCompilation() & ok return 0 if ok else 5 diff --git a/verify.mk b/verify.mk index b9b8c56a..ed6bc126 100644 --- a/verify.mk +++ b/verify.mk @@ -31,8 +31,7 @@ SLANG ?= $(UV) run --locked --project $(IDMA_ROOT) \ --with pyslang==$(IDMA_SLANG_VERSION) python \ $(IDMA_UTIL_DIR)/slang_elab.py IDMA_SLANG_ARGS := -Werror --error-limit 0 -# -Wno-finish-num covers 9 $fatal("") calls in common_verification; no -# iDMA file has one, and slang cannot scope a waiver to a dependency checkout. +# -Wno-finish-num covers common_verification; slang cannot scope it to a dependency IDMA_SLANG_TB_ARGS := --timescale=1ns/1ps -Wno-finish-num IDMA_SLANG_SYNTH_T := -t rtl -t synth IDMA_SLANG_TB_T := -t rtl -t synth -t idma_test -t simulation -t sim -t test \ @@ -105,8 +104,7 @@ idma_slang_elab: $(IDMA_SLANG_DIR)/synth.f test $$rc -eq 0 && echo "idma_slang_elab: $(IDMA_TOP) OK" || \ echo "idma_slang_elab: $(IDMA_TOP) FAILED"; exit $$rc -# slang elaboration of one testbench top; verilator cannot parse the verification -# stack at all (axi_test/apb_test type-identifier errors, randomize()-with). +# slang only; verilator cannot parse the verification stack idma_slang_tb: $(IDMA_SLANG_DIR)/tb.f @test -n "$(IDMA_TOP)" || { echo "error: set IDMA_TOP="; exit 1; } $(SLANG) -f $(IDMA_SLANG_DIR)/tb.f --top $(IDMA_TOP) \ @@ -148,8 +146,7 @@ idma_verify_tb_shared: $(IDMA_SLANG_DIR)/tb.f $(IDMA_VERIFY_DIR)/tb_tops.txt \ $(IDMA_SLANG_ARGS) $(IDMA_SLANG_TB_ARGS); \ done < $(IDMA_VERIFY_DIR)/reg_variants.list -# Out-of-tree multi-head build; the aggregate is rebuilt after, so a following -# leg compiles the tracked id set and not this one +# Out-of-tree multi-head build; the aggregate is rebuilt after idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list @test -s $(IDMA_VERIFY_DIR)/multihead_ids.list || \ { echo "error: no multi-head ids listed"; exit 1; } @@ -185,8 +182,7 @@ idma_verify_multihead: $(IDMA_VERIFY_DIR)/multihead_ids.list IDMA_VLT_SIM_T := -t rtl -t idma_test -t simulation -t synth IDMA_VLT_MAKEFLAGS ?= -# verilator --timing lowers to C++20 coroutines; g++ 11 miscompiles them. -# Recursive on purpose: every reference is in a recipe, so a plain build never probes. +# verilator --timing lowers to C++20 coroutines; g++ 11 miscompiles them IDMA_VLT_CXX = $(shell for c in g++-14 g++-13 g++-13.2.0 g++-12 g++; do \ command -v $$c >/dev/null 2>&1 && { echo $$c; break; }; done) IDMA_VLT_CXX_MAJOR = $(shell $(IDMA_VLT_CXX) -dumpversion 2>/dev/null | cut -d. -f1) @@ -256,8 +252,7 @@ idma_slang_report: $(IDMA_SLANG_DIR)/synth.f awk '!/^\.bender\//' | sort -u > $(IDMA_VERIFY_DIR)/slang_extra.txt @echo "slang -Wextra: $$(wc -l < $(IDMA_VERIFY_DIR)/slang_extra.txt) unique iDMA findings" -# The suite list comes from the database, like the CI fan-out, so the local entry -# point cannot run a different set from the one CI runs +# The suite list comes from the database, like the CI fan-out idma_verify_all: idma_verify_codegen idma_verify_shared idma_verify_tb_shared \ idma_verify_multihead $(IDMA_VERIFY_DIR)/suites.list @test -s $(IDMA_VERIFY_DIR)/suites.list || \ From a195527cf76672e9939cb46bdf4e366b4f1d9eb2 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Mon, 17 Aug 2026 11:54:59 +0200 Subject: [PATCH 23/24] ci: Fold the verification matrix into jobs.json The run set lived in src/db/verify.yml, a second database beside the one that already describes every variant, its testbench and its parameters. jobs.json is also what the internal CI generates its matrix from, so the two pipelines read two different files to answer the same question. The six suites are jobs.json entries now, each naming its testbench and carrying a verify block with its legs, expected outcome and DPI golden. The elaboration sweeps and the exclusion lists sit under a reserved _verify key. Neither shape has a synth_top, which is how the internal generator tells them apart from the variants it covers; that side is already released on the deploy tag, and its output is byte-identical with and without these entries. check_jobs.py now also requires a suite entry to name a testbench that exists, so a suite cannot reference a module nobody wrote. --- jobs/jobs.json | 984 +++++++++++++++++++++++++++++++------------- src/db/verify.yml | 116 ------ util/check_jobs.py | 21 +- util/idma_params.py | 8 +- util/run_verify.py | 19 +- verify.mk | 2 +- 6 files changed, 739 insertions(+), 411 deletions(-) delete mode 100644 src/db/verify.yml diff --git a/jobs/jobs.json b/jobs/jobs.json index 63ada665..d0cae4ab 100644 --- a/jobs/jobs.json +++ b/jobs/jobs.json @@ -1,336 +1,758 @@ { - "rw_axi" : { - "jobs" : { - "simple" : "backend_rw_axi/simple.txt", - "same_dst" : "backend_rw_axi/same_dst.txt", - "linear" : "backend_rw_axi/linear.txt", - "huge" : "backend_rw_axi/huge.txt", - "large" : "backend_rw_axi/large.txt", - "medium" : "backend_rw_axi/medium.txt", - "mixed" : "backend_rw_axi/mixed.txt", - "small" : "backend_rw_axi/small.txt", - "tiny" : "backend_rw_axi/tiny.txt", - "error_simple" : "backend_rw_axi/error_simple.txt", - "error_mixed" : "backend_rw_axi/error_mixed.txt", - "zero_transfer" : "backend_rw_axi/zero_transfer.txt" + "rw_axi": { + "jobs": { + "simple": "backend_rw_axi/simple.txt", + "same_dst": "backend_rw_axi/same_dst.txt", + "linear": "backend_rw_axi/linear.txt", + "huge": "backend_rw_axi/huge.txt", + "large": "backend_rw_axi/large.txt", + "medium": "backend_rw_axi/medium.txt", + "mixed": "backend_rw_axi/mixed.txt", + "small": "backend_rw_axi/small.txt", + "tiny": "backend_rw_axi/tiny.txt", + "error_simple": "backend_rw_axi/error_simple.txt", + "error_mixed": "backend_rw_axi/error_mixed.txt", + "zero_transfer": "backend_rw_axi/zero_transfer.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 1, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 1, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 1 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 1, + "MaskInvalidData": 1, + "RAWCouplingAvail": 1, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 1 }, - "proc_id" : "rw_axi", + "proc_id": "rw_axi", "testbench": "tb_idma_backend_rw_axi", "synth_top": "idma_backend_synth_rw_axi" }, - "r_axi_w_obi" : { - "jobs" : { - "simple" : "backend_r_axi_w_obi/simple.txt", - "same_dst" : "backend_r_axi_w_obi/same_dst.txt", - "linear" : "backend_r_axi_w_obi/linear.txt", - "huge" : "backend_r_axi_w_obi/huge.txt", - "large" : "backend_r_axi_w_obi/large.txt", - "medium" : "backend_r_axi_w_obi/medium.txt", - "mixed" : "backend_r_axi_w_obi/mixed.txt", - "small" : "backend_r_axi_w_obi/small.txt", - "tiny" : "backend_r_axi_w_obi/tiny.txt", - "zero_transfer" : "backend_r_axi_w_obi/zero_transfer.txt" + "r_axi_w_obi": { + "jobs": { + "simple": "backend_r_axi_w_obi/simple.txt", + "same_dst": "backend_r_axi_w_obi/same_dst.txt", + "linear": "backend_r_axi_w_obi/linear.txt", + "huge": "backend_r_axi_w_obi/huge.txt", + "large": "backend_r_axi_w_obi/large.txt", + "medium": "backend_r_axi_w_obi/medium.txt", + "mixed": "backend_r_axi_w_obi/mixed.txt", + "small": "backend_r_axi_w_obi/small.txt", + "tiny": "backend_r_axi_w_obi/tiny.txt", + "zero_transfer": "backend_r_axi_w_obi/zero_transfer.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 0, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 0, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "r_axi_w_obi", + "proc_id": "r_axi_w_obi", "testbench": "tb_idma_backend_r_axi_w_obi", "synth_top": "idma_backend_synth_r_axi_w_obi" }, - "r_obi_w_axi" : { - "jobs" : { - "simple" : "backend_r_obi_w_axi/simple.txt", - "same_dst" : "backend_r_obi_w_axi/same_dst.txt", - "linear" : "backend_r_obi_w_axi/linear.txt", - "huge" : "backend_r_obi_w_axi/huge.txt", - "large" : "backend_r_obi_w_axi/large.txt", - "medium" : "backend_r_obi_w_axi/medium.txt", - "mixed" : "backend_r_obi_w_axi/mixed.txt", - "small" : "backend_r_obi_w_axi/small.txt", - "tiny" : "backend_r_obi_w_axi/tiny.txt", - "zero_transfer" : "backend_r_obi_w_axi/zero_transfer.txt" + "r_obi_w_axi": { + "jobs": { + "simple": "backend_r_obi_w_axi/simple.txt", + "same_dst": "backend_r_obi_w_axi/same_dst.txt", + "linear": "backend_r_obi_w_axi/linear.txt", + "huge": "backend_r_obi_w_axi/huge.txt", + "large": "backend_r_obi_w_axi/large.txt", + "medium": "backend_r_obi_w_axi/medium.txt", + "mixed": "backend_r_obi_w_axi/mixed.txt", + "small": "backend_r_obi_w_axi/small.txt", + "tiny": "backend_r_obi_w_axi/tiny.txt", + "zero_transfer": "backend_r_obi_w_axi/zero_transfer.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 0, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 0, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "r_obi_w_axi", + "proc_id": "r_obi_w_axi", "testbench": "tb_idma_backend_r_obi_w_axi", "synth_top": "idma_backend_synth_r_obi_w_axi" }, - "snitch_read" : { - "jobs" : { - "simple" : "backend_r_axi_rw_init_rw_obi/simple.txt", - "same_dst" : "backend_r_axi_rw_init_rw_obi/same_dst.txt", - "linear" : "backend_r_axi_rw_init_rw_obi/linear.txt", - "huge" : "backend_r_axi_rw_init_rw_obi/huge.txt", - "large" : "backend_r_axi_rw_init_rw_obi/large.txt", - "medium" : "backend_r_axi_rw_init_rw_obi/medium.txt", - "mixed" : "backend_r_axi_rw_init_rw_obi/mixed.txt", - "small" : "backend_r_axi_rw_init_rw_obi/small.txt", - "tiny" : "backend_r_axi_rw_init_rw_obi/tiny.txt", - "zero_transfer" : "backend_r_axi_rw_init_rw_obi/zero_transfer.txt" + "snitch_read": { + "jobs": { + "simple": "backend_r_axi_rw_init_rw_obi/simple.txt", + "same_dst": "backend_r_axi_rw_init_rw_obi/same_dst.txt", + "linear": "backend_r_axi_rw_init_rw_obi/linear.txt", + "huge": "backend_r_axi_rw_init_rw_obi/huge.txt", + "large": "backend_r_axi_rw_init_rw_obi/large.txt", + "medium": "backend_r_axi_rw_init_rw_obi/medium.txt", + "mixed": "backend_r_axi_rw_init_rw_obi/mixed.txt", + "small": "backend_r_axi_rw_init_rw_obi/small.txt", + "tiny": "backend_r_axi_rw_init_rw_obi/tiny.txt", + "zero_transfer": "backend_r_axi_rw_init_rw_obi/zero_transfer.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 1, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 1, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "r_axi_rw_init_rw_obi", + "proc_id": "r_axi_rw_init_rw_obi", "testbench": "tb_idma_backend_r_axi_rw_init_rw_obi", "synth_top": "idma_backend_synth_r_axi_rw_init_rw_obi" }, - "snitch_write" : { - "jobs" : { - "simple" : "backend_r_obi_rw_init_w_axi/simple.txt", - "same_dst" : "backend_r_obi_rw_init_w_axi/same_dst.txt", - "linear" : "backend_r_obi_rw_init_w_axi/linear.txt", - "huge" : "backend_r_obi_rw_init_w_axi/huge.txt", - "large" : "backend_r_obi_rw_init_w_axi/large.txt", - "medium" : "backend_r_obi_rw_init_w_axi/medium.txt", - "mixed" : "backend_r_obi_rw_init_w_axi/mixed.txt", - "small" : "backend_r_obi_rw_init_w_axi/small.txt", - "tiny" : "backend_r_obi_rw_init_w_axi/tiny.txt", - "zero_transfer" : "backend_r_obi_rw_init_w_axi/zero_transfer.txt" + "snitch_write": { + "jobs": { + "simple": "backend_r_obi_rw_init_w_axi/simple.txt", + "same_dst": "backend_r_obi_rw_init_w_axi/same_dst.txt", + "linear": "backend_r_obi_rw_init_w_axi/linear.txt", + "huge": "backend_r_obi_rw_init_w_axi/huge.txt", + "large": "backend_r_obi_rw_init_w_axi/large.txt", + "medium": "backend_r_obi_rw_init_w_axi/medium.txt", + "mixed": "backend_r_obi_rw_init_w_axi/mixed.txt", + "small": "backend_r_obi_rw_init_w_axi/small.txt", + "tiny": "backend_r_obi_rw_init_w_axi/tiny.txt", + "zero_transfer": "backend_r_obi_rw_init_w_axi/zero_transfer.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 1, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 1, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "r_obi_rw_init_w_axi", + "proc_id": "r_obi_rw_init_w_axi", "testbench": "tb_idma_backend_r_obi_rw_init_w_axi", "synth_top": "idma_backend_synth_r_obi_rw_init_w_axi" }, - "snitch_read_write" : { - "jobs" : { - "simple" : "backend_rw_axi_rw_init_rw_obi/simple.txt", - "same_dst" : "backend_rw_axi_rw_init_rw_obi/same_dst.txt", - "linear" : "backend_rw_axi_rw_init_rw_obi/linear.txt", - "huge" : "backend_rw_axi_rw_init_rw_obi/huge.txt", - "large" : "backend_rw_axi_rw_init_rw_obi/large.txt", - "medium" : "backend_rw_axi_rw_init_rw_obi/medium.txt", - "mixed" : "backend_rw_axi_rw_init_rw_obi/mixed.txt", - "small" : "backend_rw_axi_rw_init_rw_obi/small.txt", - "tiny" : "backend_rw_axi_rw_init_rw_obi/tiny.txt", - "zero_transfer" : "backend_rw_axi_rw_init_rw_obi/zero_transfer.txt" + "snitch_read_write": { + "jobs": { + "simple": "backend_rw_axi_rw_init_rw_obi/simple.txt", + "same_dst": "backend_rw_axi_rw_init_rw_obi/same_dst.txt", + "linear": "backend_rw_axi_rw_init_rw_obi/linear.txt", + "huge": "backend_rw_axi_rw_init_rw_obi/huge.txt", + "large": "backend_rw_axi_rw_init_rw_obi/large.txt", + "medium": "backend_rw_axi_rw_init_rw_obi/medium.txt", + "mixed": "backend_rw_axi_rw_init_rw_obi/mixed.txt", + "small": "backend_rw_axi_rw_init_rw_obi/small.txt", + "tiny": "backend_rw_axi_rw_init_rw_obi/tiny.txt", + "zero_transfer": "backend_rw_axi_rw_init_rw_obi/zero_transfer.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 1, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 1, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "rw_axi_rw_init_rw_obi", + "proc_id": "rw_axi_rw_init_rw_obi", "testbench": "tb_idma_backend_rw_axi_rw_init_rw_obi", "synth_top": "idma_backend_synth_rw_axi_rw_init_rw_obi" }, "rw_axi_rw_axis": { - "jobs" : { - "simple" : "backend_rw_axi_rw_axis/simple.txt", - "same_dst" : "backend_rw_axi_rw_axis/same_dst.txt", - "linear" : "backend_rw_axi_rw_axis/linear.txt", - "huge" : "backend_rw_axi_rw_axis/huge.txt", - "large" : "backend_rw_axi_rw_axis/large.txt", - "medium" : "backend_rw_axi_rw_axis/medium.txt", - "mixed" : "backend_rw_axi_rw_axis/mixed.txt", - "small" : "backend_rw_axi_rw_axis/small.txt", - "tiny" : "backend_rw_axi_rw_axis/tiny.txt", - "zero_transfer" : "backend_rw_axi_rw_axis/zero_transfer.txt" - }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 0, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "jobs": { + "simple": "backend_rw_axi_rw_axis/simple.txt", + "same_dst": "backend_rw_axi_rw_axis/same_dst.txt", + "linear": "backend_rw_axi_rw_axis/linear.txt", + "huge": "backend_rw_axi_rw_axis/huge.txt", + "large": "backend_rw_axi_rw_axis/large.txt", + "medium": "backend_rw_axi_rw_axis/medium.txt", + "mixed": "backend_rw_axi_rw_axis/mixed.txt", + "small": "backend_rw_axi_rw_axis/small.txt", + "tiny": "backend_rw_axi_rw_axis/tiny.txt", + "zero_transfer": "backend_rw_axi_rw_axis/zero_transfer.txt" + }, + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 0, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "rw_axi_rw_axis", - "testbench" : "tb_idma_backend_rw_axi_rw_axis", - "synth_top" : "idma_backend_synth_rw_axi_rw_axis" + "proc_id": "rw_axi_rw_axis", + "testbench": "tb_idma_backend_rw_axi_rw_axis", + "synth_top": "idma_backend_synth_rw_axi_rw_axis" }, "rw_obi": { - "jobs" : { - "simple" : "backend_rw_obi/simple.txt", - "same_dst" : "backend_rw_obi/same_dst.txt", - "linear" : "backend_rw_obi/linear.txt", - "huge" : "backend_rw_obi/huge.txt", - "large" : "backend_rw_obi/large.txt", - "medium" : "backend_rw_obi/medium.txt", - "mixed" : "backend_rw_obi/mixed.txt", - "small" : "backend_rw_obi/small.txt", - "tiny" : "backend_rw_obi/tiny.txt", - "zero_transfer" : "backend_rw_obi/zero_transfer.txt" - }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "CombinedShifter" : 0, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 0, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "jobs": { + "simple": "backend_rw_obi/simple.txt", + "same_dst": "backend_rw_obi/same_dst.txt", + "linear": "backend_rw_obi/linear.txt", + "huge": "backend_rw_obi/huge.txt", + "large": "backend_rw_obi/large.txt", + "medium": "backend_rw_obi/medium.txt", + "mixed": "backend_rw_obi/mixed.txt", + "small": "backend_rw_obi/small.txt", + "tiny": "backend_rw_obi/tiny.txt", + "zero_transfer": "backend_rw_obi/zero_transfer.txt" + }, + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "CombinedShifter": 0, + "MaskInvalidData": 1, + "RAWCouplingAvail": 0, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "rw_obi", - "testbench" : "tb_idma_backend_rw_obi", - "synth_top" : "idma_backend_synth_rw_obi" + "proc_id": "rw_obi", + "testbench": "tb_idma_backend_rw_obi", + "synth_top": "idma_backend_synth_rw_obi" }, - "4d_extension" : { - "jobs" : { - "simple" : "4d_extension/simple.txt", - "linear_2d" : "4d_extension/linear_2D.txt" + "4d_extension": { + "jobs": { + "simple": "4d_extension/simple.txt", + "linear_2d": "4d_extension/linear_2D.txt" }, - "params" : { - "DataWidth" : 32, - "AddrWidth" : 32, - "UserWidth" : 1, - "AxiIdWidth" : 12, - "NumAxInFlight" : 3, - "BufferDepth" : 3, - "TFLenWidth" : 32, - "MemSysDepth" : 0, - "NumDim" : 4, - "RepWidth" : 32, - "StrideWidth" : 32, - "CombinedShifter" : 0, - "MaskInvalidData" : 1, - "RAWCouplingAvail" : 1, - "HardwareLegalizer" : 1, - "RejectZeroTransfers" : 1, - "ErrorHandling" : 0 + "params": { + "DataWidth": 32, + "AddrWidth": 32, + "UserWidth": 1, + "AxiIdWidth": 12, + "NumAxInFlight": 3, + "BufferDepth": 3, + "TFLenWidth": 32, + "MemSysDepth": 0, + "NumDim": 4, + "RepWidth": 32, + "StrideWidth": 32, + "CombinedShifter": 0, + "MaskInvalidData": 1, + "RAWCouplingAvail": 1, + "HardwareLegalizer": 1, + "RejectZeroTransfers": 1, + "ErrorHandling": 0 }, - "proc_id" : "rw_axi", + "proc_id": "rw_axi", "testbench": "tb_idma_nd_midend", "synth_top": "idma_nd_midend_synth" }, "desc64": { - "jobs" : { - "simple" : "jobs.json" + "jobs": { + "simple": "jobs.json" }, - "params" : { - }, - "proc_id" : "none", - "testbench" : "tb_idma_desc64_top", - "synth_top" : "idma_desc64_synth" + "params": {}, + "proc_id": "none", + "testbench": "tb_idma_desc64_top", + "synth_top": "idma_desc64_synth" }, "desc64_dma": { - "jobs" : { - "simple" : "jobs.json" - }, - "params" : { + "jobs": { + "simple": "jobs.json" }, - "proc_id" : "none", - "testbench" : "tb_idma_desc64_bench", - "synth_top" : "idma_desc64_synth" + "params": {}, + "proc_id": "none", + "testbench": "tb_idma_desc64_bench", + "synth_top": "idma_desc64_synth" }, "rt_midend": { - "jobs" : { - "simple" : "jobs.json" + "jobs": { + "simple": "jobs.json" }, - "params" : { - }, - "proc_id" : "none", - "testbench" : "tb_idma_rt_midend", - "synth_top" : "idma_rt_midend_synth" + "params": {}, + "proc_id": "none", + "testbench": "tb_idma_rt_midend", + "synth_top": "idma_rt_midend_synth" }, "mp_midend": { - "jobs" : { - "simple" : "jobs.json" - }, - "params" : { + "jobs": { + "simple": "jobs.json" }, - "proc_id" : "none", - "testbench" : "idma_mp_midend_synth", - "synth_top" : "idma_mp_midend_synth" + "params": {}, + "proc_id": "none", + "testbench": "idma_mp_midend_synth", + "synth_top": "idma_mp_midend_synth" + }, + "mxquant": { + "testbench": "tb_idma_mxquant", + "jobs": {}, + "params": {}, + "verify": { + "dpi": "idma_mxquant_dpi", + "legs": [ + { + "tag": "mxquant_32", + "params": { + "DataWidth": 32 + }, + "token": "[MXQ] ALL PASS" + }, + { + "tag": "mxquant_64", + "params": { + "DataWidth": 64 + }, + "token": "[MXQ] ALL PASS" + }, + { + "tag": "mxquant_256", + "params": { + "DataWidth": 256 + }, + "token": "[MXQ] ALL PASS" + }, + { + "tag": "mxquant_512", + "params": { + "DataWidth": 512 + }, + "token": "[MXQ] ALL PASS" + }, + { + "tag": "mxquant_1024", + "params": { + "DataWidth": 1024 + }, + "token": "[MXQ] ALL PASS" + } + ] + } + }, + "mxroundtrip": { + "testbench": "tb_idma_mxroundtrip", + "jobs": {}, + "params": {}, + "verify": { + "dpi": "idma_mxquant_dpi", + "legs": [ + { + "tag": "mxroundtrip_32", + "params": { + "DataWidth": 32 + }, + "token": "[MXRT] ALL PASS" + }, + { + "tag": "mxroundtrip_64", + "params": { + "DataWidth": 64 + }, + "token": "[MXRT] ALL PASS" + }, + { + "tag": "mxroundtrip_256", + "params": { + "DataWidth": 256 + }, + "token": "[MXRT] ALL PASS" + } + ], + "exclude": [ + { + "values": [ + 512, + 1024 + ], + "why": "heap abort during model construction, see #196" + } + ] + } + }, + "transpose": { + "testbench": "tb_idma_otf_transpose", + "jobs": {}, + "params": {}, + "verify": { + "dpi": "idma_transpose_dpi", + "legs": [ + { + "tag": "otf_transpose", + "params": { + "StrbWidth": 8, + "FullDuplex": 1 + }, + "token": "[TB] ALL PASS" + }, + { + "tag": "otf_transpose_bp", + "params": { + "StrbWidth": 8, + "FullDuplex": 1 + }, + "token": "[TB] ALL PASS", + "plusargs": [ + "+BP" + ] + }, + { + "tag": "otf_transpose_8_0", + "params": { + "StrbWidth": 8, + "FullDuplex": 0 + }, + "token": "[TB] ALL PASS", + "plusargs": [ + "+BP" + ] + }, + { + "tag": "otf_transpose_64_1", + "params": { + "StrbWidth": 64, + "FullDuplex": 1 + }, + "token": "[TB] ALL PASS", + "plusargs": [ + "+BP" + ] + }, + { + "tag": "otf_transpose_64_0", + "params": { + "StrbWidth": 64, + "FullDuplex": 0 + }, + "token": "[TB] ALL PASS", + "plusargs": [ + "+BP" + ] + } + ] + } + }, + "transpose_midend": { + "testbench": "tb_idma_transpose_midend", + "jobs": {}, + "params": {}, + "verify": { + "dpi": "idma_transpose_dpi", + "legs": [ + { + "tag": "transpose_midend_64", + "params": { + "DataWidth": 64 + }, + "token": "[MID] ALL PASS" + }, + { + "tag": "transpose_midend_512", + "params": { + "DataWidth": 512 + }, + "token": "[MID] ALL PASS" + } + ] + } + }, + "mxclear": { + "testbench": "tb_idma_mxclear", + "jobs": {}, + "params": {}, + "verify": { + "expect": "fail", + "defines": [ + "INC_ASSERT" + ], + "legs": [ + { + "tag": "mxclear_1", + "params": { + "Quant": 1 + }, + "token": "clear with in-flight state" + }, + { + "tag": "mxclear_0", + "params": { + "Quant": 0 + }, + "token": "clear with in-flight state" + } + ] + } + }, + "mxneg": { + "testbench": "tb_idma_mxneg", + "jobs": {}, + "params": {}, + "verify": { + "dpi": "idma_mxquant_dpi", + "expect": "fail", + "defines": [ + "INC_ASSERT" + ], + "legs": [ + { + "tag": "mxneg_1", + "params": { + "NegCase": 1, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeSizeAligned" + }, + { + "tag": "mxneg_2", + "params": { + "NegCase": 2, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeSrcAligned" + }, + { + "tag": "mxneg_3", + "params": { + "NegCase": 3, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeDstAligned" + }, + { + "tag": "mxneg_5", + "params": { + "NegCase": 5, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeMxdequantBeatAligned" + }, + { + "tag": "mxneg_6", + "params": { + "NegCase": 6, + "DataWidth": 64, + "EnDequant": 0, + "EnFp16": 1 + }, + "token": "ComputeOpUnsupported" + }, + { + "tag": "mxneg_7", + "params": { + "NegCase": 7, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeMxSrcProtocol" + }, + { + "tag": "mxneg_8", + "params": { + "NegCase": 8, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeMxDstProtocol" + }, + { + "tag": "mxneg_10", + "params": { + "NegCase": 10, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 1 + }, + "token": "ComputeTransposeSingleBeat" + }, + { + "tag": "mxneg_13", + "params": { + "NegCase": 13, + "DataWidth": 64, + "EnDequant": 1, + "EnFp16": 0 + }, + "token": "ComputeOpUnsupported" + } + ], + "exclude": [ + { + "case": 4, + "why": "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046" + }, + { + "case": 11, + "why": "the request is never accepted, so the guard is never sampled" + }, + { + "case": 12, + "why": "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046" + } + ] + } + }, + "_verify": { + "elab_widths": [ + 32, + 64, + 512, + 1024 + ], + "elab_compute": [ + { + "width": 64, + "ops": 15, + "tuning": 1, + "why": "every sub-unit", + "ping-pong transpose banks": null + }, + { + "width": 512, + "ops": 15, + "tuning": 1, + "why": "the wide bus", + "where pack/unpack is stressed": null + }, + { + "width": 64, + "ops": 8, + "tuning": 0, + "why": "transpose only", + "single bank": null + }, + { + "width": 64, + "ops": 6, + "tuning": 1, + "why": "mx only", + "Fp16En=0": null, + "the no-transpose branch": null + } + ], + "elab_shared_tops": [ + "idma_desc64_synth", + "idma_nd_midend_synth", + "idma_mp_midend_synth", + "idma_rt_midend_synth" + ], + "multihead_ids": [ + "2r_axi_w_axi", + "2rw_axi" + ], + "reg_variants": [ + { + "variant": 3, + "module": "idma_reg32_3d" + }, + { + "variant": 2, + "module": "idma_reg64_2d" + }, + { + "variant": 1, + "module": "idma_reg64_1d" + } + ], + "elab_covered_elsewhere": [ + "tb_idma_reg_frontend" + ], + "guards_untested": [ + { + "guard": "ComputeMxFp16Width", + "why": "only reachable from the excluded 1024 cases" + }, + { + "guard": "ComputeMxdequantLengthFits", + "why": "case 11 is never accepted" + }, + { + "guard": "ComputeDstTilelink", + "why": "no TileLink backend variant exists" + } + ], + "tops_untested": [ + { + "top": "tb_idma_nd_midend_b2b", + "why": "hangs ([B2B] timeout); latent reset/LFSR race" + }, + { + "top": "tb_idma_transpose_nd", + "why": "AW outside the destination allocation at 80 ns" + }, + { + "top": "tb_idma_transpose_b2b", + "why": "stream_watchdog trip after 4000 idle cycles" + }, + { + "top": "tb_idma_mxrand", + "why": "testbench timeout at 400 ms of simulated time" + }, + { + "top": "tb_idma_mxperf", + "why": "hangs on the first compute transfer" + }, + { + "top": "tb_idma_rt_midend", + "why": "fails under --assert on cc_rr_arb_tree lock" + }, + { + "top": "the 88 directed job files", + "why": "verilator cannot drive the idma_test class-based stimulus" + } + ] } } diff --git a/src/db/verify.yml b/src/db/verify.yml deleted file mode 100644 index 9e8ff4a0..00000000 --- a/src/db/verify.yml +++ /dev/null @@ -1,116 +0,0 @@ -# Copyright 2026 ETH Zurich and University of Bologna. -# Solderpad Hardware License, Version 0.51, see LICENSE for details. -# SPDX-License-Identifier: SHL-0.51 - -# Authors: -# - Daniel Keller - -# The public verification matrix. util/run_verify.py runs a suite from here and -# emits the CI matrix from the same entries. Excluded legs are `exclude:` entries -# with a reason; check_jobs.py fails on anything neither run nor excluded. - -# Every jobs.json entry pins DataWidth=32, so nothing wider is elaborated otherwise -elab_widths: [32, 64, 512, 1024] - -# No jobs.json entry enables compute, so its branches are unreachable without these -elab_compute: - - {width: 64, ops: 15, tuning: 1, why: every sub-unit, ping-pong transpose banks} - - {width: 512, ops: 15, tuning: 1, why: the wide bus, where pack/unpack is stressed} - - {width: 64, ops: 8, tuning: 0, why: transpose only, single bank} - - {width: 64, ops: 6, tuning: 1, why: mx only, Fp16En=0, the no-transpose branch} - -# Non-backend synthesis tops, elaborated with their own jobs.json parameters -elab_shared_tops: [idma_desc64_synth, idma_nd_midend_synth, idma_mp_midend_synth, - idma_rt_midend_synth] - -# Out-of-tree multi-head variants; license-free but generated only on request -multihead_ids: [2r_axi_w_axi, 2rw_axi] - -# The one public concrete binding; without this, 2d and 1d have no leg -reg_variants: - - {variant: 3, module: idma_reg32_3d} - - {variant: 2, module: idma_reg64_2d} - - {variant: 1, module: idma_reg64_1d} - -# Elaborated by a dedicated leg, so the shared sweep skips them -elab_covered_elsewhere: [tb_idma_reg_frontend] - -# One CI leg per run. `expect: fail` inverts the polarity: non-zero AND the token. -suites: - - mxquant: - top: tb_idma_mxquant - dpi: idma_mxquant_dpi - token: "[MXQ] ALL PASS" - sweep: {param: DataWidth, values: [32, 64, 256, 512, 1024]} - - mxroundtrip: - top: tb_idma_mxroundtrip - dpi: idma_mxquant_dpi - token: "[MXRT] ALL PASS" - sweep: {param: DataWidth, values: [32, 64, 256]} - exclude: - - {values: [512, 1024], why: "heap abort during model construction, see #196"} - - transpose: - top: tb_idma_otf_transpose - dpi: idma_transpose_dpi - token: "[TB] ALL PASS" - runs: - - {tag: otf_transpose, params: {StrbWidth: 8, FullDuplex: 1}} - # +BP is a second stimulus, backpressured, not a rerun - - {tag: otf_transpose_bp, params: {StrbWidth: 8, FullDuplex: 1}, plusargs: ["+BP"]} - - {tag: otf_transpose_8_0, params: {StrbWidth: 8, FullDuplex: 0}, plusargs: ["+BP"]} - - {tag: otf_transpose_64_1, params: {StrbWidth: 64, FullDuplex: 1}, plusargs: ["+BP"]} - - {tag: otf_transpose_64_0, params: {StrbWidth: 64, FullDuplex: 0}, plusargs: ["+BP"]} - - transpose_midend: - top: tb_idma_transpose_midend - dpi: idma_transpose_dpi - token: "[MID] ALL PASS" - sweep: {param: DataWidth, values: [64, 512]} - - mxclear: - top: tb_idma_mxclear - token: "clear with in-flight state" - expect: fail - defines: [INC_ASSERT] - sweep: {param: Quant, values: [1, 0]} - - # token is the guard name, so a case firing the wrong guard fails - mxneg: - top: tb_idma_mxneg - dpi: idma_mxquant_dpi - expect: fail - defines: [INC_ASSERT] - runs: - - {tag: mxneg_1, token: ComputeSizeAligned, params: {NegCase: 1, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_2, token: ComputeSrcAligned, params: {NegCase: 2, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_3, token: ComputeDstAligned, params: {NegCase: 3, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_5, token: ComputeMxdequantBeatAligned, params: {NegCase: 5, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_6, token: ComputeOpUnsupported, params: {NegCase: 6, DataWidth: 64, EnDequant: 0, EnFp16: 1}} - - {tag: mxneg_7, token: ComputeMxSrcProtocol, params: {NegCase: 7, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_8, token: ComputeMxDstProtocol, params: {NegCase: 8, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_10, token: ComputeTransposeSingleBeat, params: {NegCase: 10, DataWidth: 64, EnDequant: 1, EnFp16: 1}} - - {tag: mxneg_13, token: ComputeOpUnsupported, params: {NegCase: 13, DataWidth: 64, EnDequant: 1, EnFp16: 0}} - exclude: - - {case: 4, why: "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046"} - - {case: 11, why: "the request is never accepted, so the guard is never sampled"} - - {case: 12, why: "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046"} - -# Guards with no firing negative test; check_jobs.py fails on any not named here -guards_untested: - - {guard: ComputeMxFp16Width, why: "only reachable from the excluded 1024 cases"} - - {guard: ComputeMxdequantLengthFits, why: "case 11 is never accepted"} - - {guard: ComputeDstTilelink, why: "no TileLink backend variant exists"} - -# Excluded from the simulation tier. Dropped coverage, not silent skips; needs triage. -tops_untested: - - {top: tb_idma_nd_midend_b2b, why: "hangs ([B2B] timeout); latent reset/LFSR race"} - - {top: tb_idma_transpose_nd, why: "AW outside the destination allocation at 80 ns"} - - {top: tb_idma_transpose_b2b, why: "stream_watchdog trip after 4000 idle cycles"} - - {top: tb_idma_mxrand, why: "testbench timeout at 400 ms of simulated time"} - - {top: tb_idma_mxperf, why: "hangs on the first compute transfer"} - - {top: tb_idma_rt_midend, why: "fails under --assert on cc_rr_arb_tree lock"} - - {top: the 88 directed job files, - why: "verilator cannot drive the idma_test class-based stimulus"} diff --git a/util/check_jobs.py b/util/check_jobs.py index d6e32d8c..b89faa37 100644 --- a/util/check_jobs.py +++ b/util/check_jobs.py @@ -36,7 +36,6 @@ import re import sys -import yaml # On disk but deliberately unwired: these backends elaborate with ErrorHandling=0 UNWIRED_JOBS = { @@ -65,7 +64,7 @@ def main(): par.add_argument('--matrix-file', default=None, help='CI workflow that must fan out over every backend id') par.add_argument('--verify-db', default=None, - help='src/db/verify.yml; the run set and its named exclusions') + help='jobs/jobs.json; the run set and its named exclusions') par.add_argument('--mxneg-tb', default=None, help='negative-test testbench; its case labels must all be accounted for') par.add_argument('--mxneg-guard-src', default=None, @@ -75,6 +74,10 @@ def main(): patterns = args.source or ['target/rtl/*.sv', 'src/**/*.sv', 'test/**/*.sv'] with open(args.jobs, 'r') as handle: jobs = json.load(handle) + # _verify holds the globals; entries with a verify key are simulation suites + # rather than backend variants, so they carry no synth_top and no job files + suites = {k: v for k, v in jobs.items() if 'verify' in v} + jobs = {k: v for k, v in jobs.items() if k != '_verify' and 'verify' not in v} sources = load_sources(patterns) errors = [] @@ -116,6 +119,15 @@ def main(): errors.append('{}: {} "{}" is not defined in the sources'.format( name, field, module)) + # a suite entry names no synth_top, but its testbench must still exist + for name, body in sorted(suites.items()): + module = body.get('testbench') + if not module: + errors.append('{}: no testbench named'.format(name)) + elif not re.search(r'\bmodule\s+' + re.escape(module) + r'\b', sources): + errors.append('{}: testbench "{}" is not defined in the sources'.format( + name, module)) + # (d) the backend-id matrix is a literal list in the workflow, so it can drift if args.matrix_file: with open(args.matrix_file, 'r') as handle: @@ -128,8 +140,9 @@ def main(): # (e) compares the run set against the design, not against a copy of itself db = {} if args.verify_db: - with open(args.verify_db, 'r') as handle: - db = yaml.safe_load(handle) or {} + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import run_verify + db = run_verify.load(args.verify_db) mxneg = db.get('suites', {}).get('mxneg', {}) run_cases = {str(r['params']['NegCase']) for r in mxneg.get('runs', [])} diff --git a/util/idma_params.py b/util/idma_params.py index baf92fe6..1bcfa77f 100644 --- a/util/idma_params.py +++ b/util/idma_params.py @@ -94,7 +94,7 @@ def entries_for(jobs, top): def main(): par = argparse.ArgumentParser(description=__doc__) par.add_argument('--verify-db', default=None, - help='src/db/verify.yml; supplies the width and compute sweeps') + help='jobs/jobs.json; supplies the width and compute sweeps') par.add_argument('--top', required=True) par.add_argument('--jobs', default='jobs/jobs.json') par.add_argument('--source', action='append', default=[], metavar='GLOB', @@ -108,9 +108,9 @@ def main(): # The database is the source when supplied; the flags stay for one-off runs. if args.verify_db: - import yaml - with open(args.verify_db, 'r') as handle: - db = yaml.safe_load(handle) or {} + sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + import run_verify + db = run_verify.load(args.verify_db) if db.get('elab_widths'): args.widths = ' '.join(str(w) for w in db['elab_widths']) if db.get('elab_compute'): diff --git a/util/run_verify.py b/util/run_verify.py index eabdd8b0..720d0601 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -5,7 +5,7 @@ # Authors: # - Daniel Keller -"""Run a verification suite, or emit the CI matrix, from src/db/verify.yml. +"""Run a verification suite, or emit the CI matrix, from jobs/jobs.json. Both come from the same entries, so a leg cannot exist in CI and not locally, or the reverse. The old form kept the run set in make variables and the fan-out in @@ -30,16 +30,25 @@ import subprocess import sys -import yaml - HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) -DB = os.path.join(ROOT, 'src', 'db', 'verify.yml') +DB = os.path.join(ROOT, 'jobs', 'jobs.json') def load(path=None): + """Return {suites, ...globals}; entries carrying a `verify` key are suites.""" with open(path or DB, 'r') as handle: - return yaml.safe_load(handle) + jobs = json.load(handle) + db = dict(jobs.pop('_verify', {})) + db['suites'] = {} + for name, entry in jobs.items(): + if 'verify' not in entry: + continue + suite = dict(entry['verify']) + suite['top'] = entry['testbench'] + suite['runs'] = suite.pop('legs') + db['suites'][name] = suite + return db def legs(suite_name, suite): diff --git a/verify.mk b/verify.mk index ed6bc126..d296829c 100644 --- a/verify.mk +++ b/verify.mk @@ -23,7 +23,7 @@ IDMA_TOP ?= # Backend variant a leg covers IDMA_VERIFY_ID ?= rw_axi IDMA_MULTIHEAD_PAT := 2r_axi_w_axi 2rw_axi -IDMA_VERIFY_DB := $(IDMA_ROOT)/src/db/verify.yml +IDMA_VERIFY_DB := $(IDMA_ROOT)/$(IDMA_JOBS_JSON) IDMA_VERIFY_DIR := $(IDMA_ROOT)/target/verify IDMA_SLANG_DIR := $(IDMA_ROOT)/target/sim/slang IDMA_SLANG_VERSION ?= 11.0.0 From 7ac2795f01663f4e9d45818ffb7e662b719a55c0 Mon Sep 17 00:00:00 2001 From: Daniel Keller Date: Mon, 17 Aug 2026 12:03:45 +0200 Subject: [PATCH 24/24] ci: Keep the jobs.json formatting and give the suites their parameters Serialising the file with json.dump reflowed all 281 existing lines, because the file aligns its keys by column and the serialiser does not. The entries are appended as text now, so the diff is additions only and no existing line moves. The suite entries also declared an empty params block while every other entry carries its parameter set, leaving the standard widths implicit in the legs. They are declared per entry now and merged into each leg, so a leg spells out only what it varies. --- jobs/jobs.json | 1126 +++++++++++++++++--------------------------- util/run_verify.py | 5 +- 2 files changed, 437 insertions(+), 694 deletions(-) diff --git a/jobs/jobs.json b/jobs/jobs.json index d0cae4ab..5caf7d4d 100644 --- a/jobs/jobs.json +++ b/jobs/jobs.json @@ -1,758 +1,498 @@ { - "rw_axi": { - "jobs": { - "simple": "backend_rw_axi/simple.txt", - "same_dst": "backend_rw_axi/same_dst.txt", - "linear": "backend_rw_axi/linear.txt", - "huge": "backend_rw_axi/huge.txt", - "large": "backend_rw_axi/large.txt", - "medium": "backend_rw_axi/medium.txt", - "mixed": "backend_rw_axi/mixed.txt", - "small": "backend_rw_axi/small.txt", - "tiny": "backend_rw_axi/tiny.txt", - "error_simple": "backend_rw_axi/error_simple.txt", - "error_mixed": "backend_rw_axi/error_mixed.txt", - "zero_transfer": "backend_rw_axi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 1, - "MaskInvalidData": 1, - "RAWCouplingAvail": 1, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 1 - }, - "proc_id": "rw_axi", + "rw_axi" : { + "jobs" : { + "simple" : "backend_rw_axi/simple.txt", + "same_dst" : "backend_rw_axi/same_dst.txt", + "linear" : "backend_rw_axi/linear.txt", + "huge" : "backend_rw_axi/huge.txt", + "large" : "backend_rw_axi/large.txt", + "medium" : "backend_rw_axi/medium.txt", + "mixed" : "backend_rw_axi/mixed.txt", + "small" : "backend_rw_axi/small.txt", + "tiny" : "backend_rw_axi/tiny.txt", + "error_simple" : "backend_rw_axi/error_simple.txt", + "error_mixed" : "backend_rw_axi/error_mixed.txt", + "zero_transfer" : "backend_rw_axi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 1, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 1, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 1 + }, + "proc_id" : "rw_axi", "testbench": "tb_idma_backend_rw_axi", "synth_top": "idma_backend_synth_rw_axi" }, - "r_axi_w_obi": { - "jobs": { - "simple": "backend_r_axi_w_obi/simple.txt", - "same_dst": "backend_r_axi_w_obi/same_dst.txt", - "linear": "backend_r_axi_w_obi/linear.txt", - "huge": "backend_r_axi_w_obi/huge.txt", - "large": "backend_r_axi_w_obi/large.txt", - "medium": "backend_r_axi_w_obi/medium.txt", - "mixed": "backend_r_axi_w_obi/mixed.txt", - "small": "backend_r_axi_w_obi/small.txt", - "tiny": "backend_r_axi_w_obi/tiny.txt", - "zero_transfer": "backend_r_axi_w_obi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 0, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "r_axi_w_obi", + "r_axi_w_obi" : { + "jobs" : { + "simple" : "backend_r_axi_w_obi/simple.txt", + "same_dst" : "backend_r_axi_w_obi/same_dst.txt", + "linear" : "backend_r_axi_w_obi/linear.txt", + "huge" : "backend_r_axi_w_obi/huge.txt", + "large" : "backend_r_axi_w_obi/large.txt", + "medium" : "backend_r_axi_w_obi/medium.txt", + "mixed" : "backend_r_axi_w_obi/mixed.txt", + "small" : "backend_r_axi_w_obi/small.txt", + "tiny" : "backend_r_axi_w_obi/tiny.txt", + "zero_transfer" : "backend_r_axi_w_obi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 0, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "r_axi_w_obi", "testbench": "tb_idma_backend_r_axi_w_obi", "synth_top": "idma_backend_synth_r_axi_w_obi" }, - "r_obi_w_axi": { - "jobs": { - "simple": "backend_r_obi_w_axi/simple.txt", - "same_dst": "backend_r_obi_w_axi/same_dst.txt", - "linear": "backend_r_obi_w_axi/linear.txt", - "huge": "backend_r_obi_w_axi/huge.txt", - "large": "backend_r_obi_w_axi/large.txt", - "medium": "backend_r_obi_w_axi/medium.txt", - "mixed": "backend_r_obi_w_axi/mixed.txt", - "small": "backend_r_obi_w_axi/small.txt", - "tiny": "backend_r_obi_w_axi/tiny.txt", - "zero_transfer": "backend_r_obi_w_axi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 0, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "r_obi_w_axi", + "r_obi_w_axi" : { + "jobs" : { + "simple" : "backend_r_obi_w_axi/simple.txt", + "same_dst" : "backend_r_obi_w_axi/same_dst.txt", + "linear" : "backend_r_obi_w_axi/linear.txt", + "huge" : "backend_r_obi_w_axi/huge.txt", + "large" : "backend_r_obi_w_axi/large.txt", + "medium" : "backend_r_obi_w_axi/medium.txt", + "mixed" : "backend_r_obi_w_axi/mixed.txt", + "small" : "backend_r_obi_w_axi/small.txt", + "tiny" : "backend_r_obi_w_axi/tiny.txt", + "zero_transfer" : "backend_r_obi_w_axi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 0, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "r_obi_w_axi", "testbench": "tb_idma_backend_r_obi_w_axi", "synth_top": "idma_backend_synth_r_obi_w_axi" }, - "snitch_read": { - "jobs": { - "simple": "backend_r_axi_rw_init_rw_obi/simple.txt", - "same_dst": "backend_r_axi_rw_init_rw_obi/same_dst.txt", - "linear": "backend_r_axi_rw_init_rw_obi/linear.txt", - "huge": "backend_r_axi_rw_init_rw_obi/huge.txt", - "large": "backend_r_axi_rw_init_rw_obi/large.txt", - "medium": "backend_r_axi_rw_init_rw_obi/medium.txt", - "mixed": "backend_r_axi_rw_init_rw_obi/mixed.txt", - "small": "backend_r_axi_rw_init_rw_obi/small.txt", - "tiny": "backend_r_axi_rw_init_rw_obi/tiny.txt", - "zero_transfer": "backend_r_axi_rw_init_rw_obi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 1, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "r_axi_rw_init_rw_obi", + "snitch_read" : { + "jobs" : { + "simple" : "backend_r_axi_rw_init_rw_obi/simple.txt", + "same_dst" : "backend_r_axi_rw_init_rw_obi/same_dst.txt", + "linear" : "backend_r_axi_rw_init_rw_obi/linear.txt", + "huge" : "backend_r_axi_rw_init_rw_obi/huge.txt", + "large" : "backend_r_axi_rw_init_rw_obi/large.txt", + "medium" : "backend_r_axi_rw_init_rw_obi/medium.txt", + "mixed" : "backend_r_axi_rw_init_rw_obi/mixed.txt", + "small" : "backend_r_axi_rw_init_rw_obi/small.txt", + "tiny" : "backend_r_axi_rw_init_rw_obi/tiny.txt", + "zero_transfer" : "backend_r_axi_rw_init_rw_obi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 1, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "r_axi_rw_init_rw_obi", "testbench": "tb_idma_backend_r_axi_rw_init_rw_obi", "synth_top": "idma_backend_synth_r_axi_rw_init_rw_obi" }, - "snitch_write": { - "jobs": { - "simple": "backend_r_obi_rw_init_w_axi/simple.txt", - "same_dst": "backend_r_obi_rw_init_w_axi/same_dst.txt", - "linear": "backend_r_obi_rw_init_w_axi/linear.txt", - "huge": "backend_r_obi_rw_init_w_axi/huge.txt", - "large": "backend_r_obi_rw_init_w_axi/large.txt", - "medium": "backend_r_obi_rw_init_w_axi/medium.txt", - "mixed": "backend_r_obi_rw_init_w_axi/mixed.txt", - "small": "backend_r_obi_rw_init_w_axi/small.txt", - "tiny": "backend_r_obi_rw_init_w_axi/tiny.txt", - "zero_transfer": "backend_r_obi_rw_init_w_axi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 1, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "r_obi_rw_init_w_axi", + "snitch_write" : { + "jobs" : { + "simple" : "backend_r_obi_rw_init_w_axi/simple.txt", + "same_dst" : "backend_r_obi_rw_init_w_axi/same_dst.txt", + "linear" : "backend_r_obi_rw_init_w_axi/linear.txt", + "huge" : "backend_r_obi_rw_init_w_axi/huge.txt", + "large" : "backend_r_obi_rw_init_w_axi/large.txt", + "medium" : "backend_r_obi_rw_init_w_axi/medium.txt", + "mixed" : "backend_r_obi_rw_init_w_axi/mixed.txt", + "small" : "backend_r_obi_rw_init_w_axi/small.txt", + "tiny" : "backend_r_obi_rw_init_w_axi/tiny.txt", + "zero_transfer" : "backend_r_obi_rw_init_w_axi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 1, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "r_obi_rw_init_w_axi", "testbench": "tb_idma_backend_r_obi_rw_init_w_axi", "synth_top": "idma_backend_synth_r_obi_rw_init_w_axi" }, - "snitch_read_write": { - "jobs": { - "simple": "backend_rw_axi_rw_init_rw_obi/simple.txt", - "same_dst": "backend_rw_axi_rw_init_rw_obi/same_dst.txt", - "linear": "backend_rw_axi_rw_init_rw_obi/linear.txt", - "huge": "backend_rw_axi_rw_init_rw_obi/huge.txt", - "large": "backend_rw_axi_rw_init_rw_obi/large.txt", - "medium": "backend_rw_axi_rw_init_rw_obi/medium.txt", - "mixed": "backend_rw_axi_rw_init_rw_obi/mixed.txt", - "small": "backend_rw_axi_rw_init_rw_obi/small.txt", - "tiny": "backend_rw_axi_rw_init_rw_obi/tiny.txt", - "zero_transfer": "backend_rw_axi_rw_init_rw_obi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 1, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "rw_axi_rw_init_rw_obi", + "snitch_read_write" : { + "jobs" : { + "simple" : "backend_rw_axi_rw_init_rw_obi/simple.txt", + "same_dst" : "backend_rw_axi_rw_init_rw_obi/same_dst.txt", + "linear" : "backend_rw_axi_rw_init_rw_obi/linear.txt", + "huge" : "backend_rw_axi_rw_init_rw_obi/huge.txt", + "large" : "backend_rw_axi_rw_init_rw_obi/large.txt", + "medium" : "backend_rw_axi_rw_init_rw_obi/medium.txt", + "mixed" : "backend_rw_axi_rw_init_rw_obi/mixed.txt", + "small" : "backend_rw_axi_rw_init_rw_obi/small.txt", + "tiny" : "backend_rw_axi_rw_init_rw_obi/tiny.txt", + "zero_transfer" : "backend_rw_axi_rw_init_rw_obi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 1, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "rw_axi_rw_init_rw_obi", "testbench": "tb_idma_backend_rw_axi_rw_init_rw_obi", "synth_top": "idma_backend_synth_rw_axi_rw_init_rw_obi" }, "rw_axi_rw_axis": { - "jobs": { - "simple": "backend_rw_axi_rw_axis/simple.txt", - "same_dst": "backend_rw_axi_rw_axis/same_dst.txt", - "linear": "backend_rw_axi_rw_axis/linear.txt", - "huge": "backend_rw_axi_rw_axis/huge.txt", - "large": "backend_rw_axi_rw_axis/large.txt", - "medium": "backend_rw_axi_rw_axis/medium.txt", - "mixed": "backend_rw_axi_rw_axis/mixed.txt", - "small": "backend_rw_axi_rw_axis/small.txt", - "tiny": "backend_rw_axi_rw_axis/tiny.txt", - "zero_transfer": "backend_rw_axi_rw_axis/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 0, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "rw_axi_rw_axis", - "testbench": "tb_idma_backend_rw_axi_rw_axis", - "synth_top": "idma_backend_synth_rw_axi_rw_axis" + "jobs" : { + "simple" : "backend_rw_axi_rw_axis/simple.txt", + "same_dst" : "backend_rw_axi_rw_axis/same_dst.txt", + "linear" : "backend_rw_axi_rw_axis/linear.txt", + "huge" : "backend_rw_axi_rw_axis/huge.txt", + "large" : "backend_rw_axi_rw_axis/large.txt", + "medium" : "backend_rw_axi_rw_axis/medium.txt", + "mixed" : "backend_rw_axi_rw_axis/mixed.txt", + "small" : "backend_rw_axi_rw_axis/small.txt", + "tiny" : "backend_rw_axi_rw_axis/tiny.txt", + "zero_transfer" : "backend_rw_axi_rw_axis/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 0, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "rw_axi_rw_axis", + "testbench" : "tb_idma_backend_rw_axi_rw_axis", + "synth_top" : "idma_backend_synth_rw_axi_rw_axis" }, "rw_obi": { - "jobs": { - "simple": "backend_rw_obi/simple.txt", - "same_dst": "backend_rw_obi/same_dst.txt", - "linear": "backend_rw_obi/linear.txt", - "huge": "backend_rw_obi/huge.txt", - "large": "backend_rw_obi/large.txt", - "medium": "backend_rw_obi/medium.txt", - "mixed": "backend_rw_obi/mixed.txt", - "small": "backend_rw_obi/small.txt", - "tiny": "backend_rw_obi/tiny.txt", - "zero_transfer": "backend_rw_obi/zero_transfer.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "CombinedShifter": 0, - "MaskInvalidData": 1, - "RAWCouplingAvail": 0, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "rw_obi", - "testbench": "tb_idma_backend_rw_obi", - "synth_top": "idma_backend_synth_rw_obi" + "jobs" : { + "simple" : "backend_rw_obi/simple.txt", + "same_dst" : "backend_rw_obi/same_dst.txt", + "linear" : "backend_rw_obi/linear.txt", + "huge" : "backend_rw_obi/huge.txt", + "large" : "backend_rw_obi/large.txt", + "medium" : "backend_rw_obi/medium.txt", + "mixed" : "backend_rw_obi/mixed.txt", + "small" : "backend_rw_obi/small.txt", + "tiny" : "backend_rw_obi/tiny.txt", + "zero_transfer" : "backend_rw_obi/zero_transfer.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "CombinedShifter" : 0, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 0, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "rw_obi", + "testbench" : "tb_idma_backend_rw_obi", + "synth_top" : "idma_backend_synth_rw_obi" }, - "4d_extension": { - "jobs": { - "simple": "4d_extension/simple.txt", - "linear_2d": "4d_extension/linear_2D.txt" - }, - "params": { - "DataWidth": 32, - "AddrWidth": 32, - "UserWidth": 1, - "AxiIdWidth": 12, - "NumAxInFlight": 3, - "BufferDepth": 3, - "TFLenWidth": 32, - "MemSysDepth": 0, - "NumDim": 4, - "RepWidth": 32, - "StrideWidth": 32, - "CombinedShifter": 0, - "MaskInvalidData": 1, - "RAWCouplingAvail": 1, - "HardwareLegalizer": 1, - "RejectZeroTransfers": 1, - "ErrorHandling": 0 - }, - "proc_id": "rw_axi", + "4d_extension" : { + "jobs" : { + "simple" : "4d_extension/simple.txt", + "linear_2d" : "4d_extension/linear_2D.txt" + }, + "params" : { + "DataWidth" : 32, + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "NumAxInFlight" : 3, + "BufferDepth" : 3, + "TFLenWidth" : 32, + "MemSysDepth" : 0, + "NumDim" : 4, + "RepWidth" : 32, + "StrideWidth" : 32, + "CombinedShifter" : 0, + "MaskInvalidData" : 1, + "RAWCouplingAvail" : 1, + "HardwareLegalizer" : 1, + "RejectZeroTransfers" : 1, + "ErrorHandling" : 0 + }, + "proc_id" : "rw_axi", "testbench": "tb_idma_nd_midend", "synth_top": "idma_nd_midend_synth" }, "desc64": { - "jobs": { - "simple": "jobs.json" + "jobs" : { + "simple" : "jobs.json" }, - "params": {}, - "proc_id": "none", - "testbench": "tb_idma_desc64_top", - "synth_top": "idma_desc64_synth" + "params" : { + }, + "proc_id" : "none", + "testbench" : "tb_idma_desc64_top", + "synth_top" : "idma_desc64_synth" }, "desc64_dma": { - "jobs": { - "simple": "jobs.json" + "jobs" : { + "simple" : "jobs.json" + }, + "params" : { }, - "params": {}, - "proc_id": "none", - "testbench": "tb_idma_desc64_bench", - "synth_top": "idma_desc64_synth" + "proc_id" : "none", + "testbench" : "tb_idma_desc64_bench", + "synth_top" : "idma_desc64_synth" }, "rt_midend": { - "jobs": { - "simple": "jobs.json" + "jobs" : { + "simple" : "jobs.json" }, - "params": {}, - "proc_id": "none", - "testbench": "tb_idma_rt_midend", - "synth_top": "idma_rt_midend_synth" + "params" : { + }, + "proc_id" : "none", + "testbench" : "tb_idma_rt_midend", + "synth_top" : "idma_rt_midend_synth" }, "mp_midend": { - "jobs": { - "simple": "jobs.json" + "jobs" : { + "simple" : "jobs.json" + }, + "params" : { }, - "params": {}, - "proc_id": "none", - "testbench": "idma_mp_midend_synth", - "synth_top": "idma_mp_midend_synth" + "proc_id" : "none", + "testbench" : "idma_mp_midend_synth", + "synth_top" : "idma_mp_midend_synth" }, - "mxquant": { - "testbench": "tb_idma_mxquant", - "jobs": {}, - "params": {}, - "verify": { - "dpi": "idma_mxquant_dpi", - "legs": [ - { - "tag": "mxquant_32", - "params": { - "DataWidth": 32 - }, - "token": "[MXQ] ALL PASS" - }, - { - "tag": "mxquant_64", - "params": { - "DataWidth": 64 - }, - "token": "[MXQ] ALL PASS" - }, - { - "tag": "mxquant_256", - "params": { - "DataWidth": 256 - }, - "token": "[MXQ] ALL PASS" - }, - { - "tag": "mxquant_512", - "params": { - "DataWidth": 512 - }, - "token": "[MXQ] ALL PASS" - }, - { - "tag": "mxquant_1024", - "params": { - "DataWidth": 1024 - }, - "token": "[MXQ] ALL PASS" - } + "mxquant" : { + "jobs" : { + }, + "params" : { + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "TFLenWidth" : 32 + }, + "testbench" : "tb_idma_mxquant", + "verify" : { + "dpi" : "idma_mxquant_dpi", + "token" : "[MXQ] ALL PASS", + "legs" : [ + { "tag" : "mxquant_32", "params" : { "DataWidth" : 32 } }, + { "tag" : "mxquant_64", "params" : { "DataWidth" : 64 } }, + { "tag" : "mxquant_256", "params" : { "DataWidth" : 256 } }, + { "tag" : "mxquant_512", "params" : { "DataWidth" : 512 } }, + { "tag" : "mxquant_1024", "params" : { "DataWidth" : 1024 } } ] } }, - "mxroundtrip": { - "testbench": "tb_idma_mxroundtrip", - "jobs": {}, - "params": {}, - "verify": { - "dpi": "idma_mxquant_dpi", - "legs": [ - { - "tag": "mxroundtrip_32", - "params": { - "DataWidth": 32 - }, - "token": "[MXRT] ALL PASS" - }, - { - "tag": "mxroundtrip_64", - "params": { - "DataWidth": 64 - }, - "token": "[MXRT] ALL PASS" - }, - { - "tag": "mxroundtrip_256", - "params": { - "DataWidth": 256 - }, - "token": "[MXRT] ALL PASS" - } + "mxroundtrip" : { + "jobs" : { + }, + "params" : { + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "TFLenWidth" : 32 + }, + "testbench" : "tb_idma_mxroundtrip", + "verify" : { + "dpi" : "idma_mxquant_dpi", + "token" : "[MXRT] ALL PASS", + "legs" : [ + { "tag" : "mxroundtrip_32", "params" : { "DataWidth" : 32 } }, + { "tag" : "mxroundtrip_64", "params" : { "DataWidth" : 64 } }, + { "tag" : "mxroundtrip_256", "params" : { "DataWidth" : 256 } } ], - "exclude": [ - { - "values": [ - 512, - 1024 - ], - "why": "heap abort during model construction, see #196" - } + "exclude" : [ + { "values" : [512, 1024], "why" : "heap abort during model construction, see #196" } ] } }, - "transpose": { - "testbench": "tb_idma_otf_transpose", - "jobs": {}, - "params": {}, - "verify": { - "dpi": "idma_transpose_dpi", - "legs": [ - { - "tag": "otf_transpose", - "params": { - "StrbWidth": 8, - "FullDuplex": 1 - }, - "token": "[TB] ALL PASS" - }, - { - "tag": "otf_transpose_bp", - "params": { - "StrbWidth": 8, - "FullDuplex": 1 - }, - "token": "[TB] ALL PASS", - "plusargs": [ - "+BP" - ] - }, - { - "tag": "otf_transpose_8_0", - "params": { - "StrbWidth": 8, - "FullDuplex": 0 - }, - "token": "[TB] ALL PASS", - "plusargs": [ - "+BP" - ] - }, - { - "tag": "otf_transpose_64_1", - "params": { - "StrbWidth": 64, - "FullDuplex": 1 - }, - "token": "[TB] ALL PASS", - "plusargs": [ - "+BP" - ] - }, - { - "tag": "otf_transpose_64_0", - "params": { - "StrbWidth": 64, - "FullDuplex": 0 - }, - "token": "[TB] ALL PASS", - "plusargs": [ - "+BP" - ] - } + "transpose" : { + "jobs" : { + }, + "params" : { + }, + "testbench" : "tb_idma_otf_transpose", + "verify" : { + "dpi" : "idma_transpose_dpi", + "token" : "[TB] ALL PASS", + "legs" : [ + { "tag" : "otf_transpose", "params" : { "StrbWidth" : 8, "FullDuplex" : 1 } }, + { "tag" : "otf_transpose_bp", "params" : { "StrbWidth" : 8, "FullDuplex" : 1 }, "plusargs" : ["+BP"] }, + { "tag" : "otf_transpose_8_0", "params" : { "StrbWidth" : 8, "FullDuplex" : 0 }, "plusargs" : ["+BP"] }, + { "tag" : "otf_transpose_64_1", "params" : { "StrbWidth" : 64, "FullDuplex" : 1 }, "plusargs" : ["+BP"] }, + { "tag" : "otf_transpose_64_0", "params" : { "StrbWidth" : 64, "FullDuplex" : 0 }, "plusargs" : ["+BP"] } ] } }, - "transpose_midend": { - "testbench": "tb_idma_transpose_midend", - "jobs": {}, - "params": {}, - "verify": { - "dpi": "idma_transpose_dpi", - "legs": [ - { - "tag": "transpose_midend_64", - "params": { - "DataWidth": 64 - }, - "token": "[MID] ALL PASS" - }, - { - "tag": "transpose_midend_512", - "params": { - "DataWidth": 512 - }, - "token": "[MID] ALL PASS" - } + "transpose_midend" : { + "jobs" : { + }, + "params" : { + "AddrWidth" : 64 + }, + "testbench" : "tb_idma_transpose_midend", + "verify" : { + "dpi" : "idma_transpose_dpi", + "token" : "[MID] ALL PASS", + "legs" : [ + { "tag" : "transpose_midend_64", "params" : { "DataWidth" : 64 } }, + { "tag" : "transpose_midend_512", "params" : { "DataWidth" : 512 } } ] } }, - "mxclear": { - "testbench": "tb_idma_mxclear", - "jobs": {}, - "params": {}, - "verify": { - "expect": "fail", - "defines": [ - "INC_ASSERT" - ], - "legs": [ - { - "tag": "mxclear_1", - "params": { - "Quant": 1 - }, - "token": "clear with in-flight state" - }, - { - "tag": "mxclear_0", - "params": { - "Quant": 0 - }, - "token": "clear with in-flight state" - } + "mxclear" : { + "jobs" : { + }, + "params" : { + "StrbWidth" : 8 + }, + "testbench" : "tb_idma_mxclear", + "verify" : { + "expect" : "fail", + "defines" : ["INC_ASSERT"], + "token" : "clear with in-flight state", + "legs" : [ + { "tag" : "mxclear_1", "params" : { "Quant" : 1 } }, + { "tag" : "mxclear_0", "params" : { "Quant" : 0 } } ] } }, - "mxneg": { - "testbench": "tb_idma_mxneg", - "jobs": {}, - "params": {}, - "verify": { - "dpi": "idma_mxquant_dpi", - "expect": "fail", - "defines": [ - "INC_ASSERT" - ], - "legs": [ - { - "tag": "mxneg_1", - "params": { - "NegCase": 1, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeSizeAligned" - }, - { - "tag": "mxneg_2", - "params": { - "NegCase": 2, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeSrcAligned" - }, - { - "tag": "mxneg_3", - "params": { - "NegCase": 3, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeDstAligned" - }, - { - "tag": "mxneg_5", - "params": { - "NegCase": 5, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeMxdequantBeatAligned" - }, - { - "tag": "mxneg_6", - "params": { - "NegCase": 6, - "DataWidth": 64, - "EnDequant": 0, - "EnFp16": 1 - }, - "token": "ComputeOpUnsupported" - }, - { - "tag": "mxneg_7", - "params": { - "NegCase": 7, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeMxSrcProtocol" - }, - { - "tag": "mxneg_8", - "params": { - "NegCase": 8, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeMxDstProtocol" - }, - { - "tag": "mxneg_10", - "params": { - "NegCase": 10, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 1 - }, - "token": "ComputeTransposeSingleBeat" - }, - { - "tag": "mxneg_13", - "params": { - "NegCase": 13, - "DataWidth": 64, - "EnDequant": 1, - "EnFp16": 0 - }, - "token": "ComputeOpUnsupported" - } + "mxneg" : { + "jobs" : { + }, + "params" : { + "AddrWidth" : 32, + "UserWidth" : 1, + "AxiIdWidth" : 12, + "TFLenWidth" : 32 + }, + "testbench" : "tb_idma_mxneg", + "verify" : { + "dpi" : "idma_mxquant_dpi", + "expect" : "fail", + "defines" : ["INC_ASSERT"], + "legs" : [ + { "tag" : "mxneg_1", "params" : { "NegCase" : 1, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeSizeAligned" }, + { "tag" : "mxneg_2", "params" : { "NegCase" : 2, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeSrcAligned" }, + { "tag" : "mxneg_3", "params" : { "NegCase" : 3, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeDstAligned" }, + { "tag" : "mxneg_5", "params" : { "NegCase" : 5, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeMxdequantBeatAligned" }, + { "tag" : "mxneg_6", "params" : { "NegCase" : 6, "DataWidth" : 64, "EnDequant" : 0, "EnFp16" : 1 }, "token" : "ComputeOpUnsupported" }, + { "tag" : "mxneg_7", "params" : { "NegCase" : 7, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeMxSrcProtocol" }, + { "tag" : "mxneg_8", "params" : { "NegCase" : 8, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeMxDstProtocol" }, + { "tag" : "mxneg_10", "params" : { "NegCase" : 10, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 1 }, "token" : "ComputeTransposeSingleBeat" }, + { "tag" : "mxneg_13", "params" : { "NegCase" : 13, "DataWidth" : 64, "EnDequant" : 1, "EnFp16" : 0 }, "token" : "ComputeOpUnsupported" } ], - "exclude": [ - { - "case": 4, - "why": "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046" - }, - { - "case": 11, - "why": "the request is never accepted, so the guard is never sampled" - }, - { - "case": 12, - "why": "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046" - } + "exclude" : [ + { "case" : 4, "why" : "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046" }, + { "case" : 11, "why" : "the request is never accepted, so the guard is never sampled" }, + { "case" : 12, "why" : "DataWidth 1024 SIGSEGVs on verilator 5.020; passes on 5.046" } ] } }, - "_verify": { - "elab_widths": [ - 32, - 64, - 512, - 1024 - ], - "elab_compute": [ - { - "width": 64, - "ops": 15, - "tuning": 1, - "why": "every sub-unit", - "ping-pong transpose banks": null - }, - { - "width": 512, - "ops": 15, - "tuning": 1, - "why": "the wide bus", - "where pack/unpack is stressed": null - }, - { - "width": 64, - "ops": 8, - "tuning": 0, - "why": "transpose only", - "single bank": null - }, - { - "width": 64, - "ops": 6, - "tuning": 1, - "why": "mx only", - "Fp16En=0": null, - "the no-transpose branch": null - } - ], - "elab_shared_tops": [ - "idma_desc64_synth", - "idma_nd_midend_synth", - "idma_mp_midend_synth", - "idma_rt_midend_synth" - ], - "multihead_ids": [ - "2r_axi_w_axi", - "2rw_axi" + "_verify" : { + "elab_widths" : [32, 64, 512, 1024], + "elab_compute" : [ + { "width" : 64, "ops" : 15, "tuning" : 1 }, + { "width" : 512, "ops" : 15, "tuning" : 1 }, + { "width" : 64, "ops" : 8, "tuning" : 0 }, + { "width" : 64, "ops" : 6, "tuning" : 1 } ], - "reg_variants": [ - { - "variant": 3, - "module": "idma_reg32_3d" - }, - { - "variant": 2, - "module": "idma_reg64_2d" - }, - { - "variant": 1, - "module": "idma_reg64_1d" - } + "elab_shared_tops" : [ + "idma_desc64_synth", "idma_nd_midend_synth", + "idma_mp_midend_synth", "idma_rt_midend_synth" ], - "elab_covered_elsewhere": [ - "tb_idma_reg_frontend" + "multihead_ids" : ["2r_axi_w_axi", "2rw_axi"], + "reg_variants" : [ + { "variant" : 3, "module" : "idma_reg32_3d" }, + { "variant" : 2, "module" : "idma_reg64_2d" }, + { "variant" : 1, "module" : "idma_reg64_1d" } ], - "guards_untested": [ - { - "guard": "ComputeMxFp16Width", - "why": "only reachable from the excluded 1024 cases" - }, - { - "guard": "ComputeMxdequantLengthFits", - "why": "case 11 is never accepted" - }, - { - "guard": "ComputeDstTilelink", - "why": "no TileLink backend variant exists" - } + "elab_covered_elsewhere" : ["tb_idma_reg_frontend"], + "guards_untested" : [ + { "guard" : "ComputeMxFp16Width", "why" : "only reachable from the excluded 1024 cases" }, + { "guard" : "ComputeMxdequantLengthFits", "why" : "case 11 is never accepted" }, + { "guard" : "ComputeDstTilelink", "why" : "no TileLink backend variant exists" } ], - "tops_untested": [ - { - "top": "tb_idma_nd_midend_b2b", - "why": "hangs ([B2B] timeout); latent reset/LFSR race" - }, - { - "top": "tb_idma_transpose_nd", - "why": "AW outside the destination allocation at 80 ns" - }, - { - "top": "tb_idma_transpose_b2b", - "why": "stream_watchdog trip after 4000 idle cycles" - }, - { - "top": "tb_idma_mxrand", - "why": "testbench timeout at 400 ms of simulated time" - }, - { - "top": "tb_idma_mxperf", - "why": "hangs on the first compute transfer" - }, - { - "top": "tb_idma_rt_midend", - "why": "fails under --assert on cc_rr_arb_tree lock" - }, - { - "top": "the 88 directed job files", - "why": "verilator cannot drive the idma_test class-based stimulus" - } + "tops_untested" : [ + { "top" : "tb_idma_nd_midend_b2b", "why" : "hangs ([B2B] timeout); latent reset/LFSR race" }, + { "top" : "tb_idma_transpose_nd", "why" : "AW outside the destination allocation at 80 ns" }, + { "top" : "tb_idma_transpose_b2b", "why" : "stream_watchdog trip after 4000 idle cycles" }, + { "top" : "tb_idma_mxrand", "why" : "testbench timeout at 400 ms of simulated time" }, + { "top" : "tb_idma_mxperf", "why" : "hangs on the first compute transfer" }, + { "top" : "tb_idma_rt_midend", "why" : "fails under --assert on cc_rr_arb_tree lock" }, + { "top" : "the 88 directed job files", "why" : "verilator cannot drive the class-based stimulus" } ] } } diff --git a/util/run_verify.py b/util/run_verify.py index 720d0601..2ceade31 100644 --- a/util/run_verify.py +++ b/util/run_verify.py @@ -46,7 +46,10 @@ def load(path=None): continue suite = dict(entry['verify']) suite['top'] = entry['testbench'] - suite['runs'] = suite.pop('legs') + # entry params are common to every leg; a leg may override one + common = entry.get('params', {}) + suite['runs'] = [dict(r, params={**common, **r.get('params', {})}) + for r in suite.pop('legs')] db['suites'][name] = suite return db