From 6159694af9707436dad3c3595d8567fab9741344 Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 21 May 2025 15:28:31 +0800 Subject: [PATCH 1/3] spi2: remove reset_less --- misoc/cores/spi2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/misoc/cores/spi2.py b/misoc/cores/spi2.py index d2685580c..b8acc6da9 100644 --- a/misoc/cores/spi2.py +++ b/misoc/cores/spi2.py @@ -50,7 +50,7 @@ def __init__(self, width): # Parallel data out (from serial) self.pdi = Signal(width) # Serial data out (from parallel) - self.sdo = Signal(reset_less=True) + self.sdo = Signal() # Serial data in # Must be sampled at a higher layer at self.sample self.sdi = Signal() @@ -65,7 +65,7 @@ def __init__(self, width): ### - sr = Signal(width, reset_less=True) + sr = Signal(width) self.comb += [ self.pdi.eq(Mux(self.lsb_first, From a2d697967f85f720c8f4b2b03ee93653787fe106 Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 21 May 2025 15:29:42 +0800 Subject: [PATCH 2/3] spi2: push all I/O interfacing registers to IOB --- misoc/cores/spi2.py | 50 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/misoc/cores/spi2.py b/misoc/cores/spi2.py index b8acc6da9..e8b5c2ff9 100644 --- a/misoc/cores/spi2.py +++ b/misoc/cores/spi2.py @@ -278,7 +278,7 @@ def __init__(self, *pads): class SPIInterfaceXC7Diff(Module): - def __init__(self, pads, pads_n): + def __init__(self, pads, pads_n, sdo=None): self.cs = Signal(len(getattr(pads, "cs_n", [0]))) self.cs_polarity = Signal.like(self.cs) self.clk_next = Signal() @@ -286,14 +286,36 @@ def __init__(self, pads, pads_n): self.cs_next = Signal() self.ce = Signal() self.sample = Signal() - self.offline = Signal() - self.half_duplex = Signal() + self.offline_next = Signal() + self.half_duplex_next = Signal() self.sdi = Signal() - self.sdo = Signal() + self.half_duplex = Signal() + + # All data signals outputting to IO elements should be in IOB. + if hasattr(pads, "mosi"): + assert "iob" in sdo.attr + self.sdo = sdo + + def get_iob_offline_signal(): + offline = Signal() + # HACK: Prohibit vivado from merging IOB FFs + # + # DONT_TOUCH/KEEP attribute is inapplicable here, it risks vivado + # synthesizing this FF with incompatible control signals initially + # + # All IOB FFs in the same OLOGIC slice MUST share the same (re)set + self.specials += Instance("FDSE", + attr={"iob"}, + i_D=self.offline_next, + i_CE=1, + i_C=ClockSignal(), + i_S=ResetSignal(), + o_Q=offline) + return offline cs = Signal.like(self.cs) cs.reset = C((1 << len(self.cs)) - 1) - clk = Signal() + clk = Signal(attr={"iob"}) miso = Signal() mosi = Signal() miso_reg = Signal(reset_less=True) @@ -302,6 +324,7 @@ def __init__(self, pads, pads_n): self.sdi.eq(Mux(self.half_duplex, mosi_reg, miso_reg)) ] self.sync += [ + self.half_duplex.eq(self.half_duplex_next), If(self.ce, cs.eq((Replicate(self.cs_next, len(self.cs)) & self.cs) ^ ~self.cs_polarity), @@ -314,21 +337,26 @@ def __init__(self, pads, pads_n): ] if hasattr(pads, "cs_n"): + cs.attr.add("iob") for i in range(len(pads.cs_n)): self.specials += Instance("OBUFTDS", - i_I=cs[i], i_T=self.offline, + i_I=cs[i], i_T=get_iob_offline_signal(), o_O=pads.cs_n[i], o_OB=pads_n.cs_n[i]) self.specials += Instance("OBUFTDS", - i_I=clk, i_T=self.offline, + i_I=clk, i_T=get_iob_offline_signal(), o_O=pads.clk, o_OB=pads_n.clk) if hasattr(pads, "mosi"): + sdo_out_disable = Signal(attr={"iob"}, reset=1) + self.sync += sdo_out_disable.eq( + self.offline_next | self.half_duplex_next) self.specials += Instance("IOBUFDS", - o_O=mosi, i_I=self.sdo, i_T=self.offline | self.half_duplex, + o_O=mosi, i_I=self.sdo, i_T=sdo_out_disable, io_IO=pads.mosi, io_IOB=pads_n.mosi) + mosi_reg.attr.add("iob") if hasattr(pads, "miso"): - self.specials += Instance("IOBUFDS", - o_O=miso, i_I=self.sdo, i_T=1, - io_IO=pads.miso, io_IOB=pads_n.miso) + self.specials += Instance("IBUFDS", + o_O=miso, i_I=pads.miso, i_IB=pads_n.miso) + miso_reg.attr.add("iob") class SPIInterfaceiCE40Diff(Module): From b0e59489611fc90f3ae9619c7cca198066f46ad7 Mon Sep 17 00:00:00 2001 From: occheung Date: Tue, 10 Jun 2025 16:49:43 +0800 Subject: [PATCH 3/3] spi2: generalize XC7 interface to allow single-ended IO --- misoc/cores/spi2.py | 74 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 16 deletions(-) diff --git a/misoc/cores/spi2.py b/misoc/cores/spi2.py index e8b5c2ff9..ef0e21e82 100644 --- a/misoc/cores/spi2.py +++ b/misoc/cores/spi2.py @@ -277,8 +277,8 @@ def __init__(self, *pads): i += n -class SPIInterfaceXC7Diff(Module): - def __init__(self, pads, pads_n, sdo=None): +class SPIInterfaceXC7(Module): + def __init__(self, pads, pads_n=None, sdo=None): self.cs = Signal(len(getattr(pads, "cs_n", [0]))) self.cs_polarity = Signal.like(self.cs) self.clk_next = Signal() @@ -289,7 +289,6 @@ def __init__(self, pads, pads_n, sdo=None): self.offline_next = Signal() self.half_duplex_next = Signal() self.sdi = Signal() - self.half_duplex = Signal() # All data signals outputting to IO elements should be in IOB. if hasattr(pads, "mosi"): @@ -312,19 +311,57 @@ def get_iob_offline_signal(): i_S=ResetSignal(), o_Q=offline) return offline + + # This function generates single-ended/differential I/O instances. + # Arguments (excluding pads) are fabric facing signals. + # Directions (being sources or sinks) are from I/O bank's perspective. + def generate_io(pad, pad_n=None, source=None, sink=None, tristate=None): + use_input = sink is not None + use_output = source is not None + use_tristate = tristate is not None + + assert use_input or use_output + + primitive = "{}{}BUF{}{}".format( + "I" if use_input else "", + "O" if use_output else "", + "T" if use_output and not use_input and use_tristate else "", + "DS" if pad_n is not None else "", + ) + + io_dict = {} + if use_input: + io_dict["o_O"] = sink + if use_output: + io_dict["i_I"] = source + if use_tristate: + io_dict["i_T"] = tristate + + if use_input and use_output: + dev_pad = "io_IO" + elif use_input: + dev_pad = "i_I" + elif use_output: + dev_pad = "o_O" + io_dict[dev_pad] = pad + if pad_n is not None: + io_dict[dev_pad + "B"] = pad_n + + self.specials += Instance(primitive, **io_dict) cs = Signal.like(self.cs) cs.reset = C((1 << len(self.cs)) - 1) clk = Signal(attr={"iob"}) + half_duplex = Signal() miso = Signal() mosi = Signal() miso_reg = Signal(reset_less=True) mosi_reg = Signal(reset_less=True) self.comb += [ - self.sdi.eq(Mux(self.half_duplex, mosi_reg, miso_reg)) + self.sdi.eq(Mux(half_duplex, mosi_reg, miso_reg)) ] self.sync += [ - self.half_duplex.eq(self.half_duplex_next), + half_duplex.eq(self.half_duplex_next), If(self.ce, cs.eq((Replicate(self.cs_next, len(self.cs)) & self.cs) ^ ~self.cs_polarity), @@ -339,23 +376,28 @@ def get_iob_offline_signal(): if hasattr(pads, "cs_n"): cs.attr.add("iob") for i in range(len(pads.cs_n)): - self.specials += Instance("OBUFTDS", - i_I=cs[i], i_T=get_iob_offline_signal(), - o_O=pads.cs_n[i], o_OB=pads_n.cs_n[i]) - self.specials += Instance("OBUFTDS", - i_I=clk, i_T=get_iob_offline_signal(), - o_O=pads.clk, o_OB=pads_n.clk) + generate_io(pads.cs_n[i], + pad_n=pads_n.cs_n[i] if pads_n is not None else None, + source=cs[i], + tristate=get_iob_offline_signal()) + generate_io(pads.clk, + pad_n=pads_n.clk if pads_n is not None else None, + source=clk, + tristate=get_iob_offline_signal()) if hasattr(pads, "mosi"): sdo_out_disable = Signal(attr={"iob"}, reset=1) self.sync += sdo_out_disable.eq( self.offline_next | self.half_duplex_next) - self.specials += Instance("IOBUFDS", - o_O=mosi, i_I=self.sdo, i_T=sdo_out_disable, - io_IO=pads.mosi, io_IOB=pads_n.mosi) + generate_io(pads.mosi, + pad_n=pads_n.mosi if pads_n is not None else None, + source=self.sdo, + sink=mosi, + tristate=sdo_out_disable) mosi_reg.attr.add("iob") if hasattr(pads, "miso"): - self.specials += Instance("IBUFDS", - o_O=miso, i_I=pads.miso, i_IB=pads_n.miso) + generate_io(pads.miso, + pad_n=pads_n.miso if pads_n is not None else None, + sink=miso) miso_reg.attr.add("iob")