
Right now the device name in the board file is just the option nextpnr uses, but that's overnormalized and doesn't quite match the chip names used elsewhere. It is even worse for ECP5 in terms of mismatch with chip names, and for ECP5 we need to support other toolchains as well, so let's handle this uniformly everywhere.
299 lines
12 KiB
Python
299 lines
12 KiB
Python
from abc import abstractproperty
|
|
|
|
from ..hdl import *
|
|
from ..build import *
|
|
|
|
|
|
__all__ = ["LatticeICE40Platform"]
|
|
|
|
|
|
class LatticeICE40Platform(TemplatedPlatform):
|
|
"""
|
|
Required tools:
|
|
* ``yosys``
|
|
* ``nextpnr-ice40``
|
|
* ``icepack``
|
|
|
|
Available overrides:
|
|
* ``verbose``: enables logging of informational messages to standard error.
|
|
* ``read_opts``: adds options for ``read`` Yosys command.
|
|
* ``synth_opts``: adds options for ``synth_ice40`` Yosys command.
|
|
* ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
|
|
* ``script_after_synth``: inserts commands after ``synth_ice40`` in Yosys script.
|
|
* ``yosys_opts``: adds extra options for Yosys.
|
|
* ``nextpnr_opts``: adds extra and overrides default options (``--placer heap``)
|
|
for nextpnr.
|
|
|
|
Build products:
|
|
* ``{{name}}.rpt``: Yosys log.
|
|
* ``{{name}}.json``: synthesized RTL.
|
|
* ``{{name}}.tim``: nextpnr log.
|
|
* ``{{name}}.asc``: ASCII bitstream.
|
|
* ``{{name}}.bin``: binary bitstream.
|
|
"""
|
|
|
|
device = abstractproperty()
|
|
package = abstractproperty()
|
|
|
|
_nextpnr_device_options = {
|
|
"iCE40LP384": "--lp384",
|
|
"iCE40LP1K": "--lp1k",
|
|
"iCE40LP4K": "--lp8k",
|
|
"iCE40LP8K": "--lp8k",
|
|
"iCE40HX1K": "--hx1k",
|
|
"iCE40HX4K": "--hx8k",
|
|
"iCE40HX8K": "--hx8k",
|
|
"iCE40UP5K": "--up5k",
|
|
"iCE5LP4K": "--u4k",
|
|
}
|
|
|
|
file_templates = {
|
|
**TemplatedPlatform.build_script_templates,
|
|
"{{name}}.il": r"""
|
|
# {{autogenerated}}
|
|
{{emit_design("rtlil")}}
|
|
""",
|
|
"{{name}}.ys": r"""
|
|
# {{autogenerated}}
|
|
{% for file in platform.extra_files %}
|
|
{% if file.endswith(".v") -%}
|
|
read_verilog {{get_override("read_opts")|join(" ")}} {{file}}
|
|
{% elif file.endswith(".sv") -%}
|
|
read_verilog -sv {{get_override("read_opts")|join(" ")}} {{file}}
|
|
{% endif %}
|
|
{% endfor %}
|
|
read_ilang {{name}}.il
|
|
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
|
|
synth_ice40 {{get_override("synth_opts")|join(" ")}} -top {{name}}
|
|
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
|
|
write_json {{name}}.json
|
|
""",
|
|
"{{name}}.pcf": r"""
|
|
# {{autogenerated}}
|
|
{% for port_name, pin_name, extras in platform.iter_port_constraints_bits() -%}
|
|
set_io {{port_name}} {{pin_name}}
|
|
{% endfor %}
|
|
""",
|
|
"{{name}}_pre_pack.py": r"""
|
|
# {{autogenerated}}
|
|
{% for port, frequency in platform.iter_clock_constraints() -%}
|
|
{# Clock in MHz #}
|
|
ctx.addClock("{{port}}", {{frequency/1000000}})
|
|
{% endfor%}
|
|
""",
|
|
}
|
|
command_templates = [
|
|
r"""
|
|
{{get_tool("yosys")}}
|
|
{{quiet("-q")}}
|
|
{{get_override("yosys_opts")|join(" ")}}
|
|
-l {{name}}.rpt
|
|
{{name}}.ys
|
|
""",
|
|
r"""
|
|
{{get_tool("nextpnr-ice40")}}
|
|
{{quiet("-q")}}
|
|
{{get_override("nextpnr_opts")|default(["--placer","heap"])|join(" ")}}
|
|
-l {{name}}.tim
|
|
{{platform._nextpnr_device_options[platform.device]}}
|
|
--package {{platform.package|lower}}
|
|
--json {{name}}.json
|
|
--pcf {{name}}.pcf
|
|
--pre-pack {{name}}_pre_pack.py
|
|
--asc {{name}}.asc
|
|
""",
|
|
r"""
|
|
{{get_tool("icepack")}}
|
|
{{verbose("-v")}}
|
|
{{name}}.asc
|
|
{{name}}.bin
|
|
"""
|
|
]
|
|
|
|
def iter_ports(self):
|
|
for res, pin, port in self._ports:
|
|
if isinstance(res.io[0], Pins):
|
|
yield port.io
|
|
elif isinstance(res.io[0], DiffPairs):
|
|
if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
|
|
yield port.p
|
|
else:
|
|
yield port.p
|
|
yield port.n
|
|
else:
|
|
assert False
|
|
|
|
def iter_port_constraints(self):
|
|
for res, pin, port in self._ports:
|
|
if isinstance(res.io[0], Pins):
|
|
yield port.io.name, list(res.io[0].map_names(self._mapping, res)), res.extras
|
|
elif isinstance(res.io[0], DiffPairs):
|
|
if res.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
|
|
yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
|
|
else:
|
|
yield port.p.name, list(res.io[0].p.map_names(self._mapping, res)), res.extras
|
|
yield port.n.name, list(res.io[0].n.map_names(self._mapping, res)), res.extras
|
|
else:
|
|
assert False
|
|
|
|
def _get_io_buffer(self, m, pin, port, extras, o_invert=None):
|
|
def _get_dff(clk, d, q):
|
|
m.submodules += Instance("$dff",
|
|
p_CLK_POLARITY=0,
|
|
p_WIDTH=len(d),
|
|
i_CLK=clk,
|
|
i_D=d,
|
|
o_Q=q)
|
|
|
|
def _get_inverter(a, invert):
|
|
if invert is None:
|
|
return a
|
|
else:
|
|
y = Signal.like(a, name="{}_x{}".format(a.name, 1 if invert else 0))
|
|
for bit in range(len(a)):
|
|
m.submodules += Instance("SB_LUT4",
|
|
p_LUT_INIT=0b01 if invert else 0b10,
|
|
i_I0=a[bit],
|
|
i_I1=Const(0),
|
|
i_I2=Const(0),
|
|
i_I3=Const(0),
|
|
o_O=y[bit])
|
|
return y
|
|
|
|
if "GLOBAL" in extras:
|
|
is_global_input = bool(extras["GLOBAL"])
|
|
del extras["GLOBAL"]
|
|
else:
|
|
is_global_input = False
|
|
|
|
if "o" in pin.dir:
|
|
if pin.xdr < 2:
|
|
pin_o = _get_inverter(pin.o, o_invert)
|
|
elif pin.xdr == 2:
|
|
pin_o0 = _get_inverter(pin.o0, o_invert)
|
|
pin_o1 = _get_inverter(pin.o1, o_invert)
|
|
|
|
if "i" in pin.dir and pin.xdr == 2:
|
|
i0_ff = Signal.like(pin.i0, name="{}_ff".format(pin.i0.name))
|
|
i1_ff = Signal.like(pin.i1, name="{}_ff".format(pin.i1.name))
|
|
_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="{}_ff".format(pin.o1.name))
|
|
_get_dff(pin.o_clk, pin_o1, o1_ff)
|
|
|
|
for bit in range(len(port)):
|
|
io_args = [
|
|
("io", "PACKAGE_PIN", port[bit]),
|
|
*(("p", key, value) for key, value in extras.items()),
|
|
]
|
|
|
|
if "i" not in pin.dir:
|
|
i_type = 0b00 # PIN_NO_INPUT aka PIN_INPUT_REGISTERED
|
|
elif pin.xdr == 0:
|
|
i_type = 0b01 # PIN_INPUT
|
|
elif pin.xdr > 0:
|
|
i_type = 0b00 # PIN_INPUT_REGISTERED
|
|
if "o" not in pin.dir:
|
|
o_type = 0b0000 # PIN_NO_OUTPUT
|
|
elif pin.xdr == 0 and pin.dir == "o":
|
|
o_type = 0b0110 # PIN_OUTPUT
|
|
elif pin.xdr == 0:
|
|
o_type = 0b1010 # PIN_OUTPUT_TRISTATE
|
|
elif pin.xdr == 1 and pin.dir == "o":
|
|
o_type = 0b0101 # PIN_OUTPUT_REGISTERED
|
|
elif pin.xdr == 1:
|
|
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:
|
|
o_type = 0b1100 # PIN_OUTPUT_DDR_ENABLE_REGISTERED
|
|
io_args.append(("p", "PIN_TYPE", (o_type << 2) | i_type))
|
|
|
|
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))
|
|
io_args.append(("o", "D_IN_1", i1_ff))
|
|
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))
|
|
|
|
if pin.dir in ("oe", "io"):
|
|
io_args.append(("i", "OUTPUT_ENABLE", pin.oe))
|
|
|
|
if is_global_input:
|
|
m.submodules += Instance("SB_GB_IO", *io_args)
|
|
else:
|
|
m.submodules += Instance("SB_IO", *io_args)
|
|
|
|
def get_input(self, pin, port, extras):
|
|
self._check_feature("single-ended input", pin, extras,
|
|
valid_xdrs=(0, 1, 2), valid_extras=True)
|
|
m = Module()
|
|
self._get_io_buffer(m, pin, port, extras)
|
|
return m
|
|
|
|
def get_output(self, pin, port, extras):
|
|
self._check_feature("single-ended output", pin, extras,
|
|
valid_xdrs=(0, 1, 2), valid_extras=True)
|
|
m = Module()
|
|
self._get_io_buffer(m, pin, port, extras)
|
|
return m
|
|
|
|
def get_tristate(self, pin, port, extras):
|
|
self._check_feature("single-ended tristate", pin, extras,
|
|
valid_xdrs=(0, 1, 2), valid_extras=True)
|
|
m = Module()
|
|
self._get_io_buffer(m, pin, port, extras)
|
|
return m
|
|
|
|
def get_input_output(self, pin, port, extras):
|
|
self._check_feature("single-ended input/output", pin, extras,
|
|
valid_xdrs=(0, 1, 2), valid_extras=True)
|
|
m = Module()
|
|
self._get_io_buffer(m, pin, port, extras)
|
|
return m
|
|
|
|
def get_diff_input(self, pin, p_port, n_port, extras):
|
|
self._check_feature("differential input", pin, extras,
|
|
valid_xdrs=(0, 1, 2), valid_extras=True)
|
|
# 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)
|
|
m = Module()
|
|
self._get_io_buffer(m, pin, p_port, extras)
|
|
return m
|
|
|
|
def get_diff_output(self, pin, p_port, n_port, extras):
|
|
self._check_feature("differential output", pin, extras,
|
|
valid_xdrs=(0, 1, 2), valid_extras=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, p_port, extras, o_invert=False)
|
|
self._get_io_buffer(m, pin, n_port, extras, o_invert=True)
|
|
return m
|
|
|
|
# Tristate and bidirectional buffers are not supported on iCE40 because it requires external
|
|
# termination, which is incompatible for input and output differential I/Os.
|