SNAPKITTYWEST's picture
October 2026 main drop: mirror from GitHub
0ba037b verified
Raw History Blame Contribute Delete
7.29 kB
# PCIe accelerator: parallel engine, PCIe-facing core, LiteX SoC for the
# LiteFury, and the host program.
#
# make lint verilator -Wall on the engine and the PCIe core
# make sim engine vs the single-op reference core: several
# geometries and seeds (Icarus); core with 128-bit streams.
# The runs are independent targets: use make -j.
# make sim-verilator the engine testbench under Verilator
# make soc generate the LiteX SoC sources and Linux driver (no Vivado)
# make cosim host program (host/bitacc_pcie.c) against the Verilated core
# through a liblitepcie stand-in (needs `make soc` first)
# make perf engine throughput in clock cycles at the LiteFury geometry
# (8 lanes x 2048 words): host bench against the Verilated core
# make timing open-source place and route for the XC7A100T (nextpnr-xilinx;
# set NEXTPNR and CHIPDB, see timing/run.sh): Fmax estimate
# make bitstream full build with Vivado (not available in CI)
#
# The engine testbench fails unless results and final memory equal the
# reference, and unless out-of-order completion, a full reorder buffer, both
# directions of the range barrier and every operation class (single-bit and
# range; in range, out of range, wrapping, undefined) occurred.
#
# A testbench passes only if vvp exits 0 and prints PASS. (Piping vvp into
# grep would report the grep's status, so a failing run would pass.)
IVERILOG ?= iverilog
VVP ?= vvp
VERILATOR ?= verilator
PYTHON ?= python3
BUILD ?= build
OPS ?= 4000
REF := ../rtl/bit_accelerator.sv
ENGINE := rtl/bitacc_engine.sv
CORE := rtl/bitacc_pcie_core.sv
SOC_DIR := $(abspath $(BUILD))/litefury
DRIVER := $(SOC_DIR)/driver
GEOMETRIES := 2x4 4x16 8x16 8x512
SEEDS := 1 7
TB_INC := tb/gold_ops.svh tb/range_stim.svh
# plus 16 lanes, a build without the MATCH unit (opcode 15 must be an error), and
# matchers with fewer units than lanes (run-ml-<lanes>x<words>-<units>)
ENGINE_RUNS := $(foreach g,$(GEOMETRIES),$(foreach s,$(SEEDS),run-$(g)-$(s))) run-16x8-1
# $(call vvp_check,command,log): run, show the summary, fail unless exit 0 and PASS.
define vvp_check
$(1) > $(2) 2>&1; rc=$$?; grep -E '^(stimulus|range coverage|match coverage|checks|PASS|FAIL)' $(2); \
test $$rc -eq 0 && grep -q '^PASS' $(2)
endef
.PHONY: all lint sim sim-engine sim-core sim-verilator soc cosim perf timing bitstream clean $(ENGINE_RUNS) run-nomatch $(ML_RUNS)
.DEFAULT_GOAL := all
all: lint sim sim-verilator
lint:
$(VERILATOR) --lint-only -Wall $(ENGINE)
$(VERILATOR) --lint-only -Wall $(ENGINE) $(CORE) --top-module bitacc_pcie_core
sim: sim-engine sim-core
ML_RUNS := run-ml-4x16-1 run-ml-8x16-2
sim-engine: $(ENGINE_RUNS) run-nomatch $(ML_RUNS)
$(ML_RUNS): run-ml-%: $(BUILD)/tb_ml_%.vvp
@echo "engine $* (lanes x words - MATCH units)"
@$(call vvp_check,$(VVP) -n $< +seed=3 +ops=$(OPS),$(BUILD)/run-ml-$*.log)
$(BUILD)/tb_ml_%.vvp: $(REF) $(ENGINE) tb/tb_engine.sv $(TB_INC)
@mkdir -p $(BUILD)
c=$*; g=$${c%-*}; m=$${c#*-}; \
$(IVERILOG) -g2012 -Itb -DLANES=$${g%x*} -DWPL=$${g#*x} -DMLANES=$$m -o $@ $(REF) $(ENGINE) tb/tb_engine.sv
# run-<lanes>x<words per lane>-<seed>
.SECONDEXPANSION:
$(ENGINE_RUNS): run-%: $(BUILD)/tb_$$(word 1,$$(subst -, ,$$*)).vvp
@echo "engine $*"
@$(call vvp_check,$(VVP) -n $< +seed=$(word 2,$(subst -, ,$*)) +ops=$(OPS),$(BUILD)/run-$*.log)
run-nomatch: $(BUILD)/tb_nomatch.vvp
@echo "engine 4x16 without MATCH"
@$(call vvp_check,$(VVP) -n $< +seed=1 +ops=$(OPS),$(BUILD)/run-nomatch.log)
$(BUILD)/tb_nomatch.vvp: $(REF) $(ENGINE) tb/tb_engine.sv $(TB_INC)
@mkdir -p $(BUILD)
$(IVERILOG) -g2012 -Itb -DLANES=4 -DWPL=16 -DMATCH=0 -o $@ $(REF) $(ENGINE) tb/tb_engine.sv
$(BUILD)/tb_%.vvp: $(REF) $(ENGINE) tb/tb_engine.sv $(TB_INC)
@mkdir -p $(BUILD)
g=$*; $(IVERILOG) -g2012 -Itb -DLANES=$${g%x*} -DWPL=$${g#*x} -o $@ $(REF) $(ENGINE) tb/tb_engine.sv
sim-core:
@mkdir -p $(BUILD)
$(IVERILOG) -g2012 -Itb -o $(BUILD)/tb_core.vvp $(REF) $(ENGINE) $(CORE) tb/tb_pcie_core.sv
@$(call vvp_check,$(VVP) -n $(BUILD)/tb_core.vvp,$(BUILD)/core.log)
# --output-split-cfuncs 0: Verilator 5.020 splits the testbench's main initial
# block (a coroutine with a fork) across C++ functions and drops the code that
# waits for the join; the binary then crashes (illegal instruction).
sim-verilator:
@mkdir -p $(BUILD)
$(VERILATOR) --binary --timing --timescale 1ns/1ps -Wno-fatal -Wno-lint -Wno-style -Itb --output-split-cfuncs 0 \
--top-module tb_engine -Mdir $(BUILD)/verilator $(REF) $(ENGINE) tb/tb_engine.sv >/dev/null
@$(call vvp_check,$(BUILD)/verilator/Vtb_engine +ops=$(OPS),$(BUILD)/verilator.log)
soc:
cd litex && $(PYTHON) bitacc_litefury.py --driver --output-dir $(SOC_DIR)
# $(call cosim_build,dir,lanes,words per lane[,MATCH units]): host program + Verilated core + mock.
define cosim_build
@test -d $(DRIVER) || { echo "run 'make soc' first"; exit 1; }
@mkdir -p $(1)
cc -O2 -Wall -Wextra -Werror -I$(DRIVER)/user/liblitepcie -I$(DRIVER)/kernel \
-c -o $(abspath $(1))/bitacc_pcie.o host/bitacc_pcie.c
$(VERILATOR) --cc --exe --build -O2 --timescale 1ns/1ps -Wno-fatal -Wno-lint \
-GLANES=$(2) -GWORDS_PER_LANE=$(3) $(if $(4),-GMATCH_LANES=$(4)) --top-module bitacc_pcie_core -Mdir $(1)/obj \
-CFLAGS "-I$(DRIVER)/user/liblitepcie -I$(DRIVER)/kernel -DMOCK_LANES=$(2) -DMOCK_WPL=$(3) $(if $(4),-DMOCK_MATCH_LANES=$(4))" \
-LDFLAGS "$(abspath $(1))/bitacc_pcie.o" \
$(ENGINE) $(CORE) $(abspath host/sim/mock_litepcie.cpp) -o bitacc_pcie_cosim >/dev/null
endef
# Small geometry keeps the co-simulation fast; the host reads it from CSRs.
# Arguments: operations, seed, percentage of range operations.
cosim:
$(call cosim_build,$(BUILD)/cosim,4,64)
$(BUILD)/cosim/obj/bitacc_pcie_cosim selftest 200000 1 5
$(BUILD)/cosim/obj/bitacc_pcie_cosim selftest 50000 9 50
# The LiteFury configuration with MATCH: 8 lanes x 2048 words, 2 MATCH units.
# Fails if a bench needs more cycles per descriptor than the design intends:
# single-bit 1 (plus 2%), COUNT/FIND over 16384 words 16384/8 steps of 1
# cycle, BULK over 8192 words 8192/8 steps of 2 cycles, MATCH over 16384
# words 16384/2 sub-steps of 1 cycle (plus 1% and a fixed allowance for
# pipeline fill and the MATCH pattern load).
perf:
$(call cosim_build,$(BUILD)/perf,8,2048,2)
$(BUILD)/perf/obj/bitacc_pcie_cosim selftest 20000 3 20
@for kl in get:1.02 count:2084 find:2084 xor:2084 match:8300; do \
k=$${kl%:*}; limit=$${kl#*:}; n=$$( [ $$k = get ] && echo 200000 || echo 2048 ); \
out=$$($(BUILD)/perf/obj/bitacc_pcie_cosim bench $$k $$n 2>&1) || { echo "$$out"; exit 1; }; \
echo "$$out" | grep -E '^(bench|mock)'; \
echo "$$out" | awk -v l=$$limit '/^mock:/ { c = $$(NF-1) } END { if (c == "" || c + 0 > l) { print "cycles/descriptor " c " above " l; exit 1 } }' || exit 1; \
done
TIMING_CONFIGS ?= 8:2048:1:2 8:2048:0
timing:
@set -e; cd timing; for c in $(TIMING_CONFIGS); do \
echo "== $$c (lanes:words per lane:MATCH)"; ./run.sh $$(echo $$c | tr ':' ' '); done
bitstream:
cd litex && $(PYTHON) bitacc_litefury.py --build --driver --output-dir $(SOC_DIR)
clean:
rm -rf $(BUILD) host/build_cosim