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 ..lib.cdc import ResetSynchronizer
from ..lib import io
from ..build import *
@ -414,206 +415,162 @@ class LatticeICE40Platform(TemplatedPlatform):
return m
def should_skip_port_component(self, port, attrs, component):
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive for
# 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):
def _get_io_buffer_single(self, buffer, port, *, invert_lut=False):
def get_dff(domain, q, d):
for bit in range(len(d)):
m.submodules += Instance("SB_DFF",
i_C=clk,
i_C=ClockSignal(domain),
i_D=d[bit],
o_Q=q[bit])
def get_ineg(y, invert):
def get_inv(y, a):
if invert_lut:
a = Signal.like(y, name_suffix=f"_x{1 if invert else 0}")
for bit in range(len(y)):
for bit, inv in enumerate(port.invert):
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_I1=Const(0),
i_I2=Const(0),
i_I3=Const(0),
o_O=y[bit])
return a
elif invert:
a = Signal.like(y, name_suffix="_n")
m.d.comb += y.eq(~a)
return a
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):
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
m = Module()
if "GLOBAL" in attrs:
is_global_input = bool(attrs["GLOBAL"])
del attrs["GLOBAL"]
if isinstance(buffer, io.DDRBuffer):
if buffer.direction is not io.Direction.Output:
# 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:
is_global_input = False
assert not (is_global_input and i_invert)
if "i" in pin.dir:
if pin.xdr < 2:
pin_i = get_ineg(pin.i, i_invert)
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)
if buffer.direction is not io.Direction.Output:
i = Signal(len(port))
get_inv(buffer.i, i)
if buffer.direction is not io.Direction.Input:
o = Signal(len(port))
get_inv(o, buffer.o)
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", "PACKAGE_PIN", port[bit]),
*(("p", key, value) for key, value in attrs.items()),
("io", "PACKAGE_PIN", port.io[bit]),
*(("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
# 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
# from using an input clock.
i_type = 0b01 # PIN_INPUT
elif pin.xdr == 0:
elif isinstance(buffer, io.Buffer):
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
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
elif pin.xdr == 0 and pin.dir == "o":
o_type = 0b0110 # PIN_OUTPUT
elif pin.xdr == 0:
elif isinstance(buffer, io.Buffer):
o_type = 0b1010 # PIN_OUTPUT_TRISTATE
elif pin.xdr == 1 and pin.dir == "o":
o_type = 0b0101 # PIN_OUTPUT_REGISTERED
elif pin.xdr == 1:
io_args.append(("i", "D_OUT_0", o[bit]))
elif isinstance(buffer, io.FFBuffer):
o_type = 0b1101 # PIN_OUTPUT_REGISTERED_ENABLE_REGISTERED
elif pin.xdr == 2 and pin.dir == "o":
o_type = 0b0100 # PIN_OUTPUT_DDR
elif pin.xdr == 2:
io_args.append(("i", "OUTPUT_CLK", ClockSignal(buffer.o_domain)))
io_args.append(("i", "D_OUT_0", o[bit]))
elif isinstance(buffer, io.DDRBuffer):
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)))
if hasattr(pin, "i_clk"):
io_args.append(("i", "INPUT_CLK", pin.i_clk))
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 buffer.direction is not io.Direction.Input:
io_args.append(("i", "OUTPUT_ENABLE", buffer.oe))
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:
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
def get_output(self, pin, port, attrs, invert):
self._check_feature("single-ended output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
return m
def get_tristate(self, pin, port, attrs, invert):
self._check_feature("single-ended tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port.io, attrs, o_invert=invert)
return m
def get_input_output(self, pin, port, attrs, invert):
self._check_feature("single-ended input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
self._get_io_buffer(m, pin, port.io, attrs, i_invert=invert, o_invert=invert)
return m
def get_diff_input(self, pin, port, attrs, invert):
self._check_feature("differential input", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
# See comment in should_skip_port_component above.
self._get_io_buffer(m, pin, port.p, attrs, i_invert=invert)
return m
def get_diff_output(self, pin, port, attrs, invert):
self._check_feature("differential output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
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.
def get_io_buffer(self, buffer):
if not isinstance(buffer, (io.Buffer, io.FFBuffer, io.DDRBuffer)):
raise TypeError(f"Unknown IO buffer type {buffer!r}")
if isinstance(buffer.port, io.DifferentialPort):
port_p = io.SingleEndedPort(buffer.port.p, invert=buffer.port.invert,
direction=buffer.port.direction)
port_n = ~io.SingleEndedPort(buffer.port.n, invert=buffer.port.invert,
direction=buffer.port.direction)
if buffer.direction is io.Direction.Bidir:
# Tristate bidirectional buffers are not supported on iCE40 because it requires
# external termination, which is different for differential pins configured
# as inputs and outputs.
raise TypeError("iCE40 does not support bidirectional differential ports")
elif buffer.direction is io.Direction.Output:
m = Module()
invert_lut = isinstance(buffer, io.Buffer)
m.submodules.p = self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut)
m.submodules.n = self._get_io_buffer_single(buffer, port_n, invert_lut=invert_lut)
return m
elif buffer.direction is io.Direction.Input:
# On iCE40, a differential input is placed by only instantiating an SB_IO primitive
# for 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)
return self._get_io_buffer_single(buffer, port_p, invert_lut=invert_lut)
else:
assert False # :nocov:
elif isinstance(buffer.port, io.SingleEndedPort):
return self._get_io_buffer_single(buffer, buffer.port)
else:
raise TypeError(f"Unknown port type {buffer.port!r}")
# CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
# the necessary attributes; nextpnr-ice40 does not.