Add JTAGbone support for Altera Max 10 and Cyclone 10 LP
This commit is contained in:
parent
bfad61cd2a
commit
349087a8c0
|
@ -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"),
|
||||||
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue