
Although a dir="oe" pin is generally equivalent to dir="io" pin with the i* signal(s) disconnected, they are not equivalent, because some pins may not be able to support input buffers at all, either because there are no input buffers, or because the input buffers are consumed by some other resource. E.g. this can happen on iCE40 when the input buffer is consumed by a PLL.
200 lines
7.5 KiB
Python
200 lines
7.5 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 -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, 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):
|
|
self._check_feature("single-ended input", pin, extras,
|
|
valid_xdrs=(0,), valid_extras=True)
|
|
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):
|
|
self._check_feature("single-ended output", pin, extras,
|
|
valid_xdrs=(0,), valid_extras=True)
|
|
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):
|
|
self._check_feature("single-ended tristate", pin, extras,
|
|
valid_xdrs=(0,), valid_extras=True)
|
|
return self._get_io_buffer(port, extras, lambda bit: [
|
|
# PIN_OUTPUT_TRISTATE|PIN_INPUT_REGISTERED
|
|
("p", "PIN_TYPE", 0b1010_00),
|
|
("i", "D_OUT_0", pin.o[bit]),
|
|
("i", "OUTPUT_ENABLE", pin.oe),
|
|
])
|
|
|
|
def get_input_output(self, pin, port, extras):
|
|
self._check_feature("single-ended input/output", pin, extras,
|
|
valid_xdrs=(0,), valid_extras=True)
|
|
return self._get_io_buffer(port, extras, lambda bit: [
|
|
# PIN_OUTPUT_TRISTATE|PIN_INPUT
|
|
("p", "PIN_TYPE", 0b1010_01),
|
|
("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)
|