X-Git-Url: https://review.openocd.org/gitweb?p=openocd.git;a=blobdiff_plain;f=contrib%2Floaders%2Fflash%2Ffpga%2Fxilinx_bscan_spi.py;h=4246aa2f3af3f98fa2a6f8e9233d368492fa3d77;hp=fa4ec2ace6e22d3a69bf859464adfaab15a30811;hb=867bdb2e9248a974f7db0a99fbe5d2dd8b46d25d;hpb=12aee423db1aa1d315392dbaa60122c871054714;ds=sidebyside diff --git a/contrib/loaders/flash/fpga/xilinx_bscan_spi.py b/contrib/loaders/flash/fpga/xilinx_bscan_spi.py index fa4ec2ace6..4246aa2f3a 100755 --- a/contrib/loaders/flash/fpga/xilinx_bscan_spi.py +++ b/contrib/loaders/flash/fpga/xilinx_bscan_spi.py @@ -13,8 +13,11 @@ # GNU General Public License for more details. # -from migen import * -from migen.build.generic_platform import * +import unittest + +import migen as mg +import migen.build.generic_platform as mb +from migen.genlib import io from migen.build import xilinx @@ -25,59 +28,256 @@ behind FPGAs. Bitstream binaries built with this script are available at: https://github.com/jordens/bscan_spi_bitstreams -JTAG signalling is connected directly to SPI signalling. CS_N is -asserted when the JTAG IR contains the USER1 instruction and the state is -SHIFT-DR. Xilinx bscan cells sample TDO on falling TCK and forward it. -MISO requires sampling on rising CLK and leads to one cycle of latency. +A JTAG2SPI transfer consists of: + +1. an arbitrary number of 0 bits (from BYPASS registers in front of the + JTAG2SPI DR) +2. a marker bit (1) indicating the start of the JTAG2SPI transaction +3. 32 bits (big endian) describing the length of the SPI transaction +4. a number of SPI clock cycles (corresponding to 3.) with CS_N asserted +5. an arbitrary number of cycles (to shift MISO/TDO data through subsequent + BYPASS registers) + +Notes: + +* The JTAG2SPI DR is 1 bit long (due to different sampling edges of + {MISO,MOSI}/{TDO,TDI}). +* MOSI is TDI with half a cycle delay. +* TDO is MISO with half a cycle delay. +* CAPTURE-DR needs to be performed before SHIFT-DR on the BYPASSed TAPs in + JTAG chain to clear the BYPASS registers to 0. https://github.com/m-labs/migen """ -class Spartan3(Module): +class JTAG2SPI(mg.Module): + def __init__(self, spi=None, bits=32): + self.jtag = mg.Record([ + ("sel", 1), + ("shift", 1), + ("capture", 1), + ("tck", 1), + ("tdi", 1), + ("tdo", 1), + ]) + self.cs_n = mg.TSTriple() + self.clk = mg.TSTriple() + self.mosi = mg.TSTriple() + self.miso = mg.TSTriple() + + # # # + + self.cs_n.o.reset = mg.Constant(1) + self.mosi.o.reset_less = True + bits = mg.Signal(bits, reset_less=True) + head = mg.Signal(max=len(bits), reset=len(bits) - 1) + self.clock_domains.cd_sys = mg.ClockDomain() + self.submodules.fsm = mg.FSM("IDLE") + if spi is not None: + self.specials += [ + self.cs_n.get_tristate(spi.cs_n), + self.mosi.get_tristate(spi.mosi), + self.miso.get_tristate(spi.miso), + ] + if hasattr(spi, "clk"): # 7 Series drive it fixed + self.specials += self.clk.get_tristate(spi.clk) + # self.specials += io.DDROutput(1, 0, spi.clk, self.clk.o) + self.comb += [ + self.cd_sys.rst.eq(self.jtag.sel & self.jtag.capture), + self.cd_sys.clk.eq(self.jtag.tck), + self.cs_n.oe.eq(self.jtag.sel), + self.clk.oe.eq(self.jtag.sel), + self.mosi.oe.eq(self.jtag.sel), + self.miso.oe.eq(0), + # Do not suppress CLK toggles outside CS_N asserted. + # Xilinx USRCCLK0 requires three dummy cycles to do anything + # https://www.xilinx.com/support/answers/52626.html + # This is fine since CS_N changes only on falling CLK. + self.clk.o.eq(~self.jtag.tck), + self.jtag.tdo.eq(self.miso.i), + ] + # Latency calculation (in half cycles): + # 0 (falling TCK, rising CLK): + # JTAG adapter: set TDI + # 1 (rising TCK, falling CLK): + # JTAG2SPI: sample TDI -> set MOSI + # SPI: set MISO + # 2 (falling TCK, rising CLK): + # SPI: sample MOSI + # JTAG2SPI (BSCAN primitive): sample MISO -> set TDO + # 3 (rising TCK, falling CLK): + # JTAG adapter: sample TDO + self.fsm.act("IDLE", + mg.If(self.jtag.tdi & self.jtag.sel & self.jtag.shift, + mg.NextState("HEAD") + ) + ) + self.fsm.act("HEAD", + mg.If(head == 0, + mg.NextState("XFER") + ) + ) + self.fsm.act("XFER", + mg.If(bits == 0, + mg.NextState("IDLE") + ), + ) + self.sync += [ + self.mosi.o.eq(self.jtag.tdi), + self.cs_n.o.eq(~self.fsm.ongoing("XFER")), + mg.If(self.fsm.ongoing("HEAD"), + bits.eq(mg.Cat(self.jtag.tdi, bits)), + head.eq(head - 1) + ), + mg.If(self.fsm.ongoing("XFER"), + bits.eq(bits - 1) + ) + ] + + +class JTAG2SPITest(unittest.TestCase): + def setUp(self): + self.bits = 8 + self.dut = JTAG2SPI(bits=self.bits) + + def test_instantiate(self): + pass + + def test_initial_conditions(self): + def check(): + yield + self.assertEqual((yield self.dut.cs_n.oe), 0) + self.assertEqual((yield self.dut.mosi.oe), 0) + self.assertEqual((yield self.dut.miso.oe), 0) + self.assertEqual((yield self.dut.clk.oe), 0) + mg.run_simulation(self.dut, check()) + + def test_enable(self): + def check(): + yield self.dut.jtag.sel.eq(1) + yield self.dut.jtag.shift.eq(1) + yield + self.assertEqual((yield self.dut.cs_n.oe), 1) + self.assertEqual((yield self.dut.mosi.oe), 1) + self.assertEqual((yield self.dut.miso.oe), 0) + self.assertEqual((yield self.dut.clk.oe), 1) + mg.run_simulation(self.dut, check()) + + def run_seq(self, tdi, tdo, spi=None): + yield self.dut.jtag.sel.eq(1) + yield + yield self.dut.jtag.shift.eq(1) + for di in tdi: + yield self.dut.jtag.tdi.eq(di) + yield + tdo.append((yield self.dut.jtag.tdo)) + if spi is not None: + v = [] + for k in "cs_n clk mosi miso".split(): + t = getattr(self.dut, k) + v.append("{}>".format((yield t.o)) if (yield t.oe) + else "<{}".format((yield t.i))) + spi.append(" ".join(v)) + yield self.dut.jtag.sel.eq(0) + yield + yield self.dut.jtag.shift.eq(0) + yield + + def test_shift(self): + bits = 8 + data = 0x81 + tdi = [0, 0, 1] # dummy from BYPASS TAPs and marker + tdi += [((bits - 1) >> j) & 1 for j in range(self.bits - 1, -1, -1)] + tdi += [(data >> j) & 1 for j in range(bits)] + tdi += [0, 0, 0, 0] # dummy from BYPASS TAPs + tdo = [] + spi = [] + mg.run_simulation(self.dut, self.run_seq(tdi, tdo, spi)) + # print(tdo) + for l in spi: + print(l) + + +class Spartan3(mg.Module): macro = "BSCAN_SPARTAN3" toolchain = "ise" def __init__(self, platform): platform.toolchain.bitgen_opt += " -g compress -g UnusedPin:Pullup" - self.clock_domains.cd_jtag = ClockDomain(reset_less=True) - spi = platform.request("spiflash") - shift = Signal() - tdo = Signal() - sel1 = Signal() - self.comb += [ - self.cd_jtag.clk.eq(spi.clk), - spi.cs_n.eq(~shift | ~sel1), + self.submodules.j2s = j2s = JTAG2SPI(platform.request("spiflash")) + self.specials += [ + mg.Instance( + self.macro, + o_SHIFT=j2s.jtag.shift, o_SEL1=j2s.jtag.sel, + o_CAPTURE=j2s.jtag.capture, + o_DRCK1=j2s.jtag.tck, + o_TDI=j2s.jtag.tdi, i_TDO1=j2s.jtag.tdo, + i_TDO2=0), ] - self.sync.jtag += tdo.eq(spi.miso) - self.specials += Instance(self.macro, - o_DRCK1=spi.clk, o_SHIFT=shift, - o_TDI=spi.mosi, i_TDO1=tdo, i_TDO2=0, - o_SEL1=sel1) + platform.add_period_constraint(j2s.jtag.tck, 6) class Spartan3A(Spartan3): macro = "BSCAN_SPARTAN3A" -class Spartan6(Module): +class Spartan6(mg.Module): toolchain = "ise" def __init__(self, platform): platform.toolchain.bitgen_opt += " -g compress -g UnusedPin:Pullup" - self.clock_domains.cd_jtag = ClockDomain(reset_less=True) - spi = platform.request("spiflash") - shift = Signal() - tdo = Signal() - sel = Signal() - self.comb += self.cd_jtag.clk.eq(spi.clk), spi.cs_n.eq(~shift | ~sel) - self.sync.jtag += tdo.eq(spi.miso) - self.specials += Instance("BSCAN_SPARTAN6", p_JTAG_CHAIN=1, - o_TCK=spi.clk, o_SHIFT=shift, o_SEL=sel, - o_TDI=spi.mosi, i_TDO=tdo) - - -class Series7(Module): + self.submodules.j2s = j2s = JTAG2SPI(platform.request("spiflash")) + # clk = mg.Signal() + self.specials += [ + mg.Instance( + "BSCAN_SPARTAN6", p_JTAG_CHAIN=1, + o_SHIFT=j2s.jtag.shift, o_SEL=j2s.jtag.sel, + o_CAPTURE=j2s.jtag.capture, + o_DRCK=j2s.jtag.tck, + o_TDI=j2s.jtag.tdi, i_TDO=j2s.jtag.tdo), + # mg.Instance("BUFG", i_I=clk, o_O=j2s.jtag.tck) + ] + platform.add_period_constraint(j2s.jtag.tck, 6) + + +class Series7(mg.Module): + toolchain = "vivado" + + def __init__(self, platform): + platform.toolchain.bitstream_commands.extend([ + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", + "set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]" + ]) + self.submodules.j2s = j2s = JTAG2SPI(platform.request("spiflash")) + # clk = mg.Signal() + self.specials += [ + mg.Instance( + "BSCANE2", p_JTAG_CHAIN=1, + o_SHIFT=j2s.jtag.shift, o_SEL=j2s.jtag.sel, + o_CAPTURE=j2s.jtag.capture, + o_DRCK=j2s.jtag.tck, + o_TDI=j2s.jtag.tdi, i_TDO=j2s.jtag.tdo), + mg.Instance( + "STARTUPE2", i_CLK=0, i_GSR=0, i_GTS=0, + i_KEYCLEARB=0, i_PACK=1, + i_USRCCLKO=j2s.clk.o, i_USRCCLKTS=~j2s.clk.oe, + i_USRDONEO=1, i_USRDONETS=1), + # mg.Instance("BUFG", i_I=clk, o_O=j2s.jtag.tck) + ] + platform.add_period_constraint(j2s.jtag.tck, 6) + try: + self.comb += [ + platform.request("user_sma_gpio_p").eq(j2s.cs_n.i), + platform.request("user_sma_gpio_n").eq(j2s.clk.o), + platform.request("user_sma_clock_p").eq(j2s.mosi.o), + platform.request("user_sma_clock_n").eq(j2s.miso.i), + ] + except mb.ConstraintError: + pass + + +class Ultrascale(mg.Module): toolchain = "vivado" def __init__(self, platform): @@ -85,20 +285,33 @@ class Series7(Module): "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", "set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]", ]) - self.clock_domains.cd_jtag = ClockDomain(reset_less=True) - spi = platform.request("spiflash") - clk = Signal() - shift = Signal() - tdo = Signal() - sel = Signal() - self.comb += self.cd_jtag.clk.eq(clk), spi.cs_n.eq(~shift | ~sel) - self.sync.jtag += tdo.eq(spi.miso) - self.specials += Instance("BSCANE2", p_JTAG_CHAIN=1, - o_SHIFT=shift, o_TCK=clk, o_SEL=sel, - o_TDI=spi.mosi, i_TDO=tdo) - self.specials += Instance("STARTUPE2", i_CLK=0, i_GSR=0, i_GTS=0, - i_KEYCLEARB=0, i_PACK=1, i_USRCCLKO=clk, - i_USRCCLKTS=0, i_USRDONEO=1, i_USRDONETS=1) + self.submodules.j2s0 = j2s0 = JTAG2SPI() + self.submodules.j2s1 = j2s1 = JTAG2SPI(platform.request("spiflash")) + di = mg.Signal(4) + self.comb += mg.Cat(j2s0.mosi.i, j2s0.miso.i).eq(di) + self.specials += [ + mg.Instance("BSCANE2", p_JTAG_CHAIN=1, + o_SHIFT=j2s0.jtag.shift, o_SEL=j2s0.jtag.sel, + o_CAPTURE=j2s0.jtag.capture, + o_DRCK=j2s0.jtag.tck, + o_TDI=j2s0.jtag.tdi, i_TDO=j2s0.jtag.tdo), + mg.Instance("BSCANE2", p_JTAG_CHAIN=2, + o_SHIFT=j2s1.jtag.shift, o_SEL=j2s1.jtag.sel, + o_CAPTURE=j2s1.jtag.capture, + o_DRCK=j2s1.jtag.tck, + o_TDI=j2s1.jtag.tdi, i_TDO=j2s1.jtag.tdo), + mg.Instance("STARTUPE3", i_GSR=0, i_GTS=0, + i_KEYCLEARB=0, i_PACK=1, + i_USRDONEO=1, i_USRDONETS=1, + i_USRCCLKO=mg.Mux(j2s0.clk.oe, j2s0.clk.o, j2s1.clk.o), + i_USRCCLKTS=~(j2s0.clk.oe | j2s1.clk.oe), + i_FCSBO=j2s0.cs_n.o, i_FCSBTS=~j2s0.cs_n.oe, + o_DI=di, + i_DO=mg.Cat(j2s0.mosi.o, j2s0.miso.o, 0, 0), + i_DTS=mg.Cat(~j2s0.mosi.oe, ~j2s0.miso.oe, 1, 1)) + ] + platform.add_period_constraint(j2s0.jtag.tck, 6) + platform.add_period_constraint(j2s1.jtag.tck, 6) class XilinxBscanSpi(xilinx.XilinxPlatform): @@ -124,6 +337,7 @@ class XilinxBscanSpi(xilinx.XilinxPlatform): ("fbg484-1", 2): ["L16", None, "H18", "H19", "G18", "F19"], ("fbg676-1", 1): ["C23", None, "B24", "A25", "B22", "A22"], ("ffg901-1", 1): ["V26", None, "R30", "T30", "R28", "T28"], + ("ffg900-1", 1): ["U19", None, "P24", "R25", "R20", "R21"], ("ffg1156-1", 1): ["V30", None, "AA33", "AA34", "Y33", "Y34"], ("ffg1157-1", 1): ["AL33", None, "AN33", "AN34", "AK34", "AL34"], ("ffg1158-1", 1): ["C24", None, "A23", "A24", "B26", "A26"], @@ -132,6 +346,9 @@ class XilinxBscanSpi(xilinx.XilinxPlatform): ("flg1155-1", 1): ["AL28", None, "AE28", "AF28", "AJ29", "AJ30"], ("flg1932-1", 1): ["V32", None, "T33", "R33", "U31", "T31"], ("flg1926-1", 1): ["AK33", None, "AN34", "AN35", "AJ34", "AK34"], + + ("ffva1156-2-e", 1): ["G26", None, "M20", "L20", "R21", "R22"], + ("ffva1156-2-e", "sayma"): ["K21", None, "M20", "L20", "R21", "R22"], } pinouts = { @@ -181,6 +398,7 @@ class XilinxBscanSpi(xilinx.XilinxPlatform): "xc7a75t": ("csg324-1", 1, "LVCMOS25", Series7), "xc7k160t": ("fbg484-1", 2, "LVCMOS25", Series7), "xc7k325t": ("fbg676-1", 1, "LVCMOS25", Series7), + "xc7k325t-debug": ("ffg900-1", 1, "LVCMOS25", Series7), "xc7k355t": ("ffg901-1", 1, "LVCMOS25", Series7), "xc7k410t": ("fbg676-1", 1, "LVCMOS25", Series7), "xc7k420t": ("ffg1156-1", 1, "LVCMOS25", Series7), @@ -197,35 +415,53 @@ class XilinxBscanSpi(xilinx.XilinxPlatform): "xc7vx550t": ("ffg1158-1", 1, "LVCMOS18", Series7), "xc7vx690t": ("ffg1157-1", 1, "LVCMOS18", Series7), "xc7vx980t": ("ffg1926-1", 1, "LVCMOS18", Series7), + + "xcku040": ("ffva1156-2-e", 1, "LVCMOS18", Ultrascale), + "xcku040-sayma": ("ffva1156-2-e", "sayma", "LVCMOS18", Ultrascale), } def __init__(self, device, pins, std, toolchain="ise"): + ios = [self.make_spi(0, pins, std, toolchain)] + if device == "xc7k325t-ffg900-1": # debug + ios += [ + ("user_sma_clock_p", 0, mb.Pins("L25"), mb.IOStandard("LVCMOS25")), + ("user_sma_clock_n", 0, mb.Pins("K25"), mb.IOStandard("LVCMOS25")), + ("user_sma_gpio_p", 0, mb.Pins("Y23"), mb.IOStandard("LVCMOS25")), + ("user_sma_gpio_n", 0, mb.Pins("Y24"), mb.IOStandard("LVCMOS25")), + ] + xilinx.XilinxPlatform.__init__(self, device, ios, toolchain=toolchain) + + @staticmethod + def make_spi(i, pins, std, toolchain): + pu = "PULLUP" if toolchain == "ise" else "PULLUP TRUE" + pd = "PULLDOWN" if toolchain == "ise" else "PULLDOWN TRUE" cs_n, clk, mosi, miso = pins[:4] - io = ["spiflash", 0, - Subsignal("cs_n", Pins(cs_n)), - Subsignal("mosi", Pins(mosi)), - Subsignal("miso", Pins(miso), Misc("PULLUP")), - IOStandard(std), - ] + io = ["spiflash", i, + mb.Subsignal("cs_n", mb.Pins(cs_n), mb.Misc(pu)), + mb.Subsignal("mosi", mb.Pins(mosi), mb.Misc(pu)), + mb.Subsignal("miso", mb.Pins(miso), mb.Misc(pu)), + mb.IOStandard(std), + ] if clk: - io.append(Subsignal("clk", Pins(clk))) + io.append(mb.Subsignal("clk", mb.Pins(clk), mb.Misc(pd))) for i, p in enumerate(pins[4:]): - io.append(Subsignal("pullup{}".format(i), Pins(p), Misc("PULLUP"))) - xilinx.XilinxPlatform.__init__(self, device, [io], toolchain=toolchain) + io.append(mb.Subsignal("pullup{}".format(i), mb.Pins(p), + mb.Misc(pu))) + return io @classmethod - def make(cls, device, errors=False): - pkg, id, std, Top = cls.pinouts[device] + def make(cls, target, errors=False): + pkg, id, std, Top = cls.pinouts[target] pins = cls.packages[(pkg, id)] + device = target.split("-", 1)[0] platform = cls("{}-{}".format(device, pkg), pins, std, Top.toolchain) top = Top(platform) - name = "bscan_spi_{}".format(device) - dir = "build_{}".format(device) + name = "bscan_spi_{}".format(target) try: - platform.build(top, build_name=name, build_dir=dir) + platform.build(top, build_name=name) except Exception as e: print(("ERROR: xilinx_bscan_spi build failed " - "for {}: {}").format(device, e)) + "for {}: {}").format(target, e)) if errors: raise