Add JTAGbone support for Altera Max 10 and Cyclone 10 LP

This commit is contained in:
Jevin Sweval 2022-01-29 13:27:14 -08:00
parent bfad61cd2a
commit 349087a8c0
6 changed files with 240 additions and 5 deletions

View File

@ -7,7 +7,7 @@
import os import os
from litex.build.generic_platform import GenericPlatform from litex.build.generic_platform import GenericPlatform, Pins
from litex.build.altera import common, quartus from litex.build.altera import common, quartus
# AlteraPlatform ----------------------------------------------------------------------------------- # AlteraPlatform -----------------------------------------------------------------------------------
@ -51,3 +51,19 @@ class AlteraPlatform(GenericPlatform):
if hasattr(to, "p"): if hasattr(to, "p"):
to = to.p to = to.p
self.toolchain.add_false_path_constraint(self, from_, to) 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"),
}

View File

@ -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)] fmt_c = [_format_constraint(c, signame, fmt_r) for c in ([Pins(pin)] + others)]
return '\n'.join(fmt_c) 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): def _build_qsf_constraints(named_sc, named_pc):
qsf = [] qsf = []
for sig, pins, others, resname in named_sc: for sig, pins, others, resname in named_sc:
if len(pins) > 1: if len(pins) > 1:
for i, p in enumerate(pins): for i, p in enumerate(pins):
if _is_virtual_pin(p):
continue
qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname)) qsf.append(_format_qsf_constraint("{}[{}]".format(sig, i), p, others, resname))
else: else:
if _is_virtual_pin(pins[0]):
continue
qsf.append(_format_qsf_constraint(sig, pins[0], others, resname)) qsf.append(_format_qsf_constraint(sig, pins[0], others, resname))
if named_pc: if named_pc:
qsf.append("\n\n".join(named_pc)) qsf.append("\n\n".join(named_pc))

View File

@ -41,9 +41,13 @@ class OpenOCD(GenericProgrammer):
def get_ir(self, chain, config): def get_ir(self, chain, config):
# On ECP5, force IR to 0x32. # 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: if ecp5:
chain = 0x32 chain = 0x32
elif altera:
chain = 0xC
# Else IR = 1 + CHAIN. # Else IR = 1 + CHAIN.
else: else:
chain = 0x1 + chain chain = 0x1 + chain

37
litex/gen/fhdl/fsm.py Normal file
View File

@ -0,0 +1,37 @@
#
# This file is part of LiteX.
#
# Copyright (c) 2021 Jevin Sweval <jevinsweval@gmail.com>
# 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)

View File

@ -6,13 +6,171 @@
# Copyright (c) 2017 Robert Jordens <jordens@gmail.com> # Copyright (c) 2017 Robert Jordens <jordens@gmail.com>
# Copyright (c) 2021 Gregory Davill <greg.davill@gmail.com> # Copyright (c) 2021 Gregory Davill <greg.davill@gmail.com>
# Copyright (c) 2021 Gabriel L. Somlo <somlo@cmu.edu> # Copyright (c) 2021 Gabriel L. Somlo <somlo@cmu.edu>
# Copyright (c) 2021 Jevin Sweval <jevinsweval@gmail.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
from migen import * from migen import *
from migen.genlib.cdc import AsyncResetSynchronizer, MultiReg from migen.genlib.cdc import AsyncResetSynchronizer, MultiReg
from litex.gen.fhdl.fsm import CorrectedOngoingResetFSM
from litex.soc.interconnect import stream 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 ----------------------------------------------------------------------------- # Altera Atlantic JTAG -----------------------------------------------------------------------------
class JTAGAtlantic(Module): class JTAGAtlantic(Module):
@ -141,7 +299,7 @@ class ECP5JTAG(Module):
# JTAG PHY ----------------------------------------------------------------------------------------- # JTAG PHY -----------------------------------------------------------------------------------------
class JTAGPHY(Module): 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 """JTAG PHY
Provides a simple JTAG to LiteX stream module to easily stream data to/from the FPGA 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) jtag = USJTAG(chain=chain)
elif device[:5] == "LFE5U": elif device[:5] == "LFE5U":
jtag = ECP5JTAG() 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: else:
print(device) print(device)
raise NotImplementedError raise NotImplementedError

View File

@ -1211,7 +1211,7 @@ class LiteXSoC(SoC):
# Run JTAG-UART in sys_jtag clk domain similar to sys clk domain but without sys_rst. # 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.clock_domains.cd_sys_jtag = ClockDomain()
self.comb += self.cd_sys_jtag.clk.eq(ClockSignal("sys")) 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) uart = UART(uart_phy, **uart_kwargs)
# Sim. # Sim.
@ -1279,7 +1279,7 @@ class LiteXSoC(SoC):
# Core. # Core.
self.check_if_exists("jtagbone") 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.submodules.jtagbone = uart.UARTBone(phy=self.jtagbone_phy, clk_freq=self.sys_clk_freq)
self.bus.add_master(name="jtagbone", master=self.jtagbone.wishbone) self.bus.add_master(name="jtagbone", master=self.jtagbone.wishbone)