contrib: replace the GPLv2-or-later license tag
[fw/openocd] / contrib / loaders / flash / fpga / xilinx_bscan_spi.py
index fa4ec2ace6e22d3a69bf859464adfaab15a30811..408cfdd79f28628dbc2aaad7be5b8b2bb1aeb850 100755 (executable)
@@ -1,20 +1,13 @@
 #!/usr/bin/python3
-#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
 #  Copyright (C) 2015 Robert Jordens <jordens@gmail.com>
-#
-#  This program is free software; you can redistribute it and/or modify
-#  it under the terms of the GNU General Public License as published by
-#  the Free Software Foundation; either version 2 of the License, or
-#  (at your option) any later version.
-#
-#  This program is distributed in the hope that it will be useful,
-#  but WITHOUT ANY WARRANTY; without even the implied warranty of
-#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-#  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 +18,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 +275,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 +327,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 +336,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 +388,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 +405,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