File size: 7,291 Bytes
0ba037b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# 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