From faa38153a2cf397955f375f511a3eb63f0cadb39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Giuliani?= Date: Wed, 5 Aug 2026 10:13:03 +0200 Subject: [PATCH 01/11] scripts/run_vsim.sh: fix regression-script bugs and restore axi_xbar coverage (#438) - Merge the two duplicate `axi_xbar` case branches into one: the second branch (added in 78f2999) was shadowed by the first and never ran. The two parameter sweeps are complementary (exclusive/unique-id handling vs. ID-width usage, data width and pipelining), so both are now executed. - Scope SEEDS to each test (`local` copy) so per-TB seed additions (e.g. axi_lite_regs) no longer leak into subsequent tests of a full run. - Redirect vsim's stderr into the log (`2>&1` before `tee`, not after). - Reject any unrecognized dash-prefixed flag (`-*`) instead of only the `-*--*` pattern, which missed typos like `--foo`. - Fix stale `--random` reference in the header comment (`--random-seed`). --- scripts/run_vsim.sh | 57 ++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index aaf8c3f22..4a25b7763 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -23,18 +23,21 @@ if test -z ${VSIM+x}; then fi # Seed values for `sv_seed`; can be extended with specific values on a per-TB basis, as well as with -# a random number by passing the `--random` flag. The default value, 0, is always included to stay -# regression-consistent. +# a random number by passing the `--random-seed` flag. The default value, 0, is always included to +# stay regression-consistent. SEEDS=(0) call_vsim() { for seed in ${SEEDS[@]}; do - echo "run -all" | $VSIM -sv_seed $seed "$@" | tee vsim.log 2>&1 + echo "run -all" | $VSIM -sv_seed $seed "$@" 2>&1 | tee vsim.log grep "Errors: 0," vsim.log done } exec_test() { + # Work on a per-test copy so that any per-TB seed additions below do not leak into the + # subsequent tests of a full run. + local SEEDS=("${SEEDS[@]}") if [ ! -e "$ROOT/test/tb_$1.sv" ]; then echo "Testbench for '$1' not found!" exit 1 @@ -176,6 +179,8 @@ exec_test() { done ;; axi_xbar) + # Two complementary sweeps (see issue #438): the first varies exclusive-access and + # unique-id handling, the second varies ID-width usage, data width and pipelining. for NumMst in 1 6; do for NumSlv in 1 8; do for Atop in 0 1; do @@ -189,29 +194,6 @@ exec_test() { done done done - ;; - axi_to_mem_banked) - for MEM_LAT in 1 2; do - for BANK_FACTOR in 1 2; do - for NUM_BANKS in 1 2 ; do - for AXI_DATA_WIDTH in 64 256 ; do - ACT_BANKS=$((2*$BANK_FACTOR*$NUM_BANKS)) - MEM_DATA_WIDTH=$(($AXI_DATA_WIDTH/$NUM_BANKS)) - call_vsim tb_axi_to_mem_banked \ - -voptargs="+acc +cover=bcesfx" \ - -gTbAxiDataWidth=$AXI_DATA_WIDTH \ - -gTbNumWords=2048 \ - -gTbNumBanks=$ACT_BANKS \ - -gTbMemDataWidth=$MEM_DATA_WIDTH \ - -gTbMemLatency=$MEM_LAT \ - -gTbNumWrites=2000 \ - -gTbNumReads=2000 - done - done - done - done - ;; - axi_xbar) for GEN_ATOP in 0 1; do for NUM_MST in 1 6; do for NUM_SLV in 2 9; do @@ -234,6 +216,27 @@ exec_test() { done done ;; + axi_to_mem_banked) + for MEM_LAT in 1 2; do + for BANK_FACTOR in 1 2; do + for NUM_BANKS in 1 2 ; do + for AXI_DATA_WIDTH in 64 256 ; do + ACT_BANKS=$((2*$BANK_FACTOR*$NUM_BANKS)) + MEM_DATA_WIDTH=$(($AXI_DATA_WIDTH/$NUM_BANKS)) + call_vsim tb_axi_to_mem_banked \ + -voptargs="+acc +cover=bcesfx" \ + -gTbAxiDataWidth=$AXI_DATA_WIDTH \ + -gTbNumWords=2048 \ + -gTbNumBanks=$ACT_BANKS \ + -gTbMemDataWidth=$MEM_DATA_WIDTH \ + -gTbMemLatency=$MEM_LAT \ + -gTbNumWrites=2000 \ + -gTbNumReads=2000 + done + done + done + done + ;; axi_lite_dw_converter) for DWSLV in 32 64 128; do for DWMST in 16 32 64; do @@ -254,7 +257,7 @@ while (( "$#" )); do --random-seed) SEEDS+=(random) shift;; - -*--*) # unsupported flag + -*) # unsupported flag (any dash-prefixed token not matched above) echo "Error: Unsupported flag '$1'." >&2 exit 1;; *) # preserve positional arguments From 3f0b7ca5e785e2cb640a8c2ac9f9d6bdc76604cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Giuliani?= Date: Wed, 19 Aug 2026 09:16:46 +0200 Subject: [PATCH 02/11] add bounded parallelism - Add VSIM_JOBS (bounded vsim/license concurrency); set to 2 to evaluate CI impact. 1 = legacy sequential path. --- scripts/run_vsim.sh | 50 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index 4a25b7763..0156f247f 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -27,10 +27,50 @@ fi # stay regression-consistent. SEEDS=(0) +# Maximum number of `vsim` invocations to run concurrently. Each running simulation occupies one +# simulator license seat, so this also caps the license usage regardless of how many +# parametrizations a test sweeps. Defaults to 1 (fully sequential, i.e. unchanged behaviour); set +# e.g. `VSIM_JOBS=4` to parallelize. NOTE: values > 1 run multiple `vsim` processes in the same +# directory sharing the compiled `work` library; each gets its own log/wlf, but validate on your +# simulator before relying on it. +: "${VSIM_JOBS:=2}" + +vsim_fail=0 # set to 1 as soon as any background simulation reports errors +job_idx=0 # unique index per launched simulation, used for per-job artifact names + +# Block until fewer than VSIM_JOBS simulations are running. +vsim_throttle() { + while (( $(jobs -rp | wc -l) >= VSIM_JOBS )); do + wait -n || vsim_fail=1 + done +} + +# Wait for all still-running simulations to finish. +vsim_drain() { + local pid + for pid in $(jobs -rp); do + wait "$pid" || vsim_fail=1 + done +} + call_vsim() { - for seed in ${SEEDS[@]}; do - echo "run -all" | $VSIM -sv_seed $seed "$@" 2>&1 | tee vsim.log - grep "Errors: 0," vsim.log + local seed idx log + for seed in "${SEEDS[@]}"; do + if (( VSIM_JOBS <= 1 )); then + # Sequential path: legacy behaviour, unchanged. Single log, fail-fast via `set -e`. + echo "run -all" | $VSIM -sv_seed "$seed" "$@" 2>&1 | tee vsim.log + grep "Errors: 0," vsim.log + else + # Parallel path: bounded job pool, one log/wlf per job, failures collected in vsim_fail. + vsim_throttle + idx=$job_idx + job_idx=$((job_idx + 1)) + log="vsim.${1}.${idx}.log" + ( + echo "run -all" | $VSIM -sv_seed "$seed" -wlf "vsim.${idx}.wlf" "$@" 2>&1 | tee "$log" + grep "Errors: 0," "$log" + ) & + fi done } @@ -281,3 +321,7 @@ fi for t in "${tests[@]}"; do exec_test $t done + +# Wait for the last in-flight simulations and fail if any of them reported errors. +vsim_drain +exit $vsim_fail From b291e9ab6d291f82e3f3d303b4978e0107a4e542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Giuliani?= Date: Thu, 20 Aug 2026 10:38:51 +0200 Subject: [PATCH 03/11] decrease the number of sweeps in axi xbar --- scripts/run_vsim.sh | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index 0156f247f..ead9c5505 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -236,21 +236,19 @@ exec_test() { done for GEN_ATOP in 0 1; do for NUM_MST in 1 6; do - for NUM_SLV in 2 9; do - for MST_ID_USE in 3 5; do - MST_ID=5 - for DATA_WIDTH in 64 256; do - for PIPE in 0 1; do - call_vsim tb_axi_xbar -t 1ns -voptargs="+acc" \ - -gTbNumMasters=$NUM_MST \ - -gTbNumSlaves=$NUM_SLV \ - -gTbAxiIdWidthMasters=$MST_ID \ - -gTbAxiIdUsed=$MST_ID_USE \ - -gTbAxiDataWidth=$DATA_WIDTH \ - -gTbPipeline=$PIPE \ - -gTbEnAtop=$GEN_ATOP - done - done + NUM_SLV=9 + MST_ID=5 + MST_ID_USE=3 + for DATA_WIDTH in 64 256; do + for PIPE in 0 1; do + call_vsim tb_axi_xbar -t 1ns -voptargs="+acc" \ + -gTbNumMasters=$NUM_MST \ + -gTbNumSlaves=$NUM_SLV \ + -gTbAxiIdWidthMasters=$MST_ID \ + -gTbAxiIdUsed=$MST_ID_USE \ + -gTbAxiDataWidth=$DATA_WIDTH \ + -gTbPipeline=$PIPE \ + -gTbEnAtop=$GEN_ATOP done done done From 49934687acd809264679edc362f8722880007208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Giuliani?= Date: Wed, 26 Aug 2026 10:16:07 +0200 Subject: [PATCH 04/11] run_vsim.sh: robust parallel failure handling + xbar sweep coverage Address review feedback on the bounded-parallelism approach: - Track launched vsim PIDs explicitly and wait on each one instead of scanning `jobs -rp`, which lists only running jobs and would silently drop a job that already failed (e.g. an elaboration error) before the drain, yielding a false-green CI. - Restore the IdUsed == IdWidth corner in the xbar sweep (distinct ID handling path). - Drop `+acc` from the xbar sweep: it only adds signal visibility for waveform/PLI, useless in a batch-only CI run. --- scripts/run_vsim.sh | 50 +++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index ead9c5505..3e3dce764 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -29,28 +29,34 @@ SEEDS=(0) # Maximum number of `vsim` invocations to run concurrently. Each running simulation occupies one # simulator license seat, so this also caps the license usage regardless of how many -# parametrizations a test sweeps. Defaults to 1 (fully sequential, i.e. unchanged behaviour); set -# e.g. `VSIM_JOBS=4` to parallelize. NOTE: values > 1 run multiple `vsim` processes in the same -# directory sharing the compiled `work` library; each gets its own log/wlf, but validate on your -# simulator before relying on it. +# parametrizations a test sweeps. Defaults to 2; set `VSIM_JOBS=1` for the legacy fully-sequential +# behaviour, or e.g. `VSIM_JOBS=4` for more parallelism. NOTE: values > 1 run multiple `vsim` +# processes in the same directory sharing the compiled `work` library; each gets its own log/wlf, +# but validate on your simulator before relying on it. : "${VSIM_JOBS:=2}" vsim_fail=0 # set to 1 as soon as any background simulation reports errors job_idx=0 # unique index per launched simulation, used for per-job artifact names +vsim_pids=() # PIDs of launched background simulations not yet waited on -# Block until fewer than VSIM_JOBS simulations are running. +# Block until fewer than VSIM_JOBS simulations are in flight. We wait on tracked PIDs rather than +# scanning `jobs -rp`: `wait "$pid"` reliably reports the exit status of a job even if it already +# terminated before we got here (e.g. a fast elaboration error), which `jobs -rp` would omit and +# thus silently drop the failure. vsim_throttle() { - while (( $(jobs -rp | wc -l) >= VSIM_JOBS )); do - wait -n || vsim_fail=1 + while (( ${#vsim_pids[@]} >= VSIM_JOBS )); do + wait "${vsim_pids[0]}" || vsim_fail=1 + vsim_pids=("${vsim_pids[@]:1}") done } -# Wait for all still-running simulations to finish. +# Wait for all still-pending simulations to finish, recording any failures. vsim_drain() { local pid - for pid in $(jobs -rp); do + for pid in "${vsim_pids[@]}"; do wait "$pid" || vsim_fail=1 done + vsim_pids=() } call_vsim() { @@ -70,6 +76,7 @@ call_vsim() { echo "run -all" | $VSIM -sv_seed "$seed" -wlf "vsim.${idx}.wlf" "$@" 2>&1 | tee "$log" grep "Errors: 0," "$log" ) & + vsim_pids+=($!) fi done } @@ -238,17 +245,20 @@ exec_test() { for NUM_MST in 1 6; do NUM_SLV=9 MST_ID=5 - MST_ID_USE=3 - for DATA_WIDTH in 64 256; do - for PIPE in 0 1; do - call_vsim tb_axi_xbar -t 1ns -voptargs="+acc" \ - -gTbNumMasters=$NUM_MST \ - -gTbNumSlaves=$NUM_SLV \ - -gTbAxiIdWidthMasters=$MST_ID \ - -gTbAxiIdUsed=$MST_ID_USE \ - -gTbAxiDataWidth=$DATA_WIDTH \ - -gTbPipeline=$PIPE \ - -gTbEnAtop=$GEN_ATOP + # Sweep both IdUsed < IdWidth (3) and IdUsed == IdWidth (5), as the equal-width + # case exercises a distinct code path in the ID handling. + for MST_ID_USE in 3 5; do + for DATA_WIDTH in 64 256; do + for PIPE in 0 1; do + call_vsim tb_axi_xbar -t 1ns \ + -gTbNumMasters=$NUM_MST \ + -gTbNumSlaves=$NUM_SLV \ + -gTbAxiIdWidthMasters=$MST_ID \ + -gTbAxiIdUsed=$MST_ID_USE \ + -gTbAxiDataWidth=$DATA_WIDTH \ + -gTbPipeline=$PIPE \ + -gTbEnAtop=$GEN_ATOP + done done done done From dfb8c9eb78f68aafe4187bca2daab937e18564cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Giuliani?= Date: Wed, 26 Aug 2026 10:18:24 +0200 Subject: [PATCH 05/11] ci: parallelize axi_xbar sweep via a split CI job instead of bash Per review suggestion: rather than driving parallelism from run_vsim.sh with a bash job pool (VSIM_JOBS/throttle/drain), split the axi_xbar parameter sweep into two testbench-sharing CI jobs, axi_xbar and axi_xbar2, that the runner schedules concurrently. This keeps run_vsim.sh simple and sequential (fail-fast via set -e), makes per-shard results/logs easy to inspect, and lets CI concurrency limits govern simulator-license usage. - run_vsim.sh: drop the bounded-parallelism machinery; call_vsim is sequential again. Add the axi_xbar2 shard (sweep 2: ID-width usage, data width, pipelining) reusing tb_axi_xbar; axi_xbar keeps sweep 1. - .gitlab-ci.yml: add axi_xbar2 job; factor the shared xbar trigger set into a reusable anchor and add tb_axi_xbar_pkg.sv to it (was missing). Trade-off to evaluate with this trial: license capping now lives with CI job concurrency rather than the script. Backup of the bash approach kept at branch ng/vsim_opt-backup. --- .gitlab-ci.yml | 18 +++++++++-- scripts/run_vsim.sh | 76 ++++++++++++--------------------------------- 2 files changed, 36 insertions(+), 58 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 146819335..7ed26f830 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -246,13 +246,27 @@ axi_xbar: TEST_MODULE: axi_xbar rules: - *run_vsim_common_change_rule - - *run_vsim_module_change_rule - - changes: + - &axi_xbar_module_change_rule + changes: compare_to: 'refs/heads/master' paths: + - src/axi_xbar.sv + - test/tb_axi_xbar.sv + - test/tb_axi_xbar_pkg.sv - src/axi_demux.sv - src/axi_demux_simple.sv - src/axi_err_slv.sv - src/axi_mux.sv - src/axi_multicut.sv - src/axi_xbar_unmuxed.sv + +# Second half of the `axi_xbar` parameter sweep (see scripts/run_vsim.sh). Split into its own CI +# job so the runner executes it in parallel with `axi_xbar`; both reuse the same testbench and share +# the identical trigger set. +axi_xbar2: + extends: .run_vsim + variables: + TEST_MODULE: axi_xbar2 + rules: + - *run_vsim_common_change_rule + - *axi_xbar_module_change_rule diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index 3e3dce764..913a13c35 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -27,57 +27,15 @@ fi # stay regression-consistent. SEEDS=(0) -# Maximum number of `vsim` invocations to run concurrently. Each running simulation occupies one -# simulator license seat, so this also caps the license usage regardless of how many -# parametrizations a test sweeps. Defaults to 2; set `VSIM_JOBS=1` for the legacy fully-sequential -# behaviour, or e.g. `VSIM_JOBS=4` for more parallelism. NOTE: values > 1 run multiple `vsim` -# processes in the same directory sharing the compiled `work` library; each gets its own log/wlf, -# but validate on your simulator before relying on it. -: "${VSIM_JOBS:=2}" - -vsim_fail=0 # set to 1 as soon as any background simulation reports errors -job_idx=0 # unique index per launched simulation, used for per-job artifact names -vsim_pids=() # PIDs of launched background simulations not yet waited on - -# Block until fewer than VSIM_JOBS simulations are in flight. We wait on tracked PIDs rather than -# scanning `jobs -rp`: `wait "$pid"` reliably reports the exit status of a job even if it already -# terminated before we got here (e.g. a fast elaboration error), which `jobs -rp` would omit and -# thus silently drop the failure. -vsim_throttle() { - while (( ${#vsim_pids[@]} >= VSIM_JOBS )); do - wait "${vsim_pids[0]}" || vsim_fail=1 - vsim_pids=("${vsim_pids[@]:1}") - done -} - -# Wait for all still-pending simulations to finish, recording any failures. -vsim_drain() { - local pid - for pid in "${vsim_pids[@]}"; do - wait "$pid" || vsim_fail=1 - done - vsim_pids=() -} - +# Parallelism is provided by the CI: heavy sweeps (e.g. `axi_xbar`) are split across separate CI +# jobs (`axi_xbar`, `axi_xbar2`, ...) that the runner schedules concurrently. This keeps this +# script simple and sequential, with fail-fast semantics via `set -e`, and lets the CI cap the +# concurrent simulator-license usage through its own job concurrency limits. call_vsim() { - local seed idx log + local seed for seed in "${SEEDS[@]}"; do - if (( VSIM_JOBS <= 1 )); then - # Sequential path: legacy behaviour, unchanged. Single log, fail-fast via `set -e`. - echo "run -all" | $VSIM -sv_seed "$seed" "$@" 2>&1 | tee vsim.log - grep "Errors: 0," vsim.log - else - # Parallel path: bounded job pool, one log/wlf per job, failures collected in vsim_fail. - vsim_throttle - idx=$job_idx - job_idx=$((job_idx + 1)) - log="vsim.${1}.${idx}.log" - ( - echo "run -all" | $VSIM -sv_seed "$seed" -wlf "vsim.${idx}.wlf" "$@" 2>&1 | tee "$log" - grep "Errors: 0," "$log" - ) & - vsim_pids+=($!) - fi + echo "run -all" | $VSIM -sv_seed "$seed" "$@" 2>&1 | tee vsim.log + grep "Errors: 0," vsim.log done } @@ -85,7 +43,13 @@ exec_test() { # Work on a per-test copy so that any per-TB seed additions below do not leak into the # subsequent tests of a full run. local SEEDS=("${SEEDS[@]}") - if [ ! -e "$ROOT/test/tb_$1.sv" ]; then + # Testbench source backing this test. Usually `tb_.sv`, but sweep shards such as + # `axi_xbar2` are additional CI jobs that reuse another test's testbench. + local tb="tb_$1" + case "$1" in + axi_xbar2) tb="tb_axi_xbar" ;; + esac + if [ ! -e "$ROOT/test/$tb.sv" ]; then echo "Testbench for '$1' not found!" exit 1 fi @@ -226,8 +190,8 @@ exec_test() { done ;; axi_xbar) - # Two complementary sweeps (see issue #438): the first varies exclusive-access and - # unique-id handling, the second varies ID-width usage, data width and pipelining. + # Sweep 1 of 2 (see issue #438): vary exclusive-access and unique-id handling. + # The complementary sweep runs as a separate CI job, `axi_xbar2`. for NumMst in 1 6; do for NumSlv in 1 8; do for Atop in 0 1; do @@ -241,6 +205,10 @@ exec_test() { done done done + ;; + axi_xbar2) + # Sweep 2 of 2 (see issue #438): vary ID-width usage, data width and pipelining. + # Reuses tb_axi_xbar; split into its own CI job so it runs in parallel with `axi_xbar`. for GEN_ATOP in 0 1; do for NUM_MST in 1 6; do NUM_SLV=9 @@ -329,7 +297,3 @@ fi for t in "${tests[@]}"; do exec_test $t done - -# Wait for the last in-flight simulations and fail if any of them reported errors. -vsim_drain -exit $vsim_fail From b1ffcfc17db326ee2cd0e0ddd88c04d98dab1d65 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 26 Aug 2026 16:18:33 +0200 Subject: [PATCH 06/11] scripts/run_vsim.sh: drop +acc, batch simulations need no debug visibility +acc tells vopt to preserve access to internal objects for interactive debugging (waves, single-stepping, PLI). The CI runs are batch-only, so this only slows the simulations down. Coverage instrumentation is unaffected: +cover=bcesfx is kept where it was. --- scripts/run_vsim.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index 913a13c35..6501067a0 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -126,7 +126,7 @@ exec_test() { if [ $MAX_UNIQ_SLV_PORT_IDS -le $MAX_MST_PORT_IDS ]; then call_vsim tb_axi_iw_converter \ -t 1ns -coverage -classdebug \ - -voptargs="+acc +cover=bcesfx" \ + -voptargs="+cover=bcesfx" \ -GTbEnExcl=$EXCL \ -GTbAxiSlvPortIdWidth=$SLV_PORT_IW \ -GTbAxiMstPortIdWidth=$MST_PORT_IW \ @@ -135,7 +135,7 @@ exec_test() { else call_vsim tb_axi_iw_converter \ -t 1ns -coverage -classdebug \ - -voptargs="+acc +cover=bcesfx" \ + -voptargs="+cover=bcesfx" \ -GTbEnExcl=$EXCL \ -GTbAxiSlvPortIdWidth=$SLV_PORT_IW \ -GTbAxiMstPortIdWidth=$MST_PORT_IW \ @@ -148,7 +148,7 @@ exec_test() { else call_vsim tb_axi_iw_converter \ -t 1ns -coverage -classdebug \ - -voptargs="+acc +cover=bcesfx" \ + -voptargs="+cover=bcesfx" \ -GTbEnExcl=$EXCL \ -GTbAxiSlvPortIdWidth=$SLV_PORT_IW \ -GTbAxiMstPortIdWidth=$MST_PORT_IW \ @@ -240,7 +240,7 @@ exec_test() { ACT_BANKS=$((2*$BANK_FACTOR*$NUM_BANKS)) MEM_DATA_WIDTH=$(($AXI_DATA_WIDTH/$NUM_BANKS)) call_vsim tb_axi_to_mem_banked \ - -voptargs="+acc +cover=bcesfx" \ + -voptargs="+cover=bcesfx" \ -gTbAxiDataWidth=$AXI_DATA_WIDTH \ -gTbNumWords=2048 \ -gTbNumBanks=$ACT_BANKS \ @@ -261,7 +261,7 @@ exec_test() { done ;; *) - call_vsim tb_$1 -t 1ns -coverage -voptargs="+acc +cover=bcesfx" + call_vsim tb_$1 -t 1ns -coverage -voptargs="+cover=bcesfx" ;; esac } From 899697de138ff20badf7440d8a010d33f84fd3ba Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 26 Aug 2026 17:02:14 +0200 Subject: [PATCH 07/11] scripts/run_vsim.sh: drop unused coverage instrumentation The +cover=bcesfx / -coverage / -classdebug flags have collected coverage into memory since 2020, but no UCDB was ever saved, merged, or reported anywhere in the repository's history, so the data was discarded on every vsim exit. The instrumentation only cost simulation time (measured with questa-2025.1: tb_axi_addr_test 46 s -> 34 s, tb_axi_iw_converter 4.8 s -> 3.1 s per configuration). If code coverage is ever wanted for real, it needs the full loop (save per-run UCDBs, vcover merge, report, review); that should be introduced deliberately as its own change. --- scripts/run_vsim.sh | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index 6501067a0..a89c70e7a 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -125,8 +125,7 @@ exec_test() { MAX_MST_PORT_IDS=$((2**MST_PORT_IW)) if [ $MAX_UNIQ_SLV_PORT_IDS -le $MAX_MST_PORT_IDS ]; then call_vsim tb_axi_iw_converter \ - -t 1ns -coverage -classdebug \ - -voptargs="+cover=bcesfx" \ + -t 1ns \ -GTbEnExcl=$EXCL \ -GTbAxiSlvPortIdWidth=$SLV_PORT_IW \ -GTbAxiMstPortIdWidth=$MST_PORT_IW \ @@ -134,8 +133,7 @@ exec_test() { -GTbAxiSlvPortMaxTxnsPerId=5 else call_vsim tb_axi_iw_converter \ - -t 1ns -coverage -classdebug \ - -voptargs="+cover=bcesfx" \ + -t 1ns \ -GTbEnExcl=$EXCL \ -GTbAxiSlvPortIdWidth=$SLV_PORT_IW \ -GTbAxiMstPortIdWidth=$MST_PORT_IW \ @@ -147,8 +145,7 @@ exec_test() { done else call_vsim tb_axi_iw_converter \ - -t 1ns -coverage -classdebug \ - -voptargs="+cover=bcesfx" \ + -t 1ns \ -GTbEnExcl=$EXCL \ -GTbAxiSlvPortIdWidth=$SLV_PORT_IW \ -GTbAxiMstPortIdWidth=$MST_PORT_IW \ @@ -240,7 +237,6 @@ exec_test() { ACT_BANKS=$((2*$BANK_FACTOR*$NUM_BANKS)) MEM_DATA_WIDTH=$(($AXI_DATA_WIDTH/$NUM_BANKS)) call_vsim tb_axi_to_mem_banked \ - -voptargs="+cover=bcesfx" \ -gTbAxiDataWidth=$AXI_DATA_WIDTH \ -gTbNumWords=2048 \ -gTbNumBanks=$ACT_BANKS \ @@ -261,7 +257,7 @@ exec_test() { done ;; *) - call_vsim tb_$1 -t 1ns -coverage -voptargs="+cover=bcesfx" + call_vsim tb_$1 -t 1ns ;; esac } From 221ff0f2f36d962fa797547b13ac38303d45486d Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 26 Aug 2026 20:19:31 +0200 Subject: [PATCH 08/11] scripts/run_vsim.sh: shard sweeps via CI parallel jobs, not manual splits Index every simulation in enumeration order and filter on CI_NODE_INDEX/CI_NODE_TOTAL, so splitting a heavy sweep is just `parallel: N` on its CI job. This removes the axi_xbar2 pseudo-test (sweep merged back into axi_xbar, identical configurations, verified via --list diff against the old enumeration). Also: per-config log files (kept as CI artifacts on failure) instead of one overwritten vsim.log, a --list mode to inspect sweep coverage, and a loud failure for a shard that executed zero simulations. --- .gitlab-ci.yml | 23 ++++++------- scripts/run_vsim.sh | 78 ++++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7ed26f830..d0018d6e4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,6 +79,11 @@ verilator_lint: - vsim script: - cd build && ../scripts/run_vsim.sh --random-seed $TEST_MODULE + artifacts: + when: on_failure + paths: + - build/vsim-*.log + expire_in: 1 week rules: - &run_vsim_common_change_rule changes: @@ -240,14 +245,17 @@ axi_to_mem_banked: - src/axi_to_detailed_mem.sv - src/axi_to_mem.sv +# The xbar sweep is the heaviest; `parallel:` splits it into shards that the runner executes +# concurrently. Each shard runs the identical `run_vsim.sh axi_xbar` enumeration and executes its +# share of the configs, selected round-robin via `CI_NODE_INDEX` (see scripts/run_vsim.sh). axi_xbar: extends: .run_vsim + parallel: 2 variables: TEST_MODULE: axi_xbar rules: - *run_vsim_common_change_rule - - &axi_xbar_module_change_rule - changes: + - changes: compare_to: 'refs/heads/master' paths: - src/axi_xbar.sv @@ -259,14 +267,3 @@ axi_xbar: - src/axi_mux.sv - src/axi_multicut.sv - src/axi_xbar_unmuxed.sv - -# Second half of the `axi_xbar` parameter sweep (see scripts/run_vsim.sh). Split into its own CI -# job so the runner executes it in parallel with `axi_xbar`; both reuse the same testbench and share -# the identical trigger set. -axi_xbar2: - extends: .run_vsim - variables: - TEST_MODULE: axi_xbar2 - rules: - - *run_vsim_common_change_rule - - *axi_xbar_module_change_rule diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index a89c70e7a..b2321a26d 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -27,15 +27,39 @@ fi # stay regression-consistent. SEEDS=(0) -# Parallelism is provided by the CI: heavy sweeps (e.g. `axi_xbar`) are split across separate CI -# jobs (`axi_xbar`, `axi_xbar2`, ...) that the runner schedules concurrently. This keeps this -# script simple and sequential, with fail-fast semantics via `set -e`, and lets the CI cap the -# concurrent simulator-license usage through its own job concurrency limits. +# Every simulation (one parametrization run with one seed) gets a deterministic index in the +# enumeration order of this script. Parallelism is provided by the CI: a heavy sweep is split by +# running N identical copies of its job (GitLab `parallel: N`), and each copy walks the same +# enumeration but executes only the configs whose index falls on it (round-robin over +# `CI_NODE_INDEX`/`CI_NODE_TOTAL`). +# +# Reproduce one CI shard locally with e.g.: +# CI_NODE_INDEX=2 CI_NODE_TOTAL=2 ../scripts/run_vsim.sh axi_xbar +# Pass `--list` to print the enumerated configs with their indices instead of simulating. +CONFIG_IDX=0 +NUM_EXECUTED=0 +NODE_INDEX=${CI_NODE_INDEX:-1} +NODE_TOTAL=${CI_NODE_TOTAL:-1} +LIST_ONLY=0 + call_vsim() { - local seed + local seed log for seed in "${SEEDS[@]}"; do - echo "run -all" | $VSIM -sv_seed "$seed" "$@" 2>&1 | tee vsim.log - grep "Errors: 0," vsim.log + CONFIG_IDX=$((CONFIG_IDX + 1)) + # Round-robin sharding: skip configs that belong to another CI node. + if (( (CONFIG_IDX - 1) % NODE_TOTAL != NODE_INDEX - 1 )); then + continue + fi + if (( LIST_ONLY )); then + echo "$CONFIG_IDX: $* -sv_seed $seed" + continue + fi + # One log file per config, so a full sweep leaves every log behind and the actual value + # of a random seed can be recovered from the log after a failure. + log="vsim-$1-$CONFIG_IDX.log" + echo "run -all" | $VSIM -sv_seed "$seed" "$@" 2>&1 | tee "$log" + grep "Errors: 0," "$log" + NUM_EXECUTED=$((NUM_EXECUTED + 1)) done } @@ -43,13 +67,7 @@ exec_test() { # Work on a per-test copy so that any per-TB seed additions below do not leak into the # subsequent tests of a full run. local SEEDS=("${SEEDS[@]}") - # Testbench source backing this test. Usually `tb_.sv`, but sweep shards such as - # `axi_xbar2` are additional CI jobs that reuse another test's testbench. - local tb="tb_$1" - case "$1" in - axi_xbar2) tb="tb_axi_xbar" ;; - esac - if [ ! -e "$ROOT/test/$tb.sv" ]; then + if [ ! -e "$ROOT/test/tb_$1.sv" ]; then echo "Testbench for '$1' not found!" exit 1 fi @@ -187,8 +205,7 @@ exec_test() { done ;; axi_xbar) - # Sweep 1 of 2 (see issue #438): vary exclusive-access and unique-id handling. - # The complementary sweep runs as a separate CI job, `axi_xbar2`. + # Sweep 1: vary exclusive-access and unique-id handling. for NumMst in 1 6; do for NumSlv in 1 8; do for Atop in 0 1; do @@ -202,10 +219,7 @@ exec_test() { done done done - ;; - axi_xbar2) - # Sweep 2 of 2 (see issue #438): vary ID-width usage, data width and pipelining. - # Reuses tb_axi_xbar; split into its own CI job so it runs in parallel with `axi_xbar`. + # Sweep 2: vary ID-width usage, data width and pipelining. for GEN_ATOP in 0 1; do for NUM_MST in 1 6; do NUM_SLV=9 @@ -262,34 +276,40 @@ exec_test() { esac } -# Parse flags. -PARAMS="" +# Parse arguments. +tests=() while (( "$#" )); do case "$1" in --random-seed) SEEDS+=(random) shift;; + --list) + LIST_ONLY=1 + shift;; -*) # unsupported flag (any dash-prefixed token not matched above) echo "Error: Unsupported flag '$1'." >&2 exit 1;; - *) # preserve positional arguments - PARAMS="$PARAMS $1" + *) # positional argument: a test name + tests+=("$1") shift;; esac done -eval set -- "$PARAMS" -if [ "$#" -eq 0 ]; then - tests=() +if [ ${#tests[@]} -eq 0 ]; then while IFS= read -r -d $'\0'; do tb_name="$(basename -s .sv $REPLY)" dut_name="${tb_name#tb_}" tests+=("$dut_name") done < <(find "$ROOT/test" -name 'tb_*.sv' -a \( ! -name '*_pkg.sv' \) -print0) -else - tests=("$@") fi for t in "${tests[@]}"; do exec_test $t done + +# A shard that matches no config at all (e.g. `parallel: N` larger than the number of configs of +# a test) must fail loudly instead of passing as a vacuously green CI job. +if (( ! LIST_ONLY && NUM_EXECUTED == 0 )); then + echo "Error: no simulations executed on this shard (node $NODE_INDEX of $NODE_TOTAL)." >&2 + exit 1 +fi From 6db402ae8e655e92acff2ea4f9b405a176661ea5 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Wed, 26 Aug 2026 20:37:52 +0200 Subject: [PATCH 09/11] scripts/run_vsim.sh: gate sweep sharding behind an explicit VSIM_SHARDED opt-in GitLab sets CI_NODE_INDEX/CI_NODE_TOTAL on parallel:matrix jobs too, where they mean the matrix position. Trusting them unconditionally made the 12-module matrix job shard itself 12 ways: some modules executed nothing and failed, others silently ran a fraction of their configs and passed. Shard only when the job declares VSIM_SHARDED, as the axi_xbar job now does alongside its parallel: count. Also drop the vsim log upload from the test jobs. --- .gitlab-ci.yml | 8 +++----- scripts/run_vsim.sh | 26 +++++++++++++++----------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d0018d6e4..6c6ec99f3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -79,11 +79,6 @@ verilator_lint: - vsim script: - cd build && ../scripts/run_vsim.sh --random-seed $TEST_MODULE - artifacts: - when: on_failure - paths: - - build/vsim-*.log - expire_in: 1 week rules: - &run_vsim_common_change_rule changes: @@ -248,11 +243,14 @@ axi_to_mem_banked: # The xbar sweep is the heaviest; `parallel:` splits it into shards that the runner executes # concurrently. Each shard runs the identical `run_vsim.sh axi_xbar` enumeration and executes its # share of the configs, selected round-robin via `CI_NODE_INDEX` (see scripts/run_vsim.sh). +# `VSIM_SHARDED` opts in to that: without it the script must ignore `CI_NODE_*`, which GitLab +# also sets on every `parallel:matrix` job (as the matrix position). axi_xbar: extends: .run_vsim parallel: 2 variables: TEST_MODULE: axi_xbar + VSIM_SHARDED: '1' rules: - *run_vsim_common_change_rule - changes: diff --git a/scripts/run_vsim.sh b/scripts/run_vsim.sh index b2321a26d..5f4b996c3 100755 --- a/scripts/run_vsim.sh +++ b/scripts/run_vsim.sh @@ -29,25 +29,29 @@ SEEDS=(0) # Every simulation (one parametrization run with one seed) gets a deterministic index in the # enumeration order of this script. Parallelism is provided by the CI: a heavy sweep is split by -# running N identical copies of its job (GitLab `parallel: N`), and each copy walks the same -# enumeration but executes only the configs whose index falls on it (round-robin over -# `CI_NODE_INDEX`/`CI_NODE_TOTAL`). +# putting `parallel: N` on its job together with `VSIM_SHARDED=1`. Each copy walks the same +# enumeration but executes only the configs whose index falls on its shard (round-robin over +# `CI_NODE_INDEX`/`CI_NODE_TOTAL`). The `VSIM_SHARDED` opt-in is needed because `parallel:matrix` +# jobs also get `CI_NODE_*` set, where they mean the matrix position, not a sweep shard. # # Reproduce one CI shard locally with e.g.: -# CI_NODE_INDEX=2 CI_NODE_TOTAL=2 ../scripts/run_vsim.sh axi_xbar +# VSIM_SHARDED=1 CI_NODE_INDEX=2 CI_NODE_TOTAL=2 ../scripts/run_vsim.sh axi_xbar # Pass `--list` to print the enumerated configs with their indices instead of simulating. CONFIG_IDX=0 NUM_EXECUTED=0 -NODE_INDEX=${CI_NODE_INDEX:-1} -NODE_TOTAL=${CI_NODE_TOTAL:-1} +SHARD_INDEX=1 +SHARD_TOTAL=1 +if [[ -n ${VSIM_SHARDED:-} ]]; then + SHARD_INDEX=${CI_NODE_INDEX:-1} + SHARD_TOTAL=${CI_NODE_TOTAL:-1} +fi LIST_ONLY=0 call_vsim() { local seed log for seed in "${SEEDS[@]}"; do CONFIG_IDX=$((CONFIG_IDX + 1)) - # Round-robin sharding: skip configs that belong to another CI node. - if (( (CONFIG_IDX - 1) % NODE_TOTAL != NODE_INDEX - 1 )); then + if (( (CONFIG_IDX - 1) % SHARD_TOTAL != SHARD_INDEX - 1 )); then continue fi if (( LIST_ONLY )); then @@ -307,9 +311,9 @@ for t in "${tests[@]}"; do exec_test $t done -# A shard that matches no config at all (e.g. `parallel: N` larger than the number of configs of -# a test) must fail loudly instead of passing as a vacuously green CI job. +# A shard that matches no config at all (e.g. more shards than a test has configs) must fail +# loudly instead of passing as a vacuously green CI job. if (( ! LIST_ONLY && NUM_EXECUTED == 0 )); then - echo "Error: no simulations executed on this shard (node $NODE_INDEX of $NODE_TOTAL)." >&2 + echo "Error: no simulations executed on this shard (shard $SHARD_INDEX of $SHARD_TOTAL)." >&2 exit 1 fi From 8d83db76942cc487c89b651db1896ed07df91b7e Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Thu, 27 Aug 2026 13:14:18 +0200 Subject: [PATCH 10/11] ci: shard the axi_xbar sweep four ways, complete the trigger lists Measured on pipeline 6db402ae: dropping the dead coverage flags cut most test jobs by 30-70%, but axi_xbar barely benefits (132 min total, was 148) and its two shards (69/63 min) are now the longest jobs by far. Four shards of ~33 min each match the heaviest remaining job and the runner's four concurrent slots. Also complete the hand-maintained per-job trigger lists with the transitive dependencies they were missing, audited against the actual instantiation tree of every testbench. The recurring gaps: axi_err_slv internally instantiates axi_atop_filter, axi_demux_simple instantiates axi_demux_id_counters, axi_multicut instantiates axi_cut, axi_mux instantiates axi_id_prepend, axi_dw_converter instantiates both sizers, and axi_burst_splitter pulls in a whole chain via its _gran stage. --- .gitlab-ci.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c6ec99f3..0bd50735d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -145,10 +145,13 @@ axi_dw_downsizer: - changes: compare_to: 'refs/heads/master' paths: + - src/axi_atop_filter.sv - src/axi_err_slv.sv - src/axi_demux.sv - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv - src/axi_dw_converter.sv + - src/axi_dw_upsizer.sv axi_dw_upsizer: extends: .run_vsim @@ -160,10 +163,13 @@ axi_dw_upsizer: - changes: compare_to: 'refs/heads/master' paths: + - src/axi_atop_filter.sv - src/axi_err_slv.sv - src/axi_demux.sv - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv - src/axi_dw_converter.sv + - src/axi_dw_downsizer.sv axi_isolate: extends: .run_vsim @@ -175,8 +181,10 @@ axi_isolate: - changes: compare_to: 'refs/heads/master' paths: + - src/axi_atop_filter.sv - src/axi_demux.sv - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv - src/axi_err_slv.sv axi_iw_converter: @@ -193,6 +201,7 @@ axi_iw_converter: - src/axi_id_remap.sv - src/axi_demux.sv - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv - src/axi_serializer.sv - src/axi_mux.sv - src/axi_id_serialize.sv @@ -207,6 +216,7 @@ axi_lite_xbar: - changes: compare_to: 'refs/heads/master' paths: + - src/axi_atop_filter.sv - src/axi_err_slv.sv - src/axi_lite_demux.sv - src/axi_lite_mux.sv @@ -224,6 +234,12 @@ axi_to_axi_lite: paths: - src/axi_atop_filter.sv - src/axi_burst_splitter.sv + - src/axi_burst_splitter_gran.sv + - src/axi_cut.sv + - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv + - src/axi_err_slv.sv + - src/axi_multicut.sv axi_to_mem_banked: extends: .run_vsim @@ -237,6 +253,7 @@ axi_to_mem_banked: paths: - src/axi_demux.sv - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv - src/axi_to_detailed_mem.sv - src/axi_to_mem.sv @@ -247,7 +264,7 @@ axi_to_mem_banked: # also sets on every `parallel:matrix` job (as the matrix position). axi_xbar: extends: .run_vsim - parallel: 2 + parallel: 4 variables: TEST_MODULE: axi_xbar VSIM_SHARDED: '1' @@ -259,9 +276,13 @@ axi_xbar: - src/axi_xbar.sv - test/tb_axi_xbar.sv - test/tb_axi_xbar_pkg.sv + - src/axi_atop_filter.sv + - src/axi_cut.sv - src/axi_demux.sv - src/axi_demux_simple.sv + - src/axi_demux_id_counters.sv - src/axi_err_slv.sv + - src/axi_id_prepend.sv - src/axi_mux.sv - src/axi_multicut.sv - src/axi_xbar_unmuxed.sv From 5eabb2895a3ac2b6f0808e0196b9b3f50d8404c3 Mon Sep 17 00:00:00 2001 From: Chen Wu Date: Thu, 27 Aug 2026 15:28:31 +0200 Subject: [PATCH 11/11] ci: restore the generic module-change rule for axi_xbar The rule was folded into an explicit path list when axi_xbar2 shared it (the $TEST_MODULE-derived paths would not exist for the pseudo-test). With axi_xbar2 gone, use the same anchor as every other job again; the extra rule keeps only the testbench package and the submodules. --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0bd50735d..3581418c7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -270,11 +270,10 @@ axi_xbar: VSIM_SHARDED: '1' rules: - *run_vsim_common_change_rule + - *run_vsim_module_change_rule - changes: compare_to: 'refs/heads/master' paths: - - src/axi_xbar.sv - - test/tb_axi_xbar.sv - test/tb_axi_xbar_pkg.sv - src/axi_atop_filter.sv - src/axi_cut.sv