vendor._lattice_ice40: implement lib.io buffer primitives.

This commit is contained in:
Wanda 2024-04-04 22:07:09 +02:00 committed by Catherine
parent 4e3550db43
commit 6857daff54

View file

@ -2,6 +2,7 @@ from abc import abstractmethod
from ..hdl import * from ..hdl import *
from ..lib.cdc import ResetSynchronizer from ..lib.cdc import ResetSynchronizer
from ..lib import io
from ..build import * from ..build import *
@ -414,206 +415,162 @@ class LatticeICE40Platform(TemplatedPlatform):
return m return m
def should_skip_port_component(self, port, attrs, component): def _get_io_buffer_single(self, buffer, port, *, invert_lut=False):
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive for def get_dff(domain, q, d):
# the pin with z=0, which is the non-inverting pin. The pinout unfortunately differs
# between LP/HX and UP series:
# * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
# * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
if attrs.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT" and component == "n":
return True
return False
def _get_io_buffer(self, m, pin, port, attrs, *, i_invert=False, o_invert=False,
invert_lut=False):
def get_dff(clk, d, q):
for bit in range(len(d)): for bit in range(len(d)):
m.submodules += Instance("SB_DFF", m.submodules += Instance("SB_DFF",
i_C=clk, i_C=ClockSignal(domain),
i_D=d[bit], i_D=d[bit],
o_Q=q[bit]) o_Q=q[bit])
def get_ineg(y, invert): def get_inv(y, a):
if invert_lut: if invert_lut:
a = Signal.like(y, name_suffix=f"_x{1 if invert else 0}") for bit, inv in enumerate(port.invert):
for bit in range(len(y)):
m.submodules += Instance("SB_LUT4", m.submodules += Instance("SB_LUT4",
p_LUT_INIT=Const(0b01 if invert else 0b10, 16), p_LUT_INIT=Const(0b01 if inv else 0b10, 16),
i_I0=a[bit], i_I0=a[bit],
i_I1=Const(0), i_I1=Const(0),
i_I2=Const(0), i_I2=Const(0),
i_I3=Const(0), i_I3=Const(0),
o_O=y[bit]) o_O=y[bit])
return a
elif invert:
a = Signal.like(y, name_suffix="_n")
m.d.comb += y.eq(~a)
return a
else: else:
return y mask = sum(int(inv) << bit for bit, inv in enumerate(port.invert))
if mask == 0:
m.d.comb += y.eq(a)
elif mask == ((1 << len(port)) - 1):
m.d.comb += y.eq(~a)
else:
m.d.comb += y.eq(a ^ mask)
def get_oneg(a, invert): m = Module()
if invert_lut:
y = Signal.like(a, name_suffix=f"_x{1 if invert else 0}")
for bit in range(len(a)):
m.submodules += Instance("SB_LUT4",
p_LUT_INIT=Const(0b01 if invert else 0b10, 16),
i_I0=a[bit],
i_I1=Const(0),
i_I2=Const(0),
i_I3=Const(0),
o_O=y[bit])
return y
elif invert:
y = Signal.like(a, name_suffix="_n")
m.d.comb += y.eq(~a)
return y
else:
return a
if "GLOBAL" in attrs: if isinstance(buffer, io.DDRBuffer):
is_global_input = bool(attrs["GLOBAL"]) if buffer.direction is not io.Direction.Output:
del attrs["GLOBAL"] # Re-register both inputs before they enter fabric. This increases hold time
# to an entire cycle, and adds one cycle of latency.
i0 = Signal(len(port))
i1 = Signal(len(port))
i0_neg = Signal(len(port))
i1_neg = Signal(len(port))
get_inv(i0_neg, i0)
get_inv(i1_neg, i1)
get_dff(buffer.i_domain, buffer.i[0], i0_neg)
get_dff(buffer.i_domain, buffer.i[1], i1_neg)
if buffer.direction is not io.Direction.Input:
# Re-register negedge output after it leaves fabric. This increases setup time
# to an entire cycle, and doesn't add latency.
o0 = Signal(len(port))
o1 = Signal(len(port))
o1_ff = Signal(len(port))
get_dff(buffer.o_domain, o1_ff, buffer.o[1])
get_inv(o0, buffer.o[0])
get_inv(o1, o1_ff)
else: else:
is_global_input = False if buffer.direction is not io.Direction.Output:
assert not (is_global_input and i_invert) i = Signal(len(port))
get_inv(buffer.i, i)
if "i" in pin.dir: if buffer.direction is not io.Direction.Input:
if pin.xdr < 2: o = Signal(len(port))
pin_i = get_ineg(pin.i, i_invert) get_inv(o, buffer.o)
elif pin.xdr == 2:
pin_i0 = get_ineg(pin.i0, i_invert)
pin_i1 = get_ineg(pin.i1, i_invert)
if "o" in pin.dir:
if pin.xdr < 2:
pin_o = get_oneg(pin.o, o_invert)
elif pin.xdr == 2:
pin_o0 = get_oneg(pin.o0, o_invert)
pin_o1 = get_oneg(pin.o1, o_invert)
if "i" in pin.dir and pin.xdr == 2:
i0_ff = Signal.like(pin_i0, name_suffix="_ff")
i1_ff = Signal.like(pin_i1, name_suffix="_ff")
get_dff(pin.i_clk, i0_ff, pin_i0)
get_dff(pin.i_clk, i1_ff, pin_i1)
if "o" in pin.dir and pin.xdr == 2:
o1_ff = Signal.like(pin_o1, name_suffix="_ff")
get_dff(pin.o_clk, pin_o1, o1_ff)
for bit in range(len(port)): for bit in range(len(port)):
attrs = port.io.metadata[bit].attrs
is_global_input = bool(attrs.get("GLOBAL", False))
if buffer.direction is io.Direction.Output:
is_global_input = False
if is_global_input:
if port.invert[bit]:
raise ValueError("iCE40 global input buffer doesn't support inversion")
if not isinstance(buffer, io.Buffer):
raise ValueError("iCE40 global input buffer cannot be registered")
io_args = [ io_args = [
("io", "PACKAGE_PIN", port[bit]), ("io", "PACKAGE_PIN", port.io[bit]),
*(("p", key, value) for key, value in attrs.items()), *(("p", key, value) for key, value in attrs.items() if key != "GLOBAL"),
] ]
if "i" not in pin.dir: if buffer.direction is io.Direction.Output:
# If no input pin is requested, it is important to use a non-registered input pin # If no input pin is requested, it is important to use a non-registered input pin
# type, because an output-only pin would not have an input clock, and if its input # type, because an output-only pin would not have an input clock, and if its input
# is configured as registered, this would prevent a co-located input-capable pin # is configured as registered, this would prevent a co-located input-capable pin
# from using an input clock. # from using an input clock.
i_type = 0b01 # PIN_INPUT i_type = 0b01 # PIN_INPUT
elif pin.xdr == 0: elif isinstance(buffer, io.Buffer):
i_type = 0b01 # PIN_INPUT i_type = 0b01 # PIN_INPUT
elif pin.xdr > 0: if is_global_input:
io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", i[bit]))
else:
io_args.append(("o", "D_IN_0", i[bit]))
elif isinstance(buffer, io.FFBuffer):
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
if "o" not in pin.dir: io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
io_args.append(("o", "D_IN_0", i[bit]))
elif isinstance(buffer, io.DDRBuffer):
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
io_args.append(("i", "INPUT_CLK", ClockSignal(buffer.i_domain)))
io_args.append(("o", "D_IN_0", i0[bit]))
io_args.append(("o", "D_IN_1", i1[bit]))
if buffer.direction is io.Direction.Input:
o_type = 0b0000 # PIN_NO_OUTPUT o_type = 0b0000 # PIN_NO_OUTPUT
elif pin.xdr == 0 and pin.dir == "o": elif isinstance(buffer, io.Buffer):
o_type = 0b0110 # PIN_OUTPUT
elif pin.xdr == 0:
o_type = 0b1010 # PIN_OUTPUT_TRISTATE o_type = 0b1010 # PIN_OUTPUT_TRISTATE
elif pin.xdr == 1 and pin.dir == "o": io_args.append(("i", "D_OUT_0", o[bit]))
o_type = 0b0101 # PIN_OUTPUT_REGISTERED elif isinstance(buffer, io.FFBuffer):
elif pin.xdr == 1:
o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
elif pin.xdr == 2 and pin.dir == "o": io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
o_type = 0b0100 # PIN_OUTPUT_DDR io_args.append(("i", "D_OUT_0", o[bit]))
elif pin.xdr == 2: elif isinstance(buffer, io.DDRBuffer):
o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
io_args.append(("i", "D_OUT_0", o0[bit]))
io_args.append(("i", "D_OUT_1", o1[bit]))
io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6))) io_args.append(("p", "PIN_TYPE", C((o_type << 2) | i_type, 6)))
if hasattr(pin, "i_clk"): if buffer.direction is not io.Direction.Input:
io_args.append(("i", "INPUT_CLK", pin.i_clk)) io_args.append(("i", "OUTPUT_ENABLE", buffer.oe))
if hasattr(pin, "o_clk"):
io_args.append(("i", "OUTPUT_CLK", pin.o_clk))
if "i" in pin.dir:
if pin.xdr == 0 and is_global_input:
io_args.append(("o", "GLOBAL_BUFFER_OUTPUT", pin.i[bit]))
elif pin.xdr < 2:
io_args.append(("o", "D_IN_0", pin_i[bit]))
elif pin.xdr == 2:
# Re-register both inputs before they enter fabric. This increases hold time
# to an entire cycle, and adds one cycle of latency.
io_args.append(("o", "D_IN_0", i0_ff[bit]))
io_args.append(("o", "D_IN_1", i1_ff[bit]))
if "o" in pin.dir:
if pin.xdr < 2:
io_args.append(("i", "D_OUT_0", pin_o[bit]))
elif pin.xdr == 2:
# Re-register negedge output after it leaves fabric. This increases setup time
# to an entire cycle, and doesn't add latency.
io_args.append(("i", "D_OUT_0", pin_o0[bit]))
io_args.append(("i", "D_OUT_1", o1_ff[bit]))
if pin.dir in ("oe", "io"):
io_args.append(("i", "OUTPUT_ENABLE", pin.oe))
if is_global_input: if is_global_input:
m.submodules[f"{pin.name}_{bit}"] = Instance("SB_GB_IO", *io_args) m.submodules[f"buf{bit}"] = Instance("SB_GB_IO", *io_args)
else: else:
m.submodules[f"{pin.name}_{bit}"] = Instance("SB_IO", *io_args) m.submodules[f"buf{bit}"] = Instance("SB_IO", *io_args)
def get_input(self, pin, port, attrs, invert):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert)
return m return m
def get_output(self, pin, port, attrs, invert): def get_io_buffer(self, buffer):
self._check_feature("single-ended output", pin, attrs, if not isinstance(buffer, (io.Buffer, io.FFBuffer, io.DDRBuffer)):
valid_xdrs=(0, 1, 2), valid_attrs=True) raise TypeError(f"Unknown IO buffer type {buffer!r}")
m = Module() if isinstance(buffer.port, io.DifferentialPort):
self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) port_p = io.SingleEndedPort(buffer.port.p, invert=buffer.port.invert,
return m direction=buffer.port.direction)
port_n = ~io.SingleEndedPort(buffer.port.n, invert=buffer.port.invert,
def get_tristate(self, pin, port, attrs, invert): direction=buffer.port.direction)
self._check_feature("single-ended tristate", pin, attrs, if buffer.direction is io.Direction.Bidir:
valid_xdrs=(0, 1, 2), valid_attrs=True) # Tristate bidirectional buffers are not supported on iCE40 because it requires
m = Module() # external termination, which is different for differential pins configured
self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert) # as inputs and outputs.
return m raise TypeError("iCE40 does not support bidirectional differential ports")
elif buffer.direction is io.Direction.Output:
def get_input_output(self, pin, port, attrs, invert): m = Module()
self._check_feature("single-ended input/output", pin, attrs, invert_lut = isinstance(buffer, io.Buffer)
valid_xdrs=(0, 1, 2), valid_attrs=True) m.submodules.p = self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut)
m = Module() m.submodules.n = self._get_io_buffer_single(buffer, port_n, invert_lut=invert_lut)
self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert) return m
return m elif buffer.direction is io.Direction.Input:
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive
def get_diff_input(self, pin, port, attrs, invert): # for the pin with z=0, which is the non-inverting pin. The pinout unfortunately
self._check_feature("differential input", pin, attrs, # differs between LP/HX and UP series:
valid_xdrs=(0, 1, 2), valid_attrs=True) # * for LP/HX, z=0 is DPxxB (B is non-inverting, A is inverting)
m = Module() # * for UP, z=0 is IOB_xxA (A is non-inverting, B is inverting)
# See comment in should_skip_port_component above. return self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut)
self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert) else:
return m assert False # :nocov:
elif isinstance(buffer.port, io.SingleEndedPort):
def get_diff_output(self, pin, port, attrs, invert): return self._get_io_buffer_single(buffer, buffer.port)
self._check_feature("differential output", pin, attrs, else:
valid_xdrs=(0, 1, 2), valid_attrs=True) raise TypeError(f"Unknown port type {buffer.port!r}")
m = Module()
# Note that the non-inverting output pin is not driven the same way as a regular
# output pin. The inverter introduces a delay, so for a non-inverting output pin,
# an identical delay is introduced by instantiating a LUT. This makes the waveform
# perfectly symmetric in the xdr=0 case.
self._get_io_buffer(m, pin, port.p, attrs, o_invert= invert, invert_lut=True)
self._get_io_buffer(m, pin, port.n, attrs, o_invert=not invert, invert_lut=True)
return m
# Tristate bidirectional buffers are not supported on iCE40 because it requires external
# termination, which is different for differential pins configured as inputs and outputs.
# CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports # CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
# the necessary attributes; nextpnr-ice40 does not. # the necessary attributes; nextpnr-ice40 does not.