#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2015 Robert Jordens import unittest import migen as mg import migen.build.generic_platform as mb from migen.genlib import io from migen.build import xilinx """ This migen script produces proxy bitstreams to allow programming SPI flashes behind FPGAs. Bitstream binaries built with this script are available at: https://github.com/jordens/bscan_spi_bitstreams 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 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.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), ] platform.add_period_constraint(j2s.jtag.tck, 6) class Spartan3A(Spartan3): macro = "BSCAN_SPARTAN3A" class Spartan6(mg.Module): toolchain = "ise" def __init__(self, platform): platform.toolchain.bitgen_opt += " -g compress -g UnusedPin:Pullup" 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): platform.toolchain.bitstream_commands.extend([ "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", "set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]", ]) 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): packages = { # (package-speedgrade, id): [cs_n, clk, mosi, miso, *pullups] ("cp132", 1): ["M2", "N12", "N2", "N8"], ("fg320", 1): ["U3", "U16", "T4", "N10"], ("fg320", 2): ["V3", "U16", "T11", "V16"], ("fg484", 1): ["Y4", "AA20", "AB14", "AB20"], ("fgg484", 1): ["Y4", "AA20", "AB14", "AB20"], ("fgg400", 1): ["Y2", "Y19", "W12", "W18"], ("ftg256", 1): ["T2", "R14", "P10", "T14"], ("ft256", 1): ["T2", "R14", "P10", "T14"], ("fg400", 1): ["Y2", "Y19", "W12", "W18"], ("cs484", 1): ["U7", "V17", "V13", "W17"], ("qg144-2", 1): ["P38", "P70", "P64", "P65", "P62", "P61"], ("cpg196-2", 1): ["P2", "N13", "P11", "N11", "N10", "P10"], ("cpg236-1", 1): ["K19", None, "D18", "D19", "G18", "F18"], ("csg484-2", 1): ["AB5", "W17", "AB17", "Y17", "V13", "W13"], ("csg324-2", 1): ["V3", "R15", "T13", "R13", "T14", "V14"], ("csg324-1", 1): ["L13", None, "K17", "K18", "L14", "M14"], ("fbg484-1", 1): ["T19", None, "P22", "R22", "P21", "R21"], ("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"], ("ffg1926-1", 1): ["AK33", None, "AN34", "AN35", "AJ34", "AK34"], ("fhg1761-1", 1): ["AL36", None, "AM36", "AN36", "AJ36", "AJ37"], ("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 = { # bitstreams are named by die, package does not matter, speed grade # should not matter. # # chip: (package, id, standard, class) "xc3s100e": ("cp132", 1, "LVCMOS33", Spartan3), "xc3s1200e": ("fg320", 1, "LVCMOS33", Spartan3), "xc3s1400a": ("fg484", 1, "LVCMOS33", Spartan3A), "xc3s1400an": ("fgg484", 1, "LVCMOS33", Spartan3A), "xc3s1600e": ("fg320", 1, "LVCMOS33", Spartan3), "xc3s200a": ("fg320", 2, "LVCMOS33", Spartan3A), "xc3s200an": ("ftg256", 1, "LVCMOS33", Spartan3A), "xc3s250e": ("cp132", 1, "LVCMOS33", Spartan3), "xc3s400a": ("fg320", 2, "LVCMOS33", Spartan3A), "xc3s400an": ("fgg400", 1, "LVCMOS33", Spartan3A), "xc3s500e": ("cp132", 1, "LVCMOS33", Spartan3), "xc3s50a": ("ft256", 1, "LVCMOS33", Spartan3A), "xc3s50an": ("ftg256", 1, "LVCMOS33", Spartan3A), "xc3s700a": ("fg400", 1, "LVCMOS33", Spartan3A), "xc3s700an": ("fgg484", 1, "LVCMOS33", Spartan3A), "xc3sd1800a": ("cs484", 1, "LVCMOS33", Spartan3A), "xc3sd3400a": ("cs484", 1, "LVCMOS33", Spartan3A), "xc6slx100": ("csg484-2", 1, "LVCMOS33", Spartan6), "xc6slx100t": ("csg484-2", 1, "LVCMOS33", Spartan6), "xc6slx150": ("csg484-2", 1, "LVCMOS33", Spartan6), "xc6slx150t": ("csg484-2", 1, "LVCMOS33", Spartan6), "xc6slx16": ("cpg196-2", 1, "LVCMOS33", Spartan6), "xc6slx25": ("csg324-2", 1, "LVCMOS33", Spartan6), "xc6slx25t": ("csg324-2", 1, "LVCMOS33", Spartan6), "xc6slx45": ("csg324-2", 1, "LVCMOS33", Spartan6), "xc6slx45t": ("csg324-2", 1, "LVCMOS33", Spartan6), "xc6slx4": ("cpg196-2", 1, "LVCMOS33", Spartan6), "xc6slx4t": ("qg144-2", 1, "LVCMOS33", Spartan6), "xc6slx75": ("csg484-2", 1, "LVCMOS33", Spartan6), "xc6slx75t": ("csg484-2", 1, "LVCMOS33", Spartan6), "xc6slx9": ("cpg196-2", 1, "LVCMOS33", Spartan6), "xc6slx9t": ("qg144-2", 1, "LVCMOS33", Spartan6), "xc7a100t": ("csg324-1", 1, "LVCMOS25", Series7), "xc7a15t": ("cpg236-1", 1, "LVCMOS25", Series7), "xc7a200t": ("fbg484-1", 1, "LVCMOS25", Series7), "xc7a35t": ("cpg236-1", 1, "LVCMOS25", Series7), "xc7a50t": ("cpg236-1", 1, "LVCMOS25", Series7), "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), "xc7k480t": ("ffg1156-1", 1, "LVCMOS25", Series7), "xc7k70t": ("fbg484-1", 2, "LVCMOS25", Series7), "xc7v2000t": ("fhg1761-1", 1, "LVCMOS18", Series7), "xc7v585t": ("ffg1157-1", 1, "LVCMOS18", Series7), "xc7vh580t": ("flg1155-1", 1, "LVCMOS18", Series7), "xc7vh870t": ("flg1932-1", 1, "LVCMOS18", Series7), "xc7vx1140t": ("flg1926-1", 1, "LVCMOS18", Series7), "xc7vx330t": ("ffg1157-1", 1, "LVCMOS18", Series7), "xc7vx415t": ("ffg1157-1", 1, "LVCMOS18", Series7), "xc7vx485t": ("ffg1157-1", 1, "LVCMOS18", Series7), "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", 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(mb.Subsignal("clk", mb.Pins(clk), mb.Misc(pd))) for i, p in enumerate(pins[4:]): io.append(mb.Subsignal("pullup{}".format(i), mb.Pins(p), mb.Misc(pu))) return io @classmethod 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(target) try: platform.build(top, build_name=name) except Exception as e: print(("ERROR: xilinx_bscan_spi build failed " "for {}: {}").format(target, e)) if errors: raise if __name__ == "__main__": import argparse import multiprocessing p = argparse.ArgumentParser(description="build bscan_spi bitstreams " "for openocd jtagspi flash driver") p.add_argument("device", nargs="*", default=sorted(list(XilinxBscanSpi.pinouts)), help="build for these devices (default: %(default)s)") p.add_argument("-p", "--parallel", default=1, type=int, help="number of parallel builds (default: %(default)s)") args = p.parse_args() pool = multiprocessing.Pool(args.parallel) pool.map(XilinxBscanSpi.make, args.device, chunksize=1)