contrib: replace the GPLv2-or-later license tag
[fw/openocd] / contrib / loaders / flash / fpga / xilinx_bscan_spi.py
1 #!/usr/bin/python3
2 # SPDX-License-Identifier: GPL-2.0-or-later
3
4 #  Copyright (C) 2015 Robert Jordens <jordens@gmail.com>
5
6 import unittest
7
8 import migen as mg
9 import migen.build.generic_platform as mb
10 from migen.genlib import io
11 from migen.build import xilinx
12
13
14 """
15 This migen script produces proxy bitstreams to allow programming SPI flashes
16 behind FPGAs.
17
18 Bitstream binaries built with this script are available at:
19 https://github.com/jordens/bscan_spi_bitstreams
20
21 A JTAG2SPI transfer consists of:
22
23 1. an arbitrary number of 0 bits (from BYPASS registers in front of the
24    JTAG2SPI DR)
25 2. a marker bit (1) indicating the start of the JTAG2SPI transaction
26 3. 32 bits (big endian) describing the length of the SPI transaction
27 4. a number of SPI clock cycles (corresponding to 3.) with CS_N asserted
28 5. an arbitrary number of cycles (to shift MISO/TDO data through subsequent
29    BYPASS registers)
30
31 Notes:
32
33 * The JTAG2SPI DR is 1 bit long (due to different sampling edges of
34   {MISO,MOSI}/{TDO,TDI}).
35 * MOSI is TDI with half a cycle delay.
36 * TDO is MISO with half a cycle delay.
37 * CAPTURE-DR needs to be performed before SHIFT-DR on the BYPASSed TAPs in
38   JTAG chain to clear the BYPASS registers to 0.
39
40 https://github.com/m-labs/migen
41 """
42
43
44 class JTAG2SPI(mg.Module):
45     def __init__(self, spi=None, bits=32):
46         self.jtag = mg.Record([
47             ("sel", 1),
48             ("shift", 1),
49             ("capture", 1),
50             ("tck", 1),
51             ("tdi", 1),
52             ("tdo", 1),
53         ])
54         self.cs_n = mg.TSTriple()
55         self.clk = mg.TSTriple()
56         self.mosi = mg.TSTriple()
57         self.miso = mg.TSTriple()
58
59         # # #
60
61         self.cs_n.o.reset = mg.Constant(1)
62         self.mosi.o.reset_less = True
63         bits = mg.Signal(bits, reset_less=True)
64         head = mg.Signal(max=len(bits), reset=len(bits) - 1)
65         self.clock_domains.cd_sys = mg.ClockDomain()
66         self.submodules.fsm = mg.FSM("IDLE")
67         if spi is not None:
68             self.specials += [
69                     self.cs_n.get_tristate(spi.cs_n),
70                     self.mosi.get_tristate(spi.mosi),
71                     self.miso.get_tristate(spi.miso),
72             ]
73             if hasattr(spi, "clk"):  # 7 Series drive it fixed
74                 self.specials += self.clk.get_tristate(spi.clk)
75                 # self.specials += io.DDROutput(1, 0, spi.clk, self.clk.o)
76         self.comb += [
77                 self.cd_sys.rst.eq(self.jtag.sel & self.jtag.capture),
78                 self.cd_sys.clk.eq(self.jtag.tck),
79                 self.cs_n.oe.eq(self.jtag.sel),
80                 self.clk.oe.eq(self.jtag.sel),
81                 self.mosi.oe.eq(self.jtag.sel),
82                 self.miso.oe.eq(0),
83                 # Do not suppress CLK toggles outside CS_N asserted.
84                 # Xilinx USRCCLK0 requires three dummy cycles to do anything
85                 # https://www.xilinx.com/support/answers/52626.html
86                 # This is fine since CS_N changes only on falling CLK.
87                 self.clk.o.eq(~self.jtag.tck),
88                 self.jtag.tdo.eq(self.miso.i),
89         ]
90         # Latency calculation (in half cycles):
91         # 0 (falling TCK, rising CLK):
92         #   JTAG adapter: set TDI
93         # 1 (rising TCK, falling CLK):
94         #   JTAG2SPI: sample TDI -> set MOSI
95         #   SPI: set MISO
96         # 2 (falling TCK, rising CLK):
97         #   SPI: sample MOSI
98         #   JTAG2SPI (BSCAN primitive): sample MISO -> set TDO
99         # 3 (rising TCK, falling CLK):
100         #   JTAG adapter: sample TDO
101         self.fsm.act("IDLE",
102                 mg.If(self.jtag.tdi & self.jtag.sel & self.jtag.shift,
103                     mg.NextState("HEAD")
104                 )
105         )
106         self.fsm.act("HEAD",
107                 mg.If(head == 0,
108                     mg.NextState("XFER")
109                 )
110         )
111         self.fsm.act("XFER",
112                 mg.If(bits == 0,
113                     mg.NextState("IDLE")
114                 ),
115         )
116         self.sync += [
117                 self.mosi.o.eq(self.jtag.tdi),
118                 self.cs_n.o.eq(~self.fsm.ongoing("XFER")),
119                 mg.If(self.fsm.ongoing("HEAD"),
120                     bits.eq(mg.Cat(self.jtag.tdi, bits)),
121                     head.eq(head - 1)
122                 ),
123                 mg.If(self.fsm.ongoing("XFER"),
124                     bits.eq(bits - 1)
125                 )
126         ]
127
128
129 class JTAG2SPITest(unittest.TestCase):
130     def setUp(self):
131         self.bits = 8
132         self.dut = JTAG2SPI(bits=self.bits)
133
134     def test_instantiate(self):
135         pass
136
137     def test_initial_conditions(self):
138         def check():
139             yield
140             self.assertEqual((yield self.dut.cs_n.oe), 0)
141             self.assertEqual((yield self.dut.mosi.oe), 0)
142             self.assertEqual((yield self.dut.miso.oe), 0)
143             self.assertEqual((yield self.dut.clk.oe), 0)
144         mg.run_simulation(self.dut, check())
145
146     def test_enable(self):
147         def check():
148             yield self.dut.jtag.sel.eq(1)
149             yield self.dut.jtag.shift.eq(1)
150             yield
151             self.assertEqual((yield self.dut.cs_n.oe), 1)
152             self.assertEqual((yield self.dut.mosi.oe), 1)
153             self.assertEqual((yield self.dut.miso.oe), 0)
154             self.assertEqual((yield self.dut.clk.oe), 1)
155         mg.run_simulation(self.dut, check())
156
157     def run_seq(self, tdi, tdo, spi=None):
158         yield self.dut.jtag.sel.eq(1)
159         yield
160         yield self.dut.jtag.shift.eq(1)
161         for di in tdi:
162             yield self.dut.jtag.tdi.eq(di)
163             yield
164             tdo.append((yield self.dut.jtag.tdo))
165             if spi is not None:
166                 v = []
167                 for k in "cs_n clk mosi miso".split():
168                     t = getattr(self.dut, k)
169                     v.append("{}>".format((yield t.o)) if (yield t.oe)
170                             else "<{}".format((yield t.i)))
171                 spi.append(" ".join(v))
172         yield self.dut.jtag.sel.eq(0)
173         yield
174         yield self.dut.jtag.shift.eq(0)
175         yield
176
177     def test_shift(self):
178         bits = 8
179         data = 0x81
180         tdi = [0, 0, 1]  # dummy from BYPASS TAPs and marker
181         tdi += [((bits - 1) >> j) & 1 for j in range(self.bits - 1, -1, -1)]
182         tdi += [(data >> j) & 1 for j in range(bits)]
183         tdi += [0, 0, 0, 0]  # dummy from BYPASS TAPs
184         tdo = []
185         spi = []
186         mg.run_simulation(self.dut, self.run_seq(tdi, tdo, spi))
187         # print(tdo)
188         for l in spi:
189             print(l)
190
191
192 class Spartan3(mg.Module):
193     macro = "BSCAN_SPARTAN3"
194     toolchain = "ise"
195
196     def __init__(self, platform):
197         platform.toolchain.bitgen_opt += " -g compress -g UnusedPin:Pullup"
198         self.submodules.j2s = j2s = JTAG2SPI(platform.request("spiflash"))
199         self.specials += [
200                 mg.Instance(
201                     self.macro,
202                     o_SHIFT=j2s.jtag.shift, o_SEL1=j2s.jtag.sel,
203                     o_CAPTURE=j2s.jtag.capture,
204                     o_DRCK1=j2s.jtag.tck,
205                     o_TDI=j2s.jtag.tdi, i_TDO1=j2s.jtag.tdo,
206                     i_TDO2=0),
207         ]
208         platform.add_period_constraint(j2s.jtag.tck, 6)
209
210
211 class Spartan3A(Spartan3):
212     macro = "BSCAN_SPARTAN3A"
213
214
215 class Spartan6(mg.Module):
216     toolchain = "ise"
217
218     def __init__(self, platform):
219         platform.toolchain.bitgen_opt += " -g compress -g UnusedPin:Pullup"
220         self.submodules.j2s = j2s = JTAG2SPI(platform.request("spiflash"))
221         # clk = mg.Signal()
222         self.specials += [
223                 mg.Instance(
224                     "BSCAN_SPARTAN6", p_JTAG_CHAIN=1,
225                     o_SHIFT=j2s.jtag.shift, o_SEL=j2s.jtag.sel,
226                     o_CAPTURE=j2s.jtag.capture,
227                     o_DRCK=j2s.jtag.tck,
228                     o_TDI=j2s.jtag.tdi, i_TDO=j2s.jtag.tdo),
229                 # mg.Instance("BUFG", i_I=clk, o_O=j2s.jtag.tck)
230         ]
231         platform.add_period_constraint(j2s.jtag.tck, 6)
232
233
234 class Series7(mg.Module):
235     toolchain = "vivado"
236
237     def __init__(self, platform):
238         platform.toolchain.bitstream_commands.extend([
239             "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]",
240             "set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]"
241         ])
242         self.submodules.j2s = j2s = JTAG2SPI(platform.request("spiflash"))
243         # clk = mg.Signal()
244         self.specials += [
245                 mg.Instance(
246                     "BSCANE2", p_JTAG_CHAIN=1,
247                     o_SHIFT=j2s.jtag.shift, o_SEL=j2s.jtag.sel,
248                     o_CAPTURE=j2s.jtag.capture,
249                     o_DRCK=j2s.jtag.tck,
250                     o_TDI=j2s.jtag.tdi, i_TDO=j2s.jtag.tdo),
251                 mg.Instance(
252                     "STARTUPE2", i_CLK=0, i_GSR=0, i_GTS=0,
253                     i_KEYCLEARB=0, i_PACK=1,
254                     i_USRCCLKO=j2s.clk.o, i_USRCCLKTS=~j2s.clk.oe,
255                     i_USRDONEO=1, i_USRDONETS=1),
256                 # mg.Instance("BUFG", i_I=clk, o_O=j2s.jtag.tck)
257         ]
258         platform.add_period_constraint(j2s.jtag.tck, 6)
259         try:
260             self.comb += [
261                     platform.request("user_sma_gpio_p").eq(j2s.cs_n.i),
262                     platform.request("user_sma_gpio_n").eq(j2s.clk.o),
263                     platform.request("user_sma_clock_p").eq(j2s.mosi.o),
264                     platform.request("user_sma_clock_n").eq(j2s.miso.i),
265             ]
266         except mb.ConstraintError:
267             pass
268
269
270 class Ultrascale(mg.Module):
271     toolchain = "vivado"
272
273     def __init__(self, platform):
274         platform.toolchain.bitstream_commands.extend([
275             "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]",
276             "set_property BITSTREAM.CONFIG.UNUSEDPIN Pullnone [current_design]",
277         ])
278         self.submodules.j2s0 = j2s0 = JTAG2SPI()
279         self.submodules.j2s1 = j2s1 = JTAG2SPI(platform.request("spiflash"))
280         di = mg.Signal(4)
281         self.comb += mg.Cat(j2s0.mosi.i, j2s0.miso.i).eq(di)
282         self.specials += [
283                 mg.Instance("BSCANE2", p_JTAG_CHAIN=1,
284                     o_SHIFT=j2s0.jtag.shift, o_SEL=j2s0.jtag.sel,
285                     o_CAPTURE=j2s0.jtag.capture,
286                     o_DRCK=j2s0.jtag.tck,
287                     o_TDI=j2s0.jtag.tdi, i_TDO=j2s0.jtag.tdo),
288                 mg.Instance("BSCANE2", p_JTAG_CHAIN=2,
289                     o_SHIFT=j2s1.jtag.shift, o_SEL=j2s1.jtag.sel,
290                     o_CAPTURE=j2s1.jtag.capture,
291                     o_DRCK=j2s1.jtag.tck,
292                     o_TDI=j2s1.jtag.tdi, i_TDO=j2s1.jtag.tdo),
293                 mg.Instance("STARTUPE3", i_GSR=0, i_GTS=0,
294                     i_KEYCLEARB=0, i_PACK=1,
295                     i_USRDONEO=1, i_USRDONETS=1,
296                     i_USRCCLKO=mg.Mux(j2s0.clk.oe, j2s0.clk.o, j2s1.clk.o),
297                     i_USRCCLKTS=~(j2s0.clk.oe | j2s1.clk.oe),
298                     i_FCSBO=j2s0.cs_n.o, i_FCSBTS=~j2s0.cs_n.oe,
299                     o_DI=di,
300                     i_DO=mg.Cat(j2s0.mosi.o, j2s0.miso.o, 0, 0),
301                     i_DTS=mg.Cat(~j2s0.mosi.oe, ~j2s0.miso.oe, 1, 1))
302         ]
303         platform.add_period_constraint(j2s0.jtag.tck, 6)
304         platform.add_period_constraint(j2s1.jtag.tck, 6)
305
306
307 class XilinxBscanSpi(xilinx.XilinxPlatform):
308     packages = {
309         # (package-speedgrade, id): [cs_n, clk, mosi, miso, *pullups]
310         ("cp132", 1): ["M2", "N12", "N2", "N8"],
311         ("fg320", 1): ["U3", "U16", "T4", "N10"],
312         ("fg320", 2): ["V3", "U16", "T11", "V16"],
313         ("fg484", 1): ["Y4", "AA20", "AB14", "AB20"],
314         ("fgg484", 1): ["Y4", "AA20", "AB14", "AB20"],
315         ("fgg400", 1): ["Y2", "Y19", "W12", "W18"],
316         ("ftg256", 1): ["T2", "R14", "P10", "T14"],
317         ("ft256", 1): ["T2", "R14", "P10", "T14"],
318         ("fg400", 1): ["Y2", "Y19", "W12", "W18"],
319         ("cs484", 1): ["U7", "V17", "V13", "W17"],
320         ("qg144-2", 1): ["P38", "P70", "P64", "P65", "P62", "P61"],
321         ("cpg196-2", 1): ["P2", "N13", "P11", "N11", "N10", "P10"],
322         ("cpg236-1", 1): ["K19", None, "D18", "D19", "G18", "F18"],
323         ("csg484-2", 1): ["AB5", "W17", "AB17", "Y17", "V13", "W13"],
324         ("csg324-2", 1): ["V3", "R15", "T13", "R13", "T14", "V14"],
325         ("csg324-1", 1): ["L13", None, "K17", "K18", "L14", "M14"],
326         ("fbg484-1", 1): ["T19", None, "P22", "R22", "P21", "R21"],
327         ("fbg484-1", 2): ["L16", None, "H18", "H19", "G18", "F19"],
328         ("fbg676-1", 1): ["C23", None, "B24", "A25", "B22", "A22"],
329         ("ffg901-1", 1): ["V26", None, "R30", "T30", "R28", "T28"],
330         ("ffg900-1", 1): ["U19", None, "P24", "R25", "R20", "R21"],
331         ("ffg1156-1", 1): ["V30", None, "AA33", "AA34", "Y33", "Y34"],
332         ("ffg1157-1", 1): ["AL33", None, "AN33", "AN34", "AK34", "AL34"],
333         ("ffg1158-1", 1): ["C24", None, "A23", "A24", "B26", "A26"],
334         ("ffg1926-1", 1): ["AK33", None, "AN34", "AN35", "AJ34", "AK34"],
335         ("fhg1761-1", 1): ["AL36", None, "AM36", "AN36", "AJ36", "AJ37"],
336         ("flg1155-1", 1): ["AL28", None, "AE28", "AF28", "AJ29", "AJ30"],
337         ("flg1932-1", 1): ["V32", None, "T33", "R33", "U31", "T31"],
338         ("flg1926-1", 1): ["AK33", None, "AN34", "AN35", "AJ34", "AK34"],
339
340         ("ffva1156-2-e", 1): ["G26", None, "M20", "L20", "R21", "R22"],
341         ("ffva1156-2-e", "sayma"): ["K21", None, "M20", "L20", "R21", "R22"],
342     }
343
344     pinouts = {
345         # bitstreams are named by die, package does not matter, speed grade
346         # should not matter.
347         #
348         # chip: (package, id, standard, class)
349         "xc3s100e": ("cp132", 1, "LVCMOS33", Spartan3),
350         "xc3s1200e": ("fg320", 1, "LVCMOS33", Spartan3),
351         "xc3s1400a": ("fg484", 1, "LVCMOS33", Spartan3A),
352         "xc3s1400an": ("fgg484", 1, "LVCMOS33", Spartan3A),
353         "xc3s1600e": ("fg320", 1, "LVCMOS33", Spartan3),
354         "xc3s200a": ("fg320", 2, "LVCMOS33", Spartan3A),
355         "xc3s200an": ("ftg256", 1, "LVCMOS33", Spartan3A),
356         "xc3s250e": ("cp132", 1, "LVCMOS33", Spartan3),
357         "xc3s400a": ("fg320", 2, "LVCMOS33", Spartan3A),
358         "xc3s400an": ("fgg400", 1, "LVCMOS33", Spartan3A),
359         "xc3s500e": ("cp132", 1, "LVCMOS33", Spartan3),
360         "xc3s50a": ("ft256", 1, "LVCMOS33", Spartan3A),
361         "xc3s50an": ("ftg256", 1, "LVCMOS33", Spartan3A),
362         "xc3s700a": ("fg400", 1, "LVCMOS33", Spartan3A),
363         "xc3s700an": ("fgg484", 1, "LVCMOS33", Spartan3A),
364         "xc3sd1800a": ("cs484", 1, "LVCMOS33", Spartan3A),
365         "xc3sd3400a": ("cs484", 1, "LVCMOS33", Spartan3A),
366
367         "xc6slx100": ("csg484-2", 1, "LVCMOS33", Spartan6),
368         "xc6slx100t": ("csg484-2", 1, "LVCMOS33", Spartan6),
369         "xc6slx150": ("csg484-2", 1, "LVCMOS33", Spartan6),
370         "xc6slx150t": ("csg484-2", 1, "LVCMOS33", Spartan6),
371         "xc6slx16": ("cpg196-2", 1, "LVCMOS33", Spartan6),
372         "xc6slx25": ("csg324-2", 1, "LVCMOS33", Spartan6),
373         "xc6slx25t": ("csg324-2", 1, "LVCMOS33", Spartan6),
374         "xc6slx45": ("csg324-2", 1, "LVCMOS33", Spartan6),
375         "xc6slx45t": ("csg324-2", 1, "LVCMOS33", Spartan6),
376         "xc6slx4": ("cpg196-2", 1, "LVCMOS33", Spartan6),
377         "xc6slx4t": ("qg144-2", 1, "LVCMOS33", Spartan6),
378         "xc6slx75": ("csg484-2", 1, "LVCMOS33", Spartan6),
379         "xc6slx75t": ("csg484-2", 1, "LVCMOS33", Spartan6),
380         "xc6slx9": ("cpg196-2", 1, "LVCMOS33", Spartan6),
381         "xc6slx9t": ("qg144-2", 1, "LVCMOS33", Spartan6),
382
383         "xc7a100t": ("csg324-1", 1, "LVCMOS25", Series7),
384         "xc7a15t": ("cpg236-1", 1, "LVCMOS25", Series7),
385         "xc7a200t": ("fbg484-1", 1, "LVCMOS25", Series7),
386         "xc7a35t": ("cpg236-1", 1, "LVCMOS25", Series7),
387         "xc7a50t": ("cpg236-1", 1, "LVCMOS25", Series7),
388         "xc7a75t": ("csg324-1", 1, "LVCMOS25", Series7),
389         "xc7k160t": ("fbg484-1", 2, "LVCMOS25", Series7),
390         "xc7k325t": ("fbg676-1", 1, "LVCMOS25", Series7),
391         "xc7k325t-debug": ("ffg900-1", 1, "LVCMOS25", Series7),
392         "xc7k355t": ("ffg901-1", 1, "LVCMOS25", Series7),
393         "xc7k410t": ("fbg676-1", 1, "LVCMOS25", Series7),
394         "xc7k420t": ("ffg1156-1", 1, "LVCMOS25", Series7),
395         "xc7k480t": ("ffg1156-1", 1, "LVCMOS25", Series7),
396         "xc7k70t": ("fbg484-1", 2, "LVCMOS25", Series7),
397         "xc7v2000t": ("fhg1761-1", 1, "LVCMOS18", Series7),
398         "xc7v585t": ("ffg1157-1", 1, "LVCMOS18", Series7),
399         "xc7vh580t": ("flg1155-1", 1, "LVCMOS18", Series7),
400         "xc7vh870t": ("flg1932-1", 1, "LVCMOS18", Series7),
401         "xc7vx1140t": ("flg1926-1", 1, "LVCMOS18", Series7),
402         "xc7vx330t": ("ffg1157-1", 1, "LVCMOS18", Series7),
403         "xc7vx415t": ("ffg1157-1", 1, "LVCMOS18", Series7),
404         "xc7vx485t": ("ffg1157-1", 1, "LVCMOS18", Series7),
405         "xc7vx550t": ("ffg1158-1", 1, "LVCMOS18", Series7),
406         "xc7vx690t": ("ffg1157-1", 1, "LVCMOS18", Series7),
407         "xc7vx980t": ("ffg1926-1", 1, "LVCMOS18", Series7),
408
409         "xcku040": ("ffva1156-2-e", 1, "LVCMOS18", Ultrascale),
410         "xcku040-sayma": ("ffva1156-2-e", "sayma", "LVCMOS18", Ultrascale),
411     }
412
413     def __init__(self, device, pins, std, toolchain="ise"):
414         ios = [self.make_spi(0, pins, std, toolchain)]
415         if device == "xc7k325t-ffg900-1":  # debug
416             ios += [
417                 ("user_sma_clock_p", 0, mb.Pins("L25"), mb.IOStandard("LVCMOS25")),
418                 ("user_sma_clock_n", 0, mb.Pins("K25"), mb.IOStandard("LVCMOS25")),
419                 ("user_sma_gpio_p", 0, mb.Pins("Y23"), mb.IOStandard("LVCMOS25")),
420                 ("user_sma_gpio_n", 0, mb.Pins("Y24"), mb.IOStandard("LVCMOS25")),
421             ]
422         xilinx.XilinxPlatform.__init__(self, device, ios, toolchain=toolchain)
423
424     @staticmethod
425     def make_spi(i, pins, std, toolchain):
426         pu = "PULLUP" if toolchain == "ise" else "PULLUP TRUE"
427         pd = "PULLDOWN" if toolchain == "ise" else "PULLDOWN TRUE"
428         cs_n, clk, mosi, miso = pins[:4]
429         io = ["spiflash", i,
430             mb.Subsignal("cs_n", mb.Pins(cs_n), mb.Misc(pu)),
431             mb.Subsignal("mosi", mb.Pins(mosi), mb.Misc(pu)),
432             mb.Subsignal("miso", mb.Pins(miso), mb.Misc(pu)),
433             mb.IOStandard(std),
434             ]
435         if clk:
436             io.append(mb.Subsignal("clk", mb.Pins(clk), mb.Misc(pd)))
437         for i, p in enumerate(pins[4:]):
438             io.append(mb.Subsignal("pullup{}".format(i), mb.Pins(p),
439                                 mb.Misc(pu)))
440         return io
441
442     @classmethod
443     def make(cls, target, errors=False):
444         pkg, id, std, Top = cls.pinouts[target]
445         pins = cls.packages[(pkg, id)]
446         device = target.split("-", 1)[0]
447         platform = cls("{}-{}".format(device, pkg), pins, std, Top.toolchain)
448         top = Top(platform)
449         name = "bscan_spi_{}".format(target)
450         try:
451             platform.build(top, build_name=name)
452         except Exception as e:
453             print(("ERROR: xilinx_bscan_spi build failed "
454                   "for {}: {}").format(target, e))
455             if errors:
456                 raise
457
458
459 if __name__ == "__main__":
460     import argparse
461     import multiprocessing
462     p = argparse.ArgumentParser(description="build bscan_spi bitstreams "
463                                 "for openocd jtagspi flash driver")
464     p.add_argument("device", nargs="*",
465                    default=sorted(list(XilinxBscanSpi.pinouts)),
466                    help="build for these devices (default: %(default)s)")
467     p.add_argument("-p", "--parallel", default=1, type=int,
468                    help="number of parallel builds (default: %(default)s)")
469     args = p.parse_args()
470     pool = multiprocessing.Pool(args.parallel)
471     pool.map(XilinxBscanSpi.make, args.device, chunksize=1)