amaranth/nmigen/vendor/fpga/lattice_ice40.py
whitequark dc17d06fe9 vendor.fpga.lattice_ice40: instantiate SB_IO and apply extras.
The PULLUP and PULLUP_RESISTOR extras are representable in the PCF
file. The IO_STANDARD extra, however, can only be an SB_IO parameter.
2019-06-03 02:51:59 +00:00

183 lines
6.7 KiB
Python

from abc import abstractproperty
import os
import subprocess
import tempfile
from ...hdl.ast import *
from ...hdl.dsl import *
from ...hdl.ir import *
from ...build import *
__all__ = ["LatticeICE40Platform", "IceStormProgrammerMixin", "IceBurnProgrammerMixin", "TinyProgrammerMixin"]
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 {{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, 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 _get_io_buffer(self, port, extras, fn):
m = Module()
for bit in range(len(port)):
m.submodules += Instance("SB_IO",
("io", "PACKAGE_PIN", port[bit]),
*fn(bit),
*(("p", key, value) for key, value in extras.items()))
return m
def get_input(self, pin, port, extras):
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_NO_OUTPUT|PIN_INPUT
("p", "PIN_TYPE", 0b0000_01),
("o", "D_IN_0", pin.i[bit]),
])
def get_output(self, pin, port, extras):
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_OUTPUT|PIN_INPUT_REGISTERED
("p", "PIN_TYPE", 0b0110_00),
("i", "D_OUT_0", pin.o[bit]),
])
def get_tristate(self, pin, port, extras):
return self._get_io_buffer(port, extras, lambda bit: [
# PIN_OUTPUT_TRISTATE|PIN_INPUT_REGISTERED
("p", "PIN_TYPE", 0b1010_00),
("o", "D_IN_0", pin.i[bit]),
("i", "D_OUT_0", pin.o[bit]),
("i", "OUTPUT_ENABLE", pin.oe),
])
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:
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:
bitstream_file.write(bitstream)
subprocess.run([iceburn, "-evw", bitstream_file.name], check=True)
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:
bitstream_file.write(bitstream)
subprocess.run([tinyprog, *options, bitstream_file.name], check=True)