+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):