From 349087a8c0c2cc9a78a8901f25b830ad554acaae Mon Sep 17 00:00:00 2001 From: Jevin Sweval Date: Sat, 29 Jan 2022 13:27:14 -0800 Subject: [PATCH] Add JTAGbone support for Altera Max 10 and Cyclone 10 LP --- litex/build/altera/platform.py | 18 +++- litex/build/altera/quartus.py | 12 +++ litex/build/openocd.py | 6 +- litex/gen/fhdl/fsm.py | 37 ++++++++ litex/soc/cores/jtag.py | 168 ++++++++++++++++++++++++++++++++- litex/soc/integration/soc.py | 4 +- 6 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 litex/gen/fhdl/fsm.py diff --git a/litex/build/altera/platform.py b/litex/build/altera/platform.py index ce6821797..c7f03e228 100644 --- a/litex/build/altera/platform.py +++ b/litex/build/altera/platform.py @@ -7,7 +7,7 @@ import os -from litex.build.generic_platform import GenericPlatform +from litex.build.generic_platform import GenericPlatform, Pins from litex.build.altera import common, quartus # AlteraPlatform ----------------------------------------------------------------------------------- @@ -51,3 +51,19 @@ class AlteraPlatform(GenericPlatform): if hasattr(to, "p"): to = to.p self.toolchain.add_false_path_constraint(self, from_, to) + + def add_reserved_jtag_decls(self): + self.add_extension([ + ("altera_reserved_tms", 0, Pins("altera_reserved_tms")), + ("altera_reserved_tck", 0, Pins("altera_reserved_tck")), + ("altera_reserved_tdi", 0, Pins("altera_reserved_tdi")), + ("altera_reserved_tdo", 0, Pins("altera_reserved_tdo")), + ]) + + def get_reserved_jtag_pads(self): + return { + "altera_reserved_tms": self.request("altera_reserved_tms"), + "altera_reserved_tck": self.request("altera_reserved_tck"), + "altera_reserved_tdi": self.request("altera_reserved_tdi"), + "altera_reserved_tdo": self.request("altera_reserved_tdo"), + } diff --git a/litex/build/altera/quartus.py b/litex/build/altera/quartus.py index 6f03cfc69..4d5988312 100644 --- a/litex/build/altera/quartus.py +++ b/litex/build/altera/quartus.py @@ -46,13 +46,25 @@ def _format_qsf_constraint(signame, pin, others, resname): fmt_c = [_format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)] return '\n'.join(fmt_c) +def _is_virtual_pin(pin_name): + return pin_name in ( + "altera_reserved_tms", + "altera_reserved_tck", + "altera_reserved_tdi", + "altera_reserved_tdo", + ) + def _build_qsf_constraints(named_sc, named_pc): qsf = [] for sig, pins, others, resname in named_sc: if len(pins) > 1: for i, p in enumerate(pins): + if _is_virtual_pin(p): + continue qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname)) else: + if _is_virtual_pin(pins[0]): + continue qsf.append(_format_qsf_constraint(sig, pins[0], others, resname)) if named_pc: qsf.append("\n\n".join(named_pc)) diff --git a/litex/build/openocd.py b/litex/build/openocd.py index d4e15279f..87354a4ee 100644 --- a/litex/build/openocd.py +++ b/litex/build/openocd.py @@ -41,9 +41,13 @@ class OpenOCD(GenericProgrammer): def get_ir(self, chain, config): # On ECP5, force IR to 0x32. - ecp5 = "ecp5" in open(config).read() + cfg_str = open(config).read() + ecp5 = "ecp5" in cfg_str + altera = "10m50" in cfg_str # TODO: or cyclone 10 if ecp5: chain = 0x32 + elif altera: + chain = 0xC # Else IR = 1 + CHAIN. else: chain = 0x1 + chain diff --git a/litex/gen/fhdl/fsm.py b/litex/gen/fhdl/fsm.py new file mode 100644 index 000000000..2c849a8ce --- /dev/null +++ b/litex/gen/fhdl/fsm.py @@ -0,0 +1,37 @@ +# +# This file is part of LiteX. +# +# Copyright (c) 2021 Jevin Sweval +# SPDX-License-Identifier: BSD-2-Clause + +from collections import OrderedDict + +from migen.genlib.fsm import FSM + + + +class CorrectedOngoingResetFSM(FSM): + """ + This wrapper is needed for FSMs where an ongoing signal from the FSM's reset state is used. + + With the existing FSM, on SoC reset the FSM will be in the reset state + but its ongoing signal will not be asserted because the existing FSM + does not set the reset values of the ongoing signals. + """ + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.ongoing_signals = OrderedDict() + + def ongoing(self, state, *args, **kwargs): + is_ongoing = super().ongoing(state, *args, **kwargs) + self.ongoing_signals[state] = is_ongoing + return is_ongoing + + def do_finalize(self, *args, **kwargs): + for state, is_ongoing in self.ongoing_signals.items(): + is_ongoing.reset = 1 if state == self.reset_state else 0 + if is_ongoing.reset.value: + # since the default is high, must explicitly deassert in all other states + for other_state in set(self.actions) - set([state]): + self.actions[other_state].append(is_ongoing.eq(0)) + super().do_finalize(*args, **kwargs) diff --git a/litex/soc/cores/jtag.py b/litex/soc/cores/jtag.py index 03fa6b5f7..9e0627618 100644 --- a/litex/soc/cores/jtag.py +++ b/litex/soc/cores/jtag.py @@ -6,13 +6,171 @@ # Copyright (c) 2017 Robert Jordens # Copyright (c) 2021 Gregory Davill # Copyright (c) 2021 Gabriel L. Somlo +# Copyright (c) 2021 Jevin Sweval # SPDX-License-Identifier: BSD-2-Clause from migen import * from migen.genlib.cdc import AsyncResetSynchronizer, MultiReg +from litex.gen.fhdl.fsm import CorrectedOngoingResetFSM from litex.soc.interconnect import stream +# JTAG TAP FSM ------------------------------------------------------------------------------------- + +class JTAGTAPFSM(Module): + def __init__(self, tms: Signal, tck: Signal, expose_signals=True): + self.submodules.fsm = fsm = ClockDomainsRenamer("jtag")(CorrectedOngoingResetFSM()) + + fsm.act("test_logic_reset", + If(~tms, NextState("run_test_idle")) + ) + fsm.act("run_test_idle", + If( tms, NextState("select_dr_scan")) + ) + + # DR + fsm.act("select_dr_scan", + If(~tms, NextState("capture_dr") ).Else(NextState("select_ir_scan")) + ) + fsm.act("capture_dr", + If(~tms, NextState("shift_dr") ).Else(NextState("exit1_dr")) + ) + fsm.act("shift_dr", + If( tms, NextState("exit1_dr")) + ) + fsm.act("exit1_dr", + If(~tms, NextState("pause_dr") ).Else(NextState("update_dr")) + ) + fsm.act("pause_dr", + If( tms, NextState("exit2_dr")) + ) + fsm.act("exit2_dr", + If( tms, NextState("update_dr") ).Else(NextState("shift_dr")) + ) + fsm.act("update_dr", + If( tms, NextState("select_dr_scan")).Else(NextState("run_test_idle")) + ) + + # IR + fsm.act("select_ir_scan", + If(~tms, NextState("capture_ir") ).Else(NextState("test_logic_reset")) + ) + fsm.act("capture_ir", + If(~tms, NextState("shift_ir") ).Else(NextState("exit1_ir")) + ) + fsm.act("shift_ir", + If( tms, NextState("exit1_ir")) + ) + fsm.act("exit1_ir", + If(~tms, NextState("pause_ir") ).Else(NextState("update_ir")) + ) + fsm.act("pause_ir", + If( tms, NextState("exit2_ir")) + ) + fsm.act("exit2_ir", + If( tms, NextState("update_ir") ).Else(NextState("shift_ir")) + ) + fsm.act("update_ir", + If( tms, NextState("select_dr_scan")).Else(NextState("run_test_idle")) + ) + + if expose_signals: + for state_name in fsm.actions: + state_sig = fsm.ongoing(state_name) + SHOUTING_NAME = state_name.upper() + shouting_sig = Signal(name=SHOUTING_NAME) + setattr(self, SHOUTING_NAME, shouting_sig) + self.comb += shouting_sig.eq(state_sig) + + +# Altera JTAG -------------------------------------------------------------------------------------- + +class AlteraJTAG(Module): + def __init__(self, primitive, reserved_pads): + # Common with Xilinx + self.reset = reset = Signal() # provided by our own TAP FSM + self.capture = capture = Signal() # provided by our own TAP FSM + self.shift = shift = Signal() + self.update = update = Signal() + # Unique to Altera + self.runtest = runtest = Signal() + self.drck = drck = Signal() + self.sel = sel = Signal() + + self.tck = tck = Signal() + self.tms = tms = Signal() + self.tdi = tdi = Signal() + self.tdo = tdo = Signal() + + # magic reserved signals that have to be routed to the top module + self.altera_reserved_tck = rtck = Signal() + self.altera_reserved_tms = rtms = Signal() + self.altera_reserved_tdi = rtdi = Signal() + self.altera_reserved_tdo = rtdo = Signal() + + # inputs + self.tdouser = tdouser = Signal() + + # outputs + self.tmsutap = tmsutap = Signal() + self.tckutap = tckutap = Signal() + self.tdiutap = tdiutap = Signal() + + # # # + + # create falling-edge JTAG clock domain for TAP FSM + self.clock_domains.cd_jtag_inv = cd_jtag_inv = ClockDomain("jtag_inv") + self.comb += ClockSignal("jtag_inv").eq(~ClockSignal("jtag")) + self.comb += ResetSignal("jtag_inv").eq(ResetSignal("jtag")) + + # connect the TAP state signals that LiteX expects but the HW IP doesn't provide + self.submodules.tap_fsm = JTAGTAPFSM(tms, tck) + self.sync.jtag_inv += reset.eq(self.tap_fsm.TEST_LOGIC_RESET) + self.sync.jtag_inv += capture.eq(self.tap_fsm.CAPTURE_DR) + + self.specials += Instance(primitive, + # HW TAP FSM states + o_shiftuser = shift, + o_updateuser = update, + o_runidleuser = runtest, + o_clkdruser = drck, + o_usr1user = sel, + # JTAG TAP IO + i_tdouser = tdouser, + o_tmsutap = tmsutap, + o_tckutap = tckutap, + o_tdiutap = tdiutap, + # reserved pins + i_tms = rtms, + i_tck = rtck, + i_tdi = rtdi, + o_tdo = rtdo, + ) + + # connect magical reserved signals to top level pads + self.comb += [ + rtms.eq(reserved_pads["altera_reserved_tms"]), + rtck.eq(reserved_pads["altera_reserved_tck"]), + rtdi.eq(reserved_pads["altera_reserved_tdi"]), + reserved_pads["altera_reserved_tdo"].eq(rtdo), + ] + + # connect TAP IO + self.comb += [ + tck.eq(tckutap), + tms.eq(tmsutap), + tdi.eq(tdiutap), + ] + self.sync.jtag_inv += tdouser.eq(tdo) + +class MAX10JTAG(AlteraJTAG): + def __init__(self, reserved_pads, *args, **kwargs): + AlteraJTAG.__init__(self, "fiftyfivenm_jtag", reserved_pads, *args, **kwargs) + +class Cyclone10LPJTAG(AlteraJTAG): + def __init__(self, reserved_pads, *args, **kwargs): + AlteraJTAG.__init__(self, "cyclone10lp_jtag", reserved_pads, *args, **kwargs) + # Altera Atlantic JTAG ----------------------------------------------------------------------------- class JTAGAtlantic(Module): @@ -141,7 +299,7 @@ class ECP5JTAG(Module): # JTAG PHY ----------------------------------------------------------------------------------------- class JTAGPHY(Module): - def __init__(self, jtag=None, device=None, data_width=8, clock_domain="sys", chain=1): + def __init__(self, jtag=None, device=None, data_width=8, clock_domain="sys", chain=1, platform=None): """JTAG PHY Provides a simple JTAG to LiteX stream module to easily stream data to/from the FPGA @@ -178,6 +336,14 @@ class JTAGPHY(Module): jtag = USJTAG(chain=chain) elif device[:5] == "LFE5U": jtag = ECP5JTAG() + elif device[:3].lower() in ["10m"]: + assert platform is not None + platform.add_reserved_jtag_decls() + jtag = MAX10JTAG(reserved_pads=platform.get_reserved_jtag_pads()) + elif device[:4].lower() in ["10cl"]: + assert platform is not None + platform.add_reserved_jtag_decls() + jtag = Cyclone10LPJTAG(reserved_pads=platform.get_reserved_jtag_pads()) else: print(device) raise NotImplementedError diff --git a/litex/soc/integration/soc.py b/litex/soc/integration/soc.py index 0b73ddf66..2a75539a2 100644 --- a/litex/soc/integration/soc.py +++ b/litex/soc/integration/soc.py @@ -1211,7 +1211,7 @@ class LiteXSoC(SoC): # Run JTAG-UART in sys_jtag clk domain similar to sys clk domain but without sys_rst. self.clock_domains.cd_sys_jtag = ClockDomain() self.comb += self.cd_sys_jtag.clk.eq(ClockSignal("sys")) - uart_phy = JTAGPHY(device=self.platform.device, clock_domain="sys_jtag") + uart_phy = JTAGPHY(device=self.platform.device, clock_domain="sys_jtag", platform=self.platform) uart = UART(uart_phy, **uart_kwargs) # Sim. @@ -1279,7 +1279,7 @@ class LiteXSoC(SoC): # Core. self.check_if_exists("jtagbone") - self.submodules.jtagbone_phy = JTAGPHY(device=self.platform.device, chain=chain) + self.submodules.jtagbone_phy = JTAGPHY(device=self.platform.device, chain=chain, platform=self.platform) self.submodules.jtagbone = uart.UARTBone(phy=self.jtagbone_phy, clk_freq=self.sys_clk_freq) self.bus.add_master(name="jtagbone", master=self.jtagbone.wishbone)