amaranth/nmigen/vendor/fpga/lattice_ice40.py

296 lines
12 KiB
Python
Raw Normal View History

2019-06-01 10:46:50 -06:00
from abc import abstractproperty
import os
import subprocess
import tempfile
from ...hdl.ast import *
from ...hdl.dsl import *
from ...hdl.ir import *
2019-06-01 10:46:50 -06:00
from ...build import *
__all__ = ["LatticeICE40Platform",
"IceStormProgrammerMixin", "IceBurnProgrammerMixin", "TinyProgrammerMixin"]
2019-06-01 10:46:50 -06:00
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``: overrides default options (``-q``) for Yosys.
* ``nextpnr_opts``: overrides default options (``-q --placer heap``).
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()
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}}
2019-06-01 10:46:50 -06:00
{% 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, pins, extra in platform.iter_port_constraints() %}
{% if pins|count > 1 %}
{% for bit in range -%}
set_io {{port}}[{{bit}}] {{pins[bit]}}
{% endfor %}
{% else -%}
set_io {{port}} {{pins[0]}}
{% endif %}
{% 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.device}}
--package {{platform.package}}
--json {{name}}.json
--pcf {{name}}.pcf
--pre-pack {{name}}_pre_pack.py
--asc {{name}}.asc
""",
r"""
{{get_tool("icepack")}}
{{name}}.asc
{{name}}.bin
"""
]
def iter_ports(self):
for resource, pin, port in self._ports:
if isinstance(resource.io[0], Pins):
yield port.io
elif isinstance(resource.io[0], DiffPairs):
if resource.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 resource, pin, port in self._ports:
if isinstance(resource.io[0], Pins):
yield port.io.name, resource.io[0].names, resource.extras
elif isinstance(resource.io[0], DiffPairs):
if resource.extras.get("IO_STANDARD", "SB_LVCMOS") == "SB_LVDS_INPUT":
yield port.p.name, resource.io[0].p.names, resource.extras
else:
yield port.p.name, resource.io[0].p.names, resource.extras
yield port.n.name, resource.io[0].n.names, resource.extras
else:
assert False
def _get_dff(self, clk, d, q):
return Instance("$dff",
p_CLK_POLARITY=0,
p_WIDTH=len(d),
i_CLK=clk,
i_D=d,
o_Q=q)
def _get_io_buffer(self, pin, port, extras):
m = Module()
if "GLOBAL" in extras:
is_global_input = bool(extras["GLOBAL"])
del extras["GLOBAL"]
else:
is_global_input = False
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))
m.submodules += self._get_dff(pin.i_clk, i0_ff, pin.i0)
m.submodules += self._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))
m.submodules += self._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)
return m
def get_input(self, pin, port, extras):
self._check_feature("single-ended input", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)
def get_output(self, pin, port, extras):
self._check_feature("single-ended output", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)
def get_tristate(self, pin, port, extras):
self._check_feature("single-ended tristate", pin, extras,
valid_xdrs=(0, 1, 2), valid_extras=True)
return self._get_io_buffer(pin, port, extras)
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)
return self._get_io_buffer(pin, port, extras)
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)
return self._get_io_buffer(pin, p_port, extras)
2019-06-01 10:46:50 -06:00
class IceStormProgrammerMixin:
def toolchain_program(self, products, name, *, mode=None):
if mode is None and hasattr(self, "prog_mode"):
mode = self.prog_mode
if mode not in ("sram", "flash"):
raise ValueError("iceprog mode must be one of \"sram\" or \"flash\", not {!r}; "
"specify it using .build(..., program_opts={\"mode\": \"<mode>\"})"
.format(mode))
iceprog = os.environ.get("ICEPROG", "iceprog")
bitstream = products.get("{}.bin".format(name))
if mode == "sram":
options = ["-S"]
if mode == "flash":
options = []
with tempfile.NamedTemporaryFile(prefix="nmigen_iceprog_",
suffix=".bin") as bitstream_file:
2019-06-01 10:46:50 -06:00
bitstream_file.write(bitstream)
subprocess.run([iceprog, *options, bitstream_file.name], check=True)
class IceBurnProgrammerMixin:
def toolchain_program(self, products, name):
iceburn = os.environ.get("ICEBURN", "iCEburn")
bitstream = products.get("{}.bin".format(name))
with tempfile.NamedTemporaryFile(prefix="nmigen_iceburn_",
suffix=".bin") as bitstream_file:
2019-06-01 10:46:50 -06:00
bitstream_file.write(bitstream)
subprocess.run([iceburn, "-evw", bitstream_file.name], check=True)
2019-06-01 19:20:09 -06:00
class TinyProgrammerMixin:
def toolchain_program(self, products, name):
tinyprog = os.environ.get("TINYPROG", "tinyprog")
options = ["-p"]
bitstream = products.get("{}.bin".format(name))
with tempfile.NamedTemporaryFile(prefix="nmigen_tinyprog_",
suffix=".bin") as bitstream_file:
2019-06-01 19:20:09 -06:00
bitstream_file.write(bitstream)
subprocess.run([tinyprog, *options, bitstream_file.name], check=True)