From f850322ae9be14e023bdbe9820cf379cf68bb39f Mon Sep 17 00:00:00 2001 From: Saket Sinha Date: Sat, 29 Aug 2026 22:08:43 +0200 Subject: [PATCH] soc_linux: drive the vexriscv_smp tunneled debug port from BSCANE2 With --with-privileged-debug and without --jtag-tap, the RISC-V DTM is tunneled: debugPort_* carries a JTAG instruction/data path and expects a vendor boundary-scan primitive to drive it. Instantiate BSCANE2 and bind TCK/SEL/CAPTURE/SHIFT/UPDATE/RESET/TDI plus TDO back to the CPU, so the tunneled DTM reaches the FPGA's own JTAG chain and is usable over the board's existing USB-JTAG cable. JTAG_CHAIN=4 selects USER4, whose IR is 0x23d. USER1 is deliberately left free for jtagbone, which LiteX puts on chain 1, so bus access and CPU debug can share one cable. Verified on a Digilent Arty A7-35T over the on-board FT2232, with ./make.py --board=arty --cpu-count=1 --with-privileged-debug: Info : JTAG tap: xc7.tap tap/device found: 0x0362d093 Info : Examined RISC-V core; found 1 harts Info : hart 0: XLEN=32, misa=0x40141101 halt/resume with advancing pc, memory read of the reset vector, and GDB attach with symbols and a multi-frame backtrace on bios.elf. --- soc_linux.py | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/soc_linux.py b/soc_linux.py index b0780e2f..0cd13b39 100644 --- a/soc_linux.py +++ b/soc_linux.py @@ -18,6 +18,7 @@ from litex.soc.cores.spi import SPIMaster from litex.soc.cores.bitbang import I2CMaster from litex.soc.cores.pwm import PWM +from litex.soc.cores.jtag import XilinxJTAG from litex.tools.litex_json2dts_linux import generate_dts @@ -36,6 +37,62 @@ def __init__(self, **kwargs): soc_cls.__init__(self, cpu_type="vexriscv_smp", cpu_variant="linux", **kwargs) + self.add_cpu_bscan_debug() + + # CPU Debug (tunneled DTM over vendor BSCAN) -------------------------------------------- + + def add_cpu_bscan_debug(self): + """Bind the VexRiscv-SMP tunneled debug port to the FPGA's own JTAG chain. + + With --with-privileged-debug and without --jtag-tap the RISC-V DTM is *tunneled*: + its debugPort_* signals carry a JTAG instruction/data path and expect a vendor + boundary-scan primitive to drive them. + + On Xilinx that primitive is BSCANE2. JTAG_CHAIN=4 selects USER4, whose IR is 0x23. + The stock tunnel TCL works unmodified. USER1 is deliberately left free for jtagbone. + """ + if not hasattr(self.cpu, "jtag_enable"): + return + + primitive = XilinxJTAG.get_primitive(self.platform.device) + if primitive is None: + return + + tck = Signal() + tdi = Signal() + sel = Signal() + capture = Signal() + shift = Signal() + update = Signal() + reset = Signal() + + self.specials += Instance(primitive, + p_JTAG_CHAIN = 4, # USER4 -> IR 0x23 + + o_TCK = tck, + o_TDI = tdi, + o_SEL = sel, + o_CAPTURE = capture, + o_SHIFT = shift, + o_UPDATE = update, + o_RESET = reset, + i_TDO = self.cpu.jtag_tdo, + ) + + self.comb += [ + self.cpu.jtag_clk.eq(tck), + self.cpu.jtag_tdi.eq(tdi), + self.cpu.jtag_enable.eq(sel), + self.cpu.jtag_capture.eq(capture), + self.cpu.jtag_shift.eq(shift), + self.cpu.jtag_update.eq(update), + self.cpu.jtag_reset.eq(reset), + ] + + # TCK arrives on a dedicated BSCAN route. + self.platform.add_period_constraint(tck, 1e9/10e6) + self.platform.add_false_path_constraints(self.crg.cd_sys.clk, tck) + # RGB Led ---------------------------------------------------------------------------------- def add_rgb_led(self):