Implement RFC 18: Reorganize vendor platforms

This commit is contained in:
Catherine 2023-08-31 23:25:38 +00:00
parent 88cbf30128
commit 796068a192
23 changed files with 4447 additions and 4334 deletions

View file

@ -0,0 +1,44 @@
# The machinery in this module is PEP 562 compliant.
# See https://peps.python.org/pep-0562/ for details.
# Keep this list sorted alphabetically.
__all__ = [
"GowinPlatform",
"IntelPlatform",
"LatticeECP5Platform",
"LatticeICE40Platform",
"LatticeMachXO2Platform",
"LatticeMachXO3LPlatform",
"QuicklogicPlatform",
"XilinxPlatform",
]
def __dir__():
return list({*globals(), *__all__})
def __getattr__(name):
if name == "GowinPlatform":
from ._gowin import GowinPlatform
return GowinPlatform
if name == "IntelPlatform":
from ._intel import IntelPlatform
return IntelPlatform
if name == "LatticeECP5Platform":
from ._lattice_ecp5 import LatticeECP5Platform
return LatticeECP5Platform
if name == "LatticeICE40Platform":
from ._lattice_ice40 import LatticeICE40Platform
return LatticeICE40Platform
if name in ("LatticeMachXO2Platform", "LatticeMachXO3LPlatform"):
from ._lattice_machxo_2_3l import LatticeMachXO2Or3LPlatform
return LatticeMachXO2Or3LPlatform
if name == "QuicklogicPlatform":
from ._quicklogic import QuicklogicPlatform
return QuicklogicPlatform
if name == "XilinxPlatform":
from ._xilinx import XilinxPlatform
return XilinxPlatform
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

603
amaranth/vendor/_gowin.py Normal file
View file

@ -0,0 +1,603 @@
from abc import abstractproperty
from fractions import Fraction
import re
from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *
# Acknowledgments:
# Parts of this file originate from https://github.com/tcjie/Gowin
class GowinPlatform(TemplatedPlatform):
"""
.. rubric:: Apicula toolchain
Required tools:
* ``yosys``
* ``nextpnr-gowin``
* ``gowin_pack``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_APICULA``, if present.
Build products:
* ``{{name}}.fs``: binary bitstream.
.. rubric:: Gowin toolchain
Required tools:
* ``gw_sh``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_GOWIN``, if present.
Build products:
* ``{{name}}.fs``: binary bitstream.
"""
toolchain = None # selected when creating platform
part = abstractproperty()
family = abstractproperty()
def parse_part(self):
# These regular expressions match all >900 parts of Gowin device_info.csv
reg_series = r"(GW[12]{1}[AN]{1}[EFNRSZ]{0,3})-"
reg_voltage = r"(ZV|EV|LV|LX|UV|UX)"
reg_size = r"(1|2|4|9|18|55)"
reg_subseries = r"(?:(B|C|S|X|P5)?)"
reg_package = r"((?:PG|UG|EQ|LQ|MG|M|QN|CS|FN)(?:\d+)(?:P?)(?:A|E|M|CF|C|D|G|H|F|S|T|U|X)?)"
reg_speed = r"((?:C\d{1}/I\d{1})|ES|A\d{1}|I\d{1})"
match = re.match(reg_series+reg_voltage+reg_size+reg_subseries+reg_package+reg_speed+"$",
self.part)
if not match:
raise ValueError("Supplied part name is invalid")
self.series = match.group(1)
self.voltage = match.group(2)
self.size = match.group(3)
self.subseries = match.group(4) or ""
self.package = match.group(5)
self.speed = match.group(6)
match = re.match(reg_series+reg_size+reg_subseries+"$", self.family)
if not match:
raise ValueError("Supplied device family name is invalid")
self.series_f = match.group(1)
self.size_f = match.group(2)
self.subseries_f = match.group(3) or ""
# subseries_f is usually more reliable than subseries.
if self.series != self.series_f:
raise ValueError("Series extracted from supplied part name does not match "
"supplied family series")
if self.size != self.size_f:
raise ValueError("Size extracted from supplied part name does not match "
"supplied family size")
# _chipdb_device is tied to available chipdb-*.bin files of nextpnr-gowin
@property
def _chipdb_device(self):
# GW1NR series does not have its own chipdb file, but works with GW1N
if self.series == "GW1NR":
return "GW1N-{}{}".format(self.size, self.subseries_f)
return self.family
_dev_osc_mapping = {
"GW1N-1" : "OSCH",
"GW1N-1P5" : "OSCO",
"GW1N-1P5B" : "OSCO",
"GW1N-1S" : "OSCH",
"GW1N-2" : "OSCO",
"GW1N-2B" : "OSCO",
"GW1N-4" : "OSC",
"GW1N-4B" : "OSC",
"GW1N-9" : "OSC",
"GW1N-9C" : "OSC",
"GW1NR-1" : "OSCH",
"GW1NR-2" : "OSCO",
"GW1NR-2B" : "OSCO",
"GW1NR-4" : "OSC",
"GW1NR-4B" : "OSC",
"GW1NR-9" : "OSC",
"GW1NR-9C" : "OSC",
"GW1NRF-4B" : "OSC",
"GW1NS-2" : "OSCF",
"GW1NS-2C" : "OSCF",
"GW1NS-4" : "OSCZ",
"GW1NS-4C" : "OSCZ",
"GW1NSE-2C" : "OSCF",
"GW1NSER-4C" : "OSCZ",
"GW1NSR-2" : "OSCF",
"GW1NSR-2C" : "OSCF",
"GW1NSR-4" : "OSCZ",
"GW1NSR-4C" : "OSCZ",
"GW1NZ-1" : "OSCZ",
"GW1NZ-1C" : "OSCZ",
"GW2A-18" : "OSC",
"GW2A-18C" : "OSC",
"GW2A-55" : "OSC",
"GW2A-55C" : "OSC",
"GW2AN-18X" : "OSCW",
"GW2AN-55C" : "OSC",
"GW2AN-9X" : "OSCW",
"GW2ANR-18C" : "OSC",
"GW2AR-18" : "OSC",
"GW2AR-18C" : "OSC"
}
@property
def _osc_type(self):
if self.family in self._dev_osc_mapping:
return self._dev_osc_mapping[self.family]
raise NotImplementedError("Device family {} does not have an assigned oscillator type"
.format(self.family))
@property
def _osc_base_freq(self):
osc = self._osc_type
if osc == "OSC":
if self.speed == 4 and self.subseries_f in ("B", "D"):
return 210_000_000
else:
return 250_000_000
elif osc in ("OSCZ", "OSCO"):
if self.series == "GW1NSR" and self.speed == "C7/I6":
return 260_000_000
else:
return 250_000_000
elif osc in ("OSCF", "OSCH"):
return 240_000_000
elif osc == "OSCW":
return 200_000_000
else:
assert False
@property
def _osc_div(self):
div_min = 2
div_max = 128
div_step = 2
div_frac = Fraction(self._osc_base_freq, self.osc_frequency)
if div_frac.denominator != 1 or div_frac not in range(div_min, div_max, div_step):
raise ValueError(
"On-chip oscillator frequency (platform.osc_frequency) must be chosen such that "
"the oscillator divider, calculated as ({}/{}), is an integer between {} and {} in "
"steps of {}"
.format(div_frac.numerator, div_frac.denominator, div_min, div_max, div_step))
return div_frac.numerator
# Common templates
_common_file_templates = {
"{{name}}.cst": r"""
// {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
IO_LOC "{{port_name}}" {{pin_name}};
{% for attr_name, attr_value in attrs.items() -%}
IO_PORT "{{port_name}}" {{attr_name}}={{attr_value}};
{% endfor %}
{% endfor %}
""",
}
# Apicula templates
_apicula_required_tools = [
"yosys",
"nextpnr-gowin",
"gowin_pack"
]
_apicula_file_templates = {
**TemplatedPlatform.build_script_templates,
**_common_file_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_gowin {{get_override("synth_opts")|options}} -top {{name}} -json {{name}}.syn.json
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
""",
}
_apicula_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-gowin")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
--device {{platform.part}}
--family {{platform._chipdb_device}}
--json {{name}}.syn.json
--cst {{name}}.cst
--write {{name}}.pnr.json
""",
r"""
{{invoke_tool("gowin_pack")}}
-d {{platform._chipdb_device}}
-o {{name}}.fs
{{get_override("gowin_pack_opts")|options}}
{{name}}.pnr.json
"""
]
# Vendor toolchain templates
_gowin_required_tools = ["gw_sh"]
_gowin_file_templates = {
**TemplatedPlatform.build_script_templates,
**_common_file_templates,
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.tcl": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v",".sv",".vhd",".vhdl") -%}
add_file {{file}}
{% endfor %}
add_file -type verilog {{name}}.v
add_file -type cst {{name}}.cst
add_file -type sdc {{name}}.sdc
set_device -name {{platform.family}} {{platform.part}}
set_option -verilog_std v2001 -print_all_synthesis_warning 1 -show_all_warn 1
{{get_override("add_options")|default("# (add_options placeholder)")}}
run all
file delete -force {{name}}.fs
file copy -force impl/pnr/project.fs {{name}}.fs
""",
"{{name}}.sdc": r"""
// {{autogenerated}}
{% for net_signal,port_signal,frequency in platform.iter_clock_constraints() -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
_gowin_command_templates = [
r"""
{{invoke_tool("gw_sh")}}
{{name}}.tcl
"""
]
def __init__(self, *, toolchain="Apicula"):
super().__init__()
assert toolchain in ("Apicula", "Gowin")
self.toolchain = toolchain
self.parse_part()
@property
def required_tools(self):
if self.toolchain == "Apicula":
return self._apicula_required_tools
elif self.toolchain == "Gowin":
return self._gowin_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "Apicula":
return self._apicula_file_templates
elif self.toolchain == "Gowin":
return self._gowin_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "Apicula":
return self._apicula_command_templates
elif self.toolchain == "Gowin":
return self._gowin_command_templates
assert False
def add_clock_constraint(self, clock, frequency):
super().add_clock_constraint(clock, frequency)
clock.attrs["keep"] = "true"
@property
def default_clk_constraint(self):
if self.default_clk == "OSC":
if not hasattr(self, "osc_frequency"):
raise AttributeError(
"Using the on-chip oscillator as the default clock source requires "
"the platform.osc_frequency attribute to be set")
return Clock(self.osc_frequency)
# Use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
if name == "sync" and self.default_clk is not None:
m = Module()
if self.default_clk == "OSC":
clk_i = Signal()
if self._osc_type == "OSCZ":
m.submodules += Instance(self._osc_type,
p_FREQ_DIV=self._osc_div,
i_OSCEN=Const(1),
o_OSCOUT=clk_i)
elif self._osc_type == "OSCO":
# TODO: Make use of regulator configurable
m.submodules += Instance(self._osc_type,
p_REGULATOR_EN=Const(1),
p_FREQ_DIV=self._osc_div,
i_OSCEN=Const(1),
o_OSCOUT=clk_i)
elif self._osc_type == "OSCF":
m.submodules += Instance(self._osc_type,
p_FREQ_DIV=self._osc_div,
o_OSCOUT30M=None,
o_OSCOUT=clk_i)
else:
m.submodules += Instance(self._osc_type,
p_FREQ_DIV=self._osc_div,
o_OSCOUT=clk_i)
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
m.domains += ClockDomain("sync")
m.d.comb += ClockSignal("sync").eq(clk_i)
return m
def _get_xdr_buffer(self, m, pin, i_invert=False, o_invert=False):
def get_ireg(clk,d,q):
for bit in range(len(q)):
m.submodules += Instance("DFF",
i_CLK=clk,
i_D=d[bit],
o_Q=q[bit],
)
def get_oreg(clk,d,q):
for bit in range(len(q)):
m.submodules += Instance("DFF",
i_CLK=clk,
i_D=d[bit],
o_Q=q[bit]
)
def get_iddr(clk,d,q0,q1):
for bit in range(len(d)):
m.submodules += Instance("IDDR",
i_CLK=clk,
i_D=d[bit],
o_Q0=q0[bit],
o_Q1=q1[bit]
)
def get_oddr(clk,d0,d1,q):
for bit in range(len(q)):
m.submodules += Instance("ODDR",
p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK
i_CLK=clk,
i_D0=d0[bit],
i_D1=d1[bit],
o_Q0=q[bit]
)
def get_oeddr(clk,d0,d1,tx,q0,q1):
for bit in range(len(q0)):
m.submodules += Instance("ODDR",
p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK
i_CLK=clk,
i_D0=d0[bit],
i_D1=d1[bit],
i_TX=tx,
o_Q0=q0[bit],
o_Q1=q1
)
def get_ineg(y, invert):
if invert:
a = Signal.like(y, name_suffix="_n")
m.d.comb += y.eq(~a)
return a
else:
return y
def get_oneg(a, invert):
if invert:
y = Signal.like(a, name_suffix="_n")
m.d.comb += y.eq(~a)
return y
else:
return a
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)
i = o = t = None
if "i" in pin.dir:
i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
if "o" in pin.dir:
o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
if pin.dir in ("oe", "io"):
t = Signal(1, name="{}_xdr_t".format(pin.name))
if pin.xdr == 0:
if "i" in pin.dir:
i = pin_i
if "o" in pin.dir:
o = pin_o
if pin.dir in ("oe", "io"):
t = ~pin.oe
elif pin.xdr == 1:
if "i" in pin.dir:
get_ireg(pin.i_clk, i, pin_i)
if "o" in pin.dir:
get_oreg(pin.o_clk, pin_o, o)
if pin.dir in ("oe", "io"):
get_oreg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 2:
if "i" in pin.dir:
get_iddr(pin.i_clk, i, pin_i0, pin_i1)
if pin.dir in ("o",):
get_oddr(pin.o_clk, pin_o0, pin_o1, o)
if pin.dir in ("oe", "io"):
get_oeddr(pin.o_clk, pin_o0, pin_o1, ~pin.oe, o, t)
else:
assert False
return (i, o, t)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF",
i_I=port.io[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, port.io, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF",
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("TBUF",
i_OEN=t,
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF",
i_OEN=t,
i_I=o[bit],
o_O=i[bit],
io_IO=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.wodth):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IBUF",
i_I=port.p[bit],
i_IB=port.n[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_OBUF",
i_I=o[bit],
o_O=port.p[bit],
o_OB=port.n[bit],
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_TBUF",
i_OEN=t,
i_I=o[bit],
o_O=port.p[bit],
o_OB=port.n[bit]
)
return m
def get_diff_input_output(self, pin, port, atttr, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IOBUF",
i_OEN=t,
i_I=o[bit],
o_O=i[bit],
io_IO=port.p[bit],
io_IOB=port.n[bit]
)
return m

568
amaranth/vendor/_intel.py Normal file
View file

@ -0,0 +1,568 @@
from abc import abstractmethod
from ..hdl import *
from ..build import *
class IntelPlatform(TemplatedPlatform):
"""
.. rubric:: Quartus toolchain
Required tools:
* ``quartus_map``
* ``quartus_fit``
* ``quartus_asm``
* ``quartus_sta``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_QUARTUS``, if present.
Available overrides:
* ``add_settings``: inserts commands at the end of the QSF file.
* ``add_constraints``: inserts commands at the end of the SDC file.
* ``nproc``: sets the number of cores used by all tools.
* ``quartus_map_opts``: adds extra options for ``quartus_map``.
* ``quartus_fit_opts``: adds extra options for ``quartus_fit``.
* ``quartus_asm_opts``: adds extra options for ``quartus_asm``.
* ``quartus_sta_opts``: adds extra options for ``quartus_sta``.
Build products:
* ``*.rpt``: toolchain reports.
* ``{{name}}.sof``: bitstream as SRAM object file.
* ``{{name}}.rbf``: bitstream as raw binary file.
.. rubric:: Mistral toolchain
Required tools:
* ``yosys``
* ``nextpnr-mistral``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_MISTRAL``, if present.
* ``verbose``: enables logging of informational messages to standard error.
* ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
* ``synth_opts``: adds options for ``synth_intel_alm`` Yosys command.
* ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
* ``script_after_synth``: inserts commands after ``synth_intel_alm`` in Yosys script.
* ``yosys_opts``: adds extra options for ``yosys``.
* ``nextpnr_opts``: adds extra options for ``nextpnr-mistral``.
"""
toolchain = None # selected when creating platform
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
speed = property(abstractmethod(lambda: None))
suffix = ""
# Quartus templates
quartus_suppressed_warnings = [
10264, # All case item expressions in this case statement are onehot
10270, # Incomplete Verilog case statement has no default case item
10335, # Unrecognized synthesis attribute
10763, # Verilog case statement has overlapping case item expressions with non-constant or don't care bits
10935, # Verilog casex/casez overlaps with a previous casex/vasez item expression
12125, # Using design file which is not specified as a design file for the current project, but contains definitions used in project
18236, # Number of processors not specified in QSF
292013, # Feature is only available with a valid subscription license
]
quartus_required_tools = [
"quartus_map",
"quartus_fit",
"quartus_asm",
"quartus_sta",
]
quartus_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
QUARTUS_ROOTDIR=$(dirname $(dirname "${{var}}"))
# Quartus' qenv.sh does not work with `set -e`.
. "${{var}}"
fi
{% endfor %}
set -e{{verbose("x")}}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.qsf": r"""
# {{autogenerated}}
{% if get_override("nproc") -%}
set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}}
{% endif %}
{% for file in platform.iter_files(".v") -%}
set_global_assignment -name VERILOG_FILE {{file|tcl_quote}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
set_global_assignment -name SYSTEMVERILOG_FILE {{file|tcl_quote}}
{% endfor %}
{% for file in platform.iter_files(".vhd", ".vhdl") -%}
set_global_assignment -name VHDL_FILE {{file|tcl_quote}}
{% endfor %}
set_global_assignment -name VERILOG_FILE {{name}}.v
set_global_assignment -name TOP_LEVEL_ENTITY {{name}}
set_global_assignment -name DEVICE {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}}
{% for key, value in attrs.items() -%}
set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}}
{% endfor %}
{% endfor %}
set_global_assignment -name GENERATE_RBF_FILE ON
{{get_override("add_settings")|default("# (add_settings placeholder)")}}
""",
"{{name}}.sdc": r"""
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_quote}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("|")|tcl_quote}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.srf": r"""
{% for warning in platform.quartus_suppressed_warnings %}
{ "" "" "" "{{name}}.v" { } { } 0 {{warning}} "" 0 0 "Design Software" 0 -1 0 ""}
{% endfor %}
""",
}
quartus_command_templates = [
r"""
{{invoke_tool("quartus_map")}}
{{get_override("quartus_map_opts")|options}}
--rev={{name}} {{name}}
""",
r"""
{{invoke_tool("quartus_fit")}}
{{get_override("quartus_fit_opts")|options}}
--rev={{name}} {{name}}
""",
r"""
{{invoke_tool("quartus_asm")}}
{{get_override("quartus_asm_opts")|options}}
--rev={{name}} {{name}}
""",
r"""
{{invoke_tool("quartus_sta")}}
{{get_override("quartus_sta_opts")|options}}
--rev={{name}} {{name}}
""",
]
# Mistral templates
mistral_required_tools = [
"yosys",
"nextpnr-mistral"
]
mistral_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_intel_alm {{get_override("synth_opts")|options}} -top {{name}}
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
write_json {{name}}.json
""",
"{{name}}.qsf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}}
{% for key, value in attrs.items() -%}
set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}}
{% endfor %}
{% endfor %}
""",
}
mistral_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-mistral")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
--device {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
--json {{name}}.json
--qsf {{name}}.qsf
--rbf {{name}}.rbf
"""
]
# Common logic
def __init__(self, *, toolchain="Quartus"):
super().__init__()
assert toolchain in ("Quartus", "Mistral")
self.toolchain = toolchain
@property
def required_tools(self):
if self.toolchain == "Quartus":
return self.quartus_required_tools
if self.toolchain == "Mistral":
return self.mistral_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "Quartus":
return self.quartus_file_templates
if self.toolchain == "Mistral":
return self.mistral_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "Quartus":
return self.quartus_command_templates
if self.toolchain == "Mistral":
return self.mistral_command_templates
assert False
def add_clock_constraint(self, clock, frequency):
super().add_clock_constraint(clock, frequency)
clock.attrs["keep"] = "true"
@property
def default_clk_constraint(self):
# Internal high-speed oscillator on Cyclone V devices.
# It is specified to not be faster than 100MHz, but the actual
# frequency seems to vary a lot between devices. Measurements
# of 78 to 84 MHz have been observed.
if self.default_clk == "cyclonev_oscillator":
assert self.device.startswith("5C")
return Clock(100e6)
# Otherwise, use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
if name == "sync" and self.default_clk == "cyclonev_oscillator":
# Use the internal high-speed oscillator for Cyclone V devices
assert self.device.startswith("5C")
m = Module()
m.domains += ClockDomain("sync")
m.submodules += Instance("cyclonev_oscillator",
i_oscena=Const(1),
o_clkout=ClockSignal("sync"))
return m
else:
return super().create_missing_domain(name)
# The altiobuf_* and altddio_* primitives are explained in the following Intel documents:
# * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altiobuf.pdf
# * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altddio.pdf
# See also errata mentioned in: https://www.intel.com/content/www/us/en/programmable/support/support-resources/knowledge-base/solutions/rd11192012_735.html.
@staticmethod
def _get_ireg(m, pin, invert):
def get_ineg(i):
if invert:
i_neg = Signal.like(i, name_suffix="_neg")
m.d.comb += i.eq(~i_neg)
return i_neg
else:
return i
if pin.xdr == 0:
return get_ineg(pin.i)
elif pin.xdr == 1:
i_sdr = Signal(pin.width, name="{}_i_sdr")
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=pin.width,
i_CLK=pin.i_clk,
i_D=i_sdr,
o_Q=get_ineg(pin.i),
)
return i_sdr
elif pin.xdr == 2:
i_ddr = Signal(pin.width, name="{}_i_ddr".format(pin.name))
m.submodules["{}_i_ddr".format(pin.name)] = Instance("altddio_in",
p_width=pin.width,
i_datain=i_ddr,
i_inclock=pin.i_clk,
o_dataout_h=get_ineg(pin.i0),
o_dataout_l=get_ineg(pin.i1),
)
return i_ddr
assert False
@staticmethod
def _get_oreg(m, pin, invert):
def get_oneg(o):
if invert:
o_neg = Signal.like(o, name_suffix="_neg")
m.d.comb += o_neg.eq(~o)
return o_neg
else:
return o
if pin.xdr == 0:
return get_oneg(pin.o)
elif pin.xdr == 1:
o_sdr = Signal(pin.width, name="{}_o_sdr".format(pin.name))
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=pin.width,
i_CLK=pin.o_clk,
i_D=get_oneg(pin.o),
o_Q=o_sdr,
)
return o_sdr
elif pin.xdr == 2:
o_ddr = Signal(pin.width, name="{}_o_ddr".format(pin.name))
m.submodules["{}_o_ddr".format(pin.name)] = Instance("altddio_out",
p_width=pin.width,
o_dataout=o_ddr,
i_outclock=pin.o_clk,
i_datain_h=get_oneg(pin.o0),
i_datain_l=get_oneg(pin.o1),
)
return o_ddr
assert False
@staticmethod
def _get_oereg(m, pin):
# altiobuf_ requires an output enable signal for each pin, but pin.oe is 1 bit wide.
if pin.xdr == 0:
return pin.oe.replicate(pin.width)
elif pin.xdr in (1, 2):
oe_reg = Signal(pin.width, name="{}_oe_reg".format(pin.name))
oe_reg.attrs["useioff"] = "1"
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=pin.width,
i_CLK=pin.o_clk,
i_D=pin.oe,
o_Q=oe_reg,
)
return oe_reg
assert False
def get_input(self, pin, port, attrs, invert):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_in",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
i_datain=port.io,
o_dataout=self._get_ireg(m, pin, 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)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
p_use_oe="FALSE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.io,
)
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)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
p_use_oe="TRUE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.io,
i_oe=self._get_oereg(m, pin)
)
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)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_bidir",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
i_datain=self._get_oreg(m, pin, invert),
io_dataio=port.io,
o_dataout=self._get_ireg(m, pin, invert),
i_oe=self._get_oereg(m, pin),
)
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)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_in",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
i_datain=port.p,
i_datain_b=port.n,
o_dataout=self._get_ireg(m, pin, 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)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
p_use_oe="FALSE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.p,
o_dataout_b=port.n,
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
p_use_oe="TRUE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.p,
o_dataout_b=port.n,
i_oe=self._get_oereg(m, pin),
)
return m
def get_diff_input_output(self, pin, port, attrs, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_bidir",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
i_datain=self._get_oreg(m, pin, invert),
io_dataio=port.p,
io_dataio_b=port.n,
o_dataout=self._get_ireg(m, pin, invert),
i_oe=self._get_oereg(m, pin),
)
return m
# The altera_std_synchronizer{,_bundle} megafunctions embed SDC constraints that mark false
# paths, so use them instead of our default implementation.
def get_ff_sync(self, ff_sync):
return Instance("altera_std_synchronizer_bundle",
p_width=len(ff_sync.i),
p_depth=ff_sync._stages,
i_clk=ClockSignal(ff_sync._o_domain),
i_reset_n=Const(1),
i_din=ff_sync.i,
o_dout=ff_sync.o,
)
def get_async_ff_sync(self, async_ff_sync):
m = Module()
sync_output = Signal()
if async_ff_sync._edge == "pos":
m.submodules += Instance("altera_std_synchronizer",
p_depth=async_ff_sync._stages,
i_clk=ClockSignal(async_ff_sync._o_domain),
i_reset_n=~async_ff_sync.i,
i_din=Const(1),
o_dout=sync_output,
)
else:
m.submodules += Instance("altera_std_synchronizer",
p_depth=async_ff_sync._stages,
i_clk=ClockSignal(async_ff_sync._o_domain),
i_reset_n=async_ff_sync.i,
i_din=Const(1),
o_dout=sync_output,
)
m.d.comb += async_ff_sync.o.eq(~sync_output)
return m

View file

@ -0,0 +1,665 @@
from abc import abstractmethod
from ..hdl import *
from ..build import *
class LatticeECP5Platform(TemplatedPlatform):
"""
.. rubric:: Trellis toolchain
Required tools:
* ``yosys``
* ``nextpnr-ecp5``
* ``ecppack``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_TRELLIS``, if present.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
* ``synth_opts``: adds options for ``synth_ecp5`` Yosys command.
* ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
* ``script_after_synth``: inserts commands after ``synth_ecp5`` in Yosys script.
* ``yosys_opts``: adds extra options for ``yosys``.
* ``nextpnr_opts``: adds extra options for ``nextpnr-ecp5``.
* ``ecppack_opts``: adds extra options for ``ecppack``.
* ``add_preferences``: inserts commands at the end of the LPF file.
Build products:
* ``{{name}}.rpt``: Yosys log.
* ``{{name}}.json``: synthesized RTL.
* ``{{name}}.tim``: nextpnr log.
* ``{{name}}.config``: ASCII bitstream.
* ``{{name}}.bit``: binary bitstream.
* ``{{name}}.svf``: JTAG programming vector.
.. rubric:: Diamond toolchain
Required tools:
* ``pnmainc``
* ``ddtcmd``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond
itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``,
for instance) is known to work::
@echo off
set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH%
Available overrides:
* ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
* ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
* ``add_preferences``: inserts commands at the end of the LPF file.
* ``add_constraints``: inserts commands at the end of the XDC file.
Build products:
* ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
* ``{{name}}.bit``: binary bitstream.
* ``{{name}}.svf``: JTAG programming vector.
"""
toolchain = None # selected when creating platform
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
speed = property(abstractmethod(lambda: None))
grade = "C" # [C]ommercial, [I]ndustrial
# Trellis templates
_nextpnr_device_options = {
"LFE5U-12F": "--12k",
"LFE5U-25F": "--25k",
"LFE5U-45F": "--45k",
"LFE5U-85F": "--85k",
"LFE5UM-25F": "--um-25k",
"LFE5UM-45F": "--um-45k",
"LFE5UM-85F": "--um-85k",
"LFE5UM5G-25F": "--um5g-25k",
"LFE5UM5G-45F": "--um5g-45k",
"LFE5UM5G-85F": "--um5g-85k",
}
_nextpnr_package_options = {
"BG256": "caBGA256",
"MG285": "csfBGA285",
"BG381": "caBGA381",
"BG554": "caBGA554",
"BG756": "caBGA756",
}
_trellis_required_tools = [
"yosys",
"nextpnr-ecp5",
"ecppack"
]
_trellis_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_ecp5 {{get_override("synth_opts")|options}} -top {{name}}
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
write_json {{name}}.json
""",
"{{name}}.lpf": r"""
# {{autogenerated}}
BLOCK ASYNCPATHS;
BLOCK RESETPATHS;
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
{% if attrs -%}
IOBUF PORT "{{port_name}}"
{%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
{% endif %}
{% endfor %}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
FREQUENCY PORT "{{port_signal.name}}" {{frequency}} HZ;
{% else -%}
FREQUENCY NET "{{net_signal|hierarchy(".")}}" {{frequency}} HZ;
{% endif %}
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
"""
}
_trellis_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-ecp5")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
{{platform._nextpnr_device_options[platform.device]}}
--package {{platform._nextpnr_package_options[platform.package]|upper}}
--speed {{platform.speed}}
--json {{name}}.json
--lpf {{name}}.lpf
--textcfg {{name}}.config
""",
r"""
{{invoke_tool("ecppack")}}
{{verbose("--verbose")}}
{{get_override("ecppack_opts")|options}}
--input {{name}}.config
--bit {{name}}.bit
--svf {{name}}.svf
"""
]
# Diamond templates
_diamond_required_tools = [
"pnmainc",
"ddtcmd"
]
_diamond_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
bindir=$(dirname "${{var}}")
. "${{var}}"
fi
{% endfor %}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.tcl": r"""
prj_project new -name {{name}} -impl impl -impl_dir {{name}}_impl \
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
-lpf {{name}}.lpf \
-synthesis synplify
{% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
prj_src add {{file|tcl_escape}}
{% endfor %}
prj_src add {{name}}.v
prj_impl option top {{name}}
prj_src add {{name}}.sdc
{{get_override("script_project")|default("# (script_project placeholder)")}}
prj_project save
prj_run Synthesis -impl impl
prj_run Translate -impl impl
prj_run Map -impl impl
prj_run PAR -impl impl
prj_run Export -impl impl -task Bitgen
{{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
""",
"{{name}}.lpf": r"""
# {{autogenerated}}
BLOCK ASYNCPATHS;
BLOCK RESETPATHS;
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
{% if attrs -%}
IOBUF PORT "{{port_name}}"
{%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
{% endif %}
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
""",
"{{name}}.sdc": r"""
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
_diamond_command_templates = [
# These don't have any usable command-line option overrides.
r"""
{{invoke_tool("pnmainc")}}
{{name}}.tcl
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -bit
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -svfsingle -revd -op "Fast Program"
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.svf
""",
]
# Common logic
def __init__(self, *, toolchain="Trellis"):
super().__init__()
assert toolchain in ("Trellis", "Diamond")
self.toolchain = toolchain
@property
def required_tools(self):
if self.toolchain == "Trellis":
return self._trellis_required_tools
if self.toolchain == "Diamond":
return self._diamond_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "Trellis":
return self._trellis_file_templates
if self.toolchain == "Diamond":
return self._diamond_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "Trellis":
return self._trellis_command_templates
if self.toolchain == "Diamond":
return self._diamond_command_templates
assert False
@property
def default_clk_constraint(self):
if self.default_clk == "OSCG":
return Clock(310e6 / self.oscg_div)
return super().default_clk_constraint
def create_missing_domain(self, name):
# Lattice ECP5 devices have two global set/reset signals: PUR, which is driven at startup
# by the configuration logic and unconditionally resets every storage element, and GSR,
# which is driven by user logic and each storage element may be configured as affected or
# unaffected by GSR. PUR is purely asynchronous, so even though it is a low-skew global
# network, its deassertion may violate a setup/hold constraint with relation to a user
# clock. To avoid this, a GSR/SGSR instance should be driven synchronized to user clock.
if name == "sync" and self.default_clk is not None:
m = Module()
if self.default_clk == "OSCG":
if not hasattr(self, "oscg_div"):
raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
"and 128")
if not isinstance(self.oscg_div, int) or self.oscg_div < 2 or self.oscg_div > 128:
raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
"and 128, not {!r}"
.format(self.oscg_div))
clk_i = Signal()
m.submodules += Instance("OSCG", p_DIV=self.oscg_div, o_OSC=clk_i)
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
gsr0 = Signal()
gsr1 = Signal()
# There is no end-of-startup signal on ECP5, but PUR is released after IOB enable, so
# a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
m.submodules += [
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0, o_Q=gsr1),
# Although we already synchronize the reset input to user clock, SGSR has dedicated
# clock routing to the center of the FPGA; use that just in case it turns out to be
# more reliable. (None of this is documented.)
Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
]
# GSR implicitly connects to every appropriate storage element. As such, the sync
# domain is reset-less; domains driven by other clocks would need to have dedicated
# reset circuitry or otherwise meet setup/hold constraints on their own.
m.domains += ClockDomain("sync", reset_less=True)
m.d.comb += ClockSignal("sync").eq(clk_i)
return m
_single_ended_io_types = [
"HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33",
"SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II",
]
_differential_io_types = [
"BLVDS25", "BLVDS25E", "HSUL12D", "LVCMOS18D", "LVCMOS25D", "LVCMOS33D",
"LVDS", "LVDS25E", "LVPECL33", "LVPECL33E", "LVTTL33D", "MLVDS", "MLVDS25E",
"SLVS", "SSTL135D_I", "SSTL135D_II", "SSTL15D_I", "SSTL15D_II", "SSTL18D_I",
"SSTL18D_II", "SUBLVDS",
]
def should_skip_port_component(self, port, attrs, component):
# On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
# the PIOA or PIOC location, which is always the non-inverting pin.
if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
return True
return False
def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False):
def get_ireg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("IFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_oreg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("OFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_oereg(clk, oe, q):
for bit in range(len(q)):
m.submodules += Instance("OFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=oe,
o_Q=q[bit]
)
def get_iddr(sclk, d, q0, q1):
for bit in range(len(d)):
m.submodules += Instance("IDDRX1F",
i_SCLK=sclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit]
)
def get_iddrx2(sclk, eclk, d, q0, q1, q2, q3):
for bit in range(len(d)):
m.submodules += Instance("IDDRX2F",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit]
)
def get_iddr71b(sclk, eclk, d, q0, q1, q2, q3, q4, q5, q6):
for bit in range(len(d)):
m.submodules += Instance("IDDR71B",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit],
o_Q4=q4[bit], o_Q5=q5[bit], o_Q6=q6[bit],
)
def get_oddr(sclk, d0, d1, q):
for bit in range(len(q)):
m.submodules += Instance("ODDRX1F",
i_SCLK=sclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit],
o_Q=q[bit]
)
def get_oddrx2(sclk, eclk, d0, d1, d2, d3, q):
for bit in range(len(q)):
m.submodules += Instance("ODDRX2F",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit],
o_Q=q[bit]
)
def get_oddr71b(sclk, eclk, d0, d1, d2, d3, d4, d5, d6, q):
for bit in range(len(q)):
m.submodules += Instance("ODDR71B",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit],
i_D4=d4[bit], i_D5=d5[bit], i_D6=d6[bit],
o_Q=q[bit]
)
def get_ineg(z, invert):
if invert:
a = Signal.like(z, name_suffix="_n")
m.d.comb += z.eq(~a)
return a
else:
return z
def get_oneg(a, invert):
if invert:
z = Signal.like(a, name_suffix="_n")
m.d.comb += z.eq(~a)
return z
else:
return a
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)
elif pin.xdr == 4:
pin_i0 = get_ineg(pin.i0, i_invert)
pin_i1 = get_ineg(pin.i1, i_invert)
pin_i2 = get_ineg(pin.i2, i_invert)
pin_i3 = get_ineg(pin.i3, i_invert)
elif pin.xdr == 7:
pin_i0 = get_ineg(pin.i0, i_invert)
pin_i1 = get_ineg(pin.i1, i_invert)
pin_i2 = get_ineg(pin.i2, i_invert)
pin_i3 = get_ineg(pin.i3, i_invert)
pin_i4 = get_ineg(pin.i4, i_invert)
pin_i5 = get_ineg(pin.i5, i_invert)
pin_i6 = get_ineg(pin.i6, 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)
elif pin.xdr == 4:
pin_o0 = get_oneg(pin.o0, o_invert)
pin_o1 = get_oneg(pin.o1, o_invert)
pin_o2 = get_oneg(pin.o2, o_invert)
pin_o3 = get_oneg(pin.o3, o_invert)
elif pin.xdr == 7:
pin_o0 = get_oneg(pin.o0, o_invert)
pin_o1 = get_oneg(pin.o1, o_invert)
pin_o2 = get_oneg(pin.o2, o_invert)
pin_o3 = get_oneg(pin.o3, o_invert)
pin_o4 = get_oneg(pin.o4, o_invert)
pin_o5 = get_oneg(pin.o5, o_invert)
pin_o6 = get_oneg(pin.o6, o_invert)
i = o = t = None
if "i" in pin.dir:
i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
if "o" in pin.dir:
o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
if pin.dir in ("oe", "io"):
t = Signal(pin.width, name="{}_xdr_t".format(pin.name))
if pin.xdr == 0:
if "i" in pin.dir:
i = pin_i
if "o" in pin.dir:
o = pin_o
if pin.dir in ("oe", "io"):
t = (~pin.oe).replicate(pin.width)
elif pin.xdr == 1:
if "i" in pin.dir:
get_ireg(pin.i_clk, i, pin_i)
if "o" in pin.dir:
get_oreg(pin.o_clk, pin_o, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 2:
if "i" in pin.dir:
get_iddr(pin.i_clk, i, pin_i0, pin_i1)
if "o" in pin.dir:
get_oddr(pin.o_clk, pin_o0, pin_o1, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 4:
if "i" in pin.dir:
get_iddrx2(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3)
if "o" in pin.dir:
get_oddrx2(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 7:
if "i" in pin.dir:
get_iddr71b(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3, pin_i4, pin_i5, pin_i6)
if "o" in pin.dir:
get_oddr71b(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, pin_o4, pin_o5, pin_o6, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
else:
assert False
return (i, o, t)
def get_input(self, pin, port, attrs, invert):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.io[bit],
o_O=i[bit]
)
return m
def get_output(self, pin, port, attrs, invert):
self._check_feature("single-ended output", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.io[bit]
)
return m
def get_tristate(self, pin, port, attrs, invert):
self._check_feature("single-ended tristate", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t[bit],
i_I=o[bit],
o_O=port.io[bit]
)
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, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t[bit],
i_I=o[bit],
o_O=i[bit],
io_B=port.io[bit]
)
return m
def get_diff_input(self, pin, port, attrs, invert):
self._check_feature("differential input", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.p[bit],
o_O=i[bit]
)
return m
def get_diff_output(self, pin, port, attrs, invert):
self._check_feature("differential output", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t[bit],
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_input_output(self, pin, port, attrs, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t[bit],
i_I=o[bit],
o_O=i[bit],
io_B=port.p[bit],
)
return m
# CDC primitives are not currently specialized for ECP5.
# While Diamond supports false path constraints; nextpnr-ecp5 does not.

View file

@ -0,0 +1,624 @@
from abc import abstractmethod
from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *
class LatticeICE40Platform(TemplatedPlatform):
"""
.. rubric:: IceStorm toolchain
Required tools:
* ``yosys``
* ``nextpnr-ice40``
* ``icepack``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_ICESTORM``, if present.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``read_verilog_opts``: adds options for ``read_verilog`` 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 options for ``nextpnr-ice40``.
* ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
* ``add_constraints``: inserts commands at the end in the PCF file.
Build products:
* ``{{name}}.rpt``: Yosys log.
* ``{{name}}.json``: synthesized RTL.
* ``{{name}}.tim``: nextpnr log.
* ``{{name}}.asc``: ASCII bitstream.
* ``{{name}}.bin``: binary bitstream.
.. rubric:: iCECube2 toolchain
This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.
Required tools:
* iCECube2 toolchain
* ``tclsh``
The environment is populated by setting the necessary environment variables based on
``AMARANTH_ENV_ICECUBE2``, which must point to the root of the iCECube2 installation, and
is required.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``lse_opts``: adds options for LSE.
* ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
* ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
* ``add_constraints``: inserts commands in SDC file.
* ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
Tcl script.
Build products:
* ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
* ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
* ``{{name}}.edf``: EDIF netlist.
* ``{{name}}.bin``: binary bitstream.
"""
toolchain = None # selected when creating platform
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
# IceStorm templates
_nextpnr_device_options = {
"iCE40LP384": "--lp384",
"iCE40LP1K": "--lp1k",
"iCE40LP4K": "--lp8k",
"iCE40LP8K": "--lp8k",
"iCE40HX1K": "--hx1k",
"iCE40HX4K": "--hx8k",
"iCE40HX8K": "--hx8k",
"iCE40UP5K": "--up5k",
"iCE40UP3K": "--up5k",
"iCE5LP4K": "--u4k",
"iCE5LP2K": "--u4k",
"iCE5LP1K": "--u4k",
}
_nextpnr_package_options = {
"iCE40LP4K": ":4k",
"iCE40HX4K": ":4k",
"iCE40UP3K": "",
"iCE5LP2K": "",
"iCE5LP1K": "",
}
_icestorm_required_tools = [
"yosys",
"nextpnr-ice40",
"icepack",
]
_icestorm_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_ice40 {{get_override("synth_opts")|options}} -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, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}}
{% endfor%}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
_icestorm_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-ice40")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
{{platform._nextpnr_device_options[platform.device]}}
--package
{{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]|
default("")}}
--json {{name}}.json
--pcf {{name}}.pcf
--asc {{name}}.asc
""",
r"""
{{invoke_tool("icepack")}}
{{verbose("-v")}}
{{name}}.asc
{{name}}.bin
"""
]
# iCECube2 templates
_icecube2_required_tools = [
"synthesis",
"synpwrap",
"tclsh",
]
_icecube2_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
# LSE environment
export LD_LIBRARY_PATH=${{var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
export PATH=${{var}}/LSE/bin/lin64:$PATH
export FOUNDRY=${{var}}/LSE
# Synplify environment
export LD_LIBRARY_PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
export PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
export SYNPLIFY_PATH=${{var}}/synpbase
# Common environment
export SBT_DIR=${{var}}/sbt_backend
else
echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
fi
{% endfor %}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}_lse.prj": r"""
# {{autogenerated}}
-a SBT{{platform.family}}
-d {{platform.device}}
-t {{platform.package}}
{{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
{% for file in platform.iter_files(".v") -%}
-ver {{file}}
{% endfor %}
-ver {{name}}.v
-sdc {{name}}.sdc
-top {{name}}
-output_edif {{name}}.edf
-logfile {{name}}_lse.log
""",
"{{name}}_syn.prj": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
add_file -verilog {{file|tcl_escape}}
{% endfor %}
add_file -verilog {{name}}.v
add_file -constraint {{name}}.sdc
{{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
impl -add {{name}}_design -type fpga
set_option -technology SBT{{platform.family}}
set_option -part {{platform.device}}
set_option -package {{platform.package}}
{{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
project -result_format edif
project -result_file {{name}}.edf
impl -active {{name}}_design
project -run compile
project -run map
project -run fpga_mapper
file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
""",
"{{name}}.sdc": r"""
# {{autogenerated}}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.tcl": r"""
# {{autogenerated}}
set device {{platform.device}}-{{platform.package}}
set top_module {{name}}
set proj_dir .
set output_dir .
set edif_file {{name}}
set tool_options ":edifparser -y {{name}}.pcf"
set sbt_root $::env(SBT_DIR)
append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
source $sbt_tcl
run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
{{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
exit
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
}
_lse_icecube2_command_templates = [
r"""synthesis -f {{name}}_lse.prj""",
r"""tclsh {{name}}.tcl""",
]
_synplify_icecube2_command_templates = [
r"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
r"""tclsh {{name}}.tcl""",
]
# Common logic
def __init__(self, *, toolchain="IceStorm"):
super().__init__()
assert toolchain in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
self.toolchain = toolchain
@property
def family(self):
if self.device.startswith("iCE40"):
return "iCE40"
if self.device.startswith("iCE5"):
return "iCE5"
assert False
@property
def _toolchain_env_var(self):
if self.toolchain == "IceStorm":
return f"AMARANTH_ENV_{self.toolchain}"
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return f"AMARANTH_ENV_ICECUBE2"
assert False
@property
def required_tools(self):
if self.toolchain == "IceStorm":
return self._icestorm_required_tools
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return self._icecube2_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "IceStorm":
return self._icestorm_file_templates
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return self._icecube2_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "IceStorm":
return self._icestorm_command_templates
if self.toolchain == "LSE-iCECube2":
return self._lse_icecube2_command_templates
if self.toolchain == "Synplify-iCECube2":
return self._synplify_icecube2_command_templates
assert False
@property
def default_clk_constraint(self):
# Internal high-speed oscillator: 48 MHz / (2 ^ div)
if self.default_clk == "SB_HFOSC":
return Clock(48e6 / 2 ** self.hfosc_div)
# Internal low-speed oscillator: 10 KHz
elif self.default_clk == "SB_LFOSC":
return Clock(10e3)
# Otherwise, use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
# For unknown reasons (no errata was ever published, and no documentation mentions this
# issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
# global reset. Note that this is a *time-based* delay, generated purely by the internal
# oscillator, which may not be observed nor influenced directly. For details, see links:
# * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411
# * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673
#
# To handle this, it is necessary to have a global reset in any iCE40 design that may
# potentially instantiate BRAMs, and assert this reset for >3 us after configuration.
# (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated
# reset line, this line is ORed with the power on reset.
#
# If an internal oscillator is selected as the default clock source, the power-on-reset
# delay is increased to 100 us, since the oscillators are only stable after that long.
#
# The power-on reset timer counts up because the vendor tools do not support initialization
# of flip-flops.
if name == "sync" and self.default_clk is not None:
m = Module()
# Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider.
if self.default_clk == "SB_HFOSC":
if not hasattr(self, "hfosc_div"):
raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
"between 0 and 3")
if not isinstance(self.hfosc_div, int) or self.hfosc_div < 0 or self.hfosc_div > 3:
raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
"between 0 and 3, not {!r}"
.format(self.hfosc_div))
clk_i = Signal()
m.submodules += Instance("SB_HFOSC",
i_CLKHFEN=1,
i_CLKHFPU=1,
p_CLKHF_DIV="0b{0:02b}".format(self.hfosc_div),
o_CLKHF=clk_i)
delay = int(100e-6 * self.default_clk_frequency)
# Internal low-speed clock: 10 KHz.
elif self.default_clk == "SB_LFOSC":
clk_i = Signal()
m.submodules += Instance("SB_LFOSC",
i_CLKLFEN=1,
i_CLKLFPU=1,
o_CLKLF=clk_i)
delay = int(100e-6 * self.default_clk_frequency)
# User-defined clock signal.
else:
clk_i = self.request(self.default_clk).i
delay = int(15e-6 * self.default_clk_frequency)
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
# Power-on-reset domain
m.domains += ClockDomain("por", reset_less=True, local=True)
timer = Signal(range(delay))
ready = Signal()
m.d.comb += ClockSignal("por").eq(clk_i)
with m.If(timer == delay):
m.d.por += ready.eq(1)
with m.Else():
m.d.por += timer.eq(timer + 1)
# Primary domain
m.domains += ClockDomain("sync")
m.d.comb += ClockSignal("sync").eq(clk_i)
if self.default_rst is not None:
m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync")
else:
m.d.comb += ResetSignal("sync").eq(~ready)
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):
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=len(d),
i_CLK=clk,
i_D=d,
o_Q=q)
def get_ineg(y, invert):
if invert_lut:
a = Signal.like(y, name_suffix="_x{}".format(1 if invert else 0))
for bit in range(len(y)):
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 a
elif invert:
a = Signal.like(y, name_suffix="_n")
m.d.comb += y.eq(~a)
return a
else:
return y
def get_oneg(a, invert):
if invert_lut:
y = Signal.like(a, name_suffix="_x{}".format(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
if "GLOBAL" in attrs:
is_global_input = bool(attrs["GLOBAL"])
del attrs["GLOBAL"]
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)
for bit in range(len(port)):
io_args = [
("io", "PACKAGE_PIN", port[bit]),
*(("p", key, value) for key, value in attrs.items()),
]
if "i" not in pin.dir:
# 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:
i_type = 0b01 # PIN_INPUT
elif pin.xdr > 0:
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
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", 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 is_global_input:
m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_GB_IO", *io_args)
else:
m.submodules["{}_{}".format(pin.name, 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.
# CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
# the necessary attributes; nextpnr-ice40 does not.

View file

@ -0,0 +1,449 @@
from abc import abstractmethod
from ..hdl import *
from ..build import *
# MachXO2 and MachXO3L primitives are the same. Handle both using
# one class and expose user-aliases for convenience.
class LatticeMachXO2Or3LPlatform(TemplatedPlatform):
"""
Required tools:
* ``pnmainc``
* ``ddtcmd``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond
itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``,
for instance) is known to work::
@echo off
set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH%
Available overrides:
* ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
* ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
* ``add_preferences``: inserts commands at the end of the LPF file.
* ``add_constraints``: inserts commands at the end of the XDC file.
Build products:
* ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
* ``{{name}}.jed``: JEDEC fuse file.
* ``{{name}}.bit``: binary bitstream.
* ``{{name}}.svf``: JTAG programming vector for FLASH programming.
* ``{{name}}_flash.svf``: JTAG programming vector for FLASH programming.
* ``{{name}}_sram.svf``: JTAG programming vector for SRAM programming.
"""
toolchain = "Diamond"
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
speed = property(abstractmethod(lambda: None))
grade = "C" # [C]ommercial, [I]ndustrial
required_tools = [
"pnmainc",
"ddtcmd"
]
file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
bindir=$(dirname "${{var}}")
. "${{var}}"
fi
{% endfor %}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.tcl": r"""
prj_project new -name {{name}} -impl impl -impl_dir {{name}}_impl \
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
-lpf {{name}}.lpf \
-synthesis synplify
{% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
prj_src add {{file|tcl_escape}}
{% endfor %}
prj_src add {{name}}.v
prj_impl option top {{name}}
prj_src add {{name}}.sdc
{{get_override("script_project")|default("# (script_project placeholder)")}}
prj_project save
prj_run Synthesis -impl impl
prj_run Translate -impl impl
prj_run Map -impl impl
prj_run PAR -impl impl
prj_run Export -impl impl -task Bitgen
prj_run Export -impl impl -task Jedecgen
{{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
""",
"{{name}}.lpf": r"""
# {{autogenerated}}
BLOCK ASYNCPATHS;
BLOCK RESETPATHS;
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
{% if attrs -%}
IOBUF PORT "{{port_name}}"
{%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
{% endif %}
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
""",
"{{name}}.sdc": r"""
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
command_templates = [
# These don't have any usable command-line option overrides.
r"""
{{invoke_tool("pnmainc")}}
{{name}}.tcl
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -bit
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -jed
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}}
-if {{name}}_impl/{{name}}_impl.jed -of {{name}}.jed
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -svfsingle -revd -op "FLASH Erase,Program,Verify"
-if {{name}}_impl/{{name}}_impl.jed -of {{name}}_flash.svf
""",
# TODO(amaranth-0.4): remove
r"""
{% if syntax == "bat" -%}
copy {{name}}_flash.svf {{name}}.svf
{% else -%}
cp {{name}}_flash.svf {{name}}.svf
{% endif %}
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -svfsingle -revd -op "SRAM Fast Program"
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}_sram.svf
""",
]
# These numbers were extracted from
# "MachXO2 sysCLOCK PLL Design and Usage Guide"
_supported_osch_freqs = [
2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89,
3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59,
4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00,
7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64,
11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63,
17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33,
53.20, 66.50, 88.67, 133.00
]
@property
def default_clk_constraint(self):
# Internal high-speed oscillator on MachXO2/MachXO3L devices.
# It can have a range of frequencies.
if self.default_clk == "OSCH":
assert self.osch_frequency in self._supported_osch_freqs
return Clock(int(self.osch_frequency * 1e6))
# Otherwise, use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
# Lattice MachXO2/MachXO3L devices have two global set/reset signals: PUR, which is driven at
# startup by the configuration logic and unconditionally resets every storage element,
# and GSR, which is driven by user logic and each storage element may be configured as
# affected or unaffected by GSR. PUR is purely asynchronous, so even though it is
# a low-skew global network, its deassertion may violate a setup/hold constraint with
# relation to a user clock. To avoid this, a GSR/SGSR instance should be driven
# synchronized to user clock.
if name == "sync" and self.default_clk is not None:
using_osch = False
if self.default_clk == "OSCH":
using_osch = True
clk_i = Signal()
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
gsr0 = Signal()
gsr1 = Signal()
m = Module()
# There is no end-of-startup signal on MachXO2/MachXO3L, but PUR is released after IOB
# enable, so a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
m.submodules += [
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0, o_Q=gsr1),
# Although we already synchronize the reset input to user clock, SGSR has dedicated
# clock routing to the center of the FPGA; use that just in case it turns out to be
# more reliable. (None of this is documented.)
Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
]
if using_osch:
osch_freq = self.osch_frequency
if osch_freq not in self._supported_osch_freqs:
raise ValueError("Frequency {!r} is not valid for OSCH clock. Valid frequencies are {!r}"
.format(osch_freq, self._supported_osch_freqs))
osch_freq_param = "{:.2f}".format(float(osch_freq))
m.submodules += [ Instance("OSCH", p_NOM_FREQ=osch_freq_param, i_STDBY=Const(0), o_OSC=clk_i, o_SEDSTDBY=Signal()) ]
# GSR implicitly connects to every appropriate storage element. As such, the sync
# domain is reset-less; domains driven by other clocks would need to have dedicated
# reset circuitry or otherwise meet setup/hold constraints on their own.
m.domains += ClockDomain("sync", reset_less=True)
m.d.comb += ClockSignal("sync").eq(clk_i)
return m
_single_ended_io_types = [
"PCI33", "LVTTL33", "LVCMOS33", "LVCMOS25", "LVCMOS18", "LVCMOS15", "LVCMOS12",
"LVCMOS25R33", "LVCMOS18R33", "LVCMOS18R25", "LVCMOS15R33", "LVCMOS15R25", "LVCMOS12R33",
"LVCMOS12R25", "LVCMOS10R33", "LVCMOS10R25", "SSTL25_I", "SSTL25_II", "SSTL18_I",
"SSTL18_II", "HSTL18_I", "HSTL18_II",
]
_differential_io_types = [
"LVDS25", "LVDS25E", "RSDS25", "RSDS25E", "BLVDS25", "BLVDS25E", "MLVDS25", "MLVDS25E",
"LVPECL33", "LVPECL33E", "SSTL25D_I", "SSTL25D_II", "SSTL18D_I", "SSTL18D_II",
"HSTL18D_I", "HSTL18D_II", "LVTTL33D", "LVCMOS33D", "LVCMOS25D", "LVCMOS18D", "LVCMOS15D",
"LVCMOS12D", "MIPI",
]
def should_skip_port_component(self, port, attrs, component):
# On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
# the PIOA or PIOC location, which is always the non-inverting pin.
if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
return True
return False
def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False):
def get_ireg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("IFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_oreg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("OFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_iddr(sclk, d, q0, q1):
for bit in range(len(d)):
m.submodules += Instance("IDDRXE",
i_SCLK=sclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit]
)
def get_oddr(sclk, d0, d1, q):
for bit in range(len(q)):
m.submodules += Instance("ODDRXE",
i_SCLK=sclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit],
o_Q=q[bit]
)
def get_ineg(z, invert):
if invert:
a = Signal.like(z, name_suffix="_n")
m.d.comb += z.eq(~a)
return a
else:
return z
def get_oneg(a, invert):
if invert:
z = Signal.like(a, name_suffix="_n")
m.d.comb += z.eq(~a)
return z
else:
return a
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)
i = o = t = None
if "i" in pin.dir:
i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
if "o" in pin.dir:
o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
if pin.dir in ("oe", "io"):
t = Signal(1, name="{}_xdr_t".format(pin.name))
if pin.xdr == 0:
if "i" in pin.dir:
i = pin_i
if "o" in pin.dir:
o = pin_o
if pin.dir in ("oe", "io"):
t = ~pin.oe
elif pin.xdr == 1:
# Note that currently nextpnr will not pack an FF (*FS1P3DX) into the PIO.
if "i" in pin.dir:
get_ireg(pin.i_clk, i, pin_i)
if "o" in pin.dir:
get_oreg(pin.o_clk, pin_o, o)
if pin.dir in ("oe", "io"):
get_oreg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 2:
if "i" in pin.dir:
get_iddr(pin.i_clk, i, pin_i0, pin_i1)
if "o" in pin.dir:
get_oddr(pin.o_clk, pin_o0, pin_o1, o)
if pin.dir in ("oe", "io"):
# It looks like Diamond will not pack an OREG as a tristate register in a DDR PIO.
# It is not clear what is the recommended set of primitives for this task.
# Similarly, nextpnr will not pack anything as a tristate register in a DDR PIO.
get_oreg(pin.o_clk, ~pin.oe, t)
else:
assert False
return (i, o, t)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.io[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t,
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t,
i_I=o[bit],
o_O=i[bit],
io_B=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.p[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t,
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_input_output(self, pin, port, attrs, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t,
i_I=o[bit],
o_O=i[bit],
io_B=port.p[bit],
)
return m
# CDC primitives are not currently specialized for MachXO2/MachXO3L.

View file

@ -0,0 +1,184 @@
from abc import abstractmethod
from ..hdl import *
from ..lib.cdc import ResetSynchronizer
from ..build import *
__all__ = ["QuicklogicPlatform"]
class QuicklogicPlatform(TemplatedPlatform):
"""
.. rubric:: Symbiflow toolchain
Required tools:
* ``symbiflow_synth``
* ``symbiflow_pack``
* ``symbiflow_place``
* ``symbiflow_route``
* ``symbiflow_write_fasm``
* ``symbiflow_write_bitstream``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_QLSYMBIFLOW``, if present.
Available overrides:
* ``add_constraints``: inserts commands in XDC file.
"""
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
# Since the QuickLogic version of SymbiFlow toolchain is not upstreamed yet
# we should distinguish the QuickLogic version from mainline one.
# QuickLogic toolchain: https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases
toolchain = "QLSymbiflow"
required_tools = [
"symbiflow_synth",
"symbiflow_pack",
"symbiflow_place",
"symbiflow_route",
"symbiflow_write_fasm",
"symbiflow_write_bitstream",
"symbiflow_write_openocd",
]
file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
"{{name}}.xdc": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
{% for attr_name, attr_value in attrs.items() -%}
set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }]
{% endfor %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.sdc": r"""
# {{autogenerated}}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -period {{100000000/frequency}} {{port_signal.name|ascii_escape}}
{% endif %}
{% endfor %}
"""
}
command_templates = [
r"""
{{invoke_tool("symbiflow_synth")}}
-t {{name}}
-v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v
-d {{platform.device}}
-p {{name}}.pcf
-P {{platform.package}}
-x {{name}}.xdc
""",
r"""
{{invoke_tool("symbiflow_pack")}}
-e {{name}}.eblif
-d {{platform.device}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_place")}}
-e {{name}}.eblif
-d {{platform.device}}
-p {{name}}.pcf
-n {{name}}.net
-P {{platform.package}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_route")}}
-e {{name}}.eblif
-d {{platform.device}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_write_fasm")}}
-e {{name}}.eblif
-d {{platform.device}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_write_bitstream")}}
-f {{name}}.fasm
-d {{platform.device}}
-P {{platform.package}}
-b {{name}}.bit
""",
# This should be `invoke_tool("symbiflow_write_openocd")`, but isn't because of a bug in
# the QLSymbiflow v1.3.0 toolchain release.
r"""
python3 -m quicklogic_fasm.bitstream_to_openocd
{{name}}.bit
{{name}}.openocd
--osc-freq {{platform.osc_freq}}
--fpga-clk-divider {{platform.osc_div}}
""",
]
# Common logic
@property
def default_clk_constraint(self):
if self.default_clk == "sys_clk0":
return Clock(self.osc_freq / self.osc_div)
return super().default_clk_constraint
def add_clock_constraint(self, clock, frequency):
super().add_clock_constraint(clock, frequency)
clock.attrs["keep"] = "TRUE"
def create_missing_domain(self, name):
if name == "sync" and self.default_clk is not None:
m = Module()
if self.default_clk == "sys_clk0":
if not hasattr(self, "osc_div"):
raise ValueError("OSC divider (osc_div) must be an integer between 2 "
"and 512")
if not isinstance(self.osc_div, int) or self.osc_div < 2 or self.osc_div > 512:
raise ValueError("OSC divider (osc_div) must be an integer between 2 "
"and 512, not {!r}"
.format(self.osc_div))
if not hasattr(self, "osc_freq"):
raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 "
"and 80000000")
if not isinstance(self.osc_freq, int) or self.osc_freq < 2100000 or self.osc_freq > 80000000:
raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 "
"and 80000000, not {!r}"
.format(self.osc_freq))
clk_i = Signal()
sys_clk0 = Signal()
m.submodules += Instance("qlal4s3b_cell_macro",
o_Sys_Clk0=sys_clk0)
m.submodules += Instance("gclkbuff",
o_A=sys_clk0,
o_Z=clk_i)
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
m.domains += ClockDomain("sync")
m.d.comb += ClockSignal("sync").eq(clk_i)
m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
return m

1218
amaranth/vendor/_xilinx.py Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,606 +1,13 @@
from abc import abstractproperty # TODO(amaranth-0.5): remove module
from fractions import Fraction
import re
from ..hdl import * import warnings
from ..lib.cdc import ResetSynchronizer import importlib
from ..build import * from .. import vendor
# Acknowledgments:
# Parts of this file originate from https://github.com/tcjie/Gowin
def __getattr__(name):
__all__ = ["GowinPlatform"] if name in ("GowinPlatform",):
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
DeprecationWarning, stacklevel=2)
class GowinPlatform(TemplatedPlatform): return getattr(vendor, name)
""" raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
.. rubric:: Apicula toolchain
Required tools:
* ``yosys``
* ``nextpnr-gowin``
* ``gowin_pack``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_APICULA``, if present.
Build products:
* ``{{name}}.fs``: binary bitstream.
.. rubric:: Gowin toolchain
Required tools:
* ``gw_sh``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_GOWIN``, if present.
Build products:
* ``{{name}}.fs``: binary bitstream.
"""
toolchain = None # selected when creating platform
part = abstractproperty()
family = abstractproperty()
def parse_part(self):
# These regular expressions match all >900 parts of Gowin device_info.csv
reg_series = r"(GW[12]{1}[AN]{1}[EFNRSZ]{0,3})-"
reg_voltage = r"(ZV|EV|LV|LX|UV|UX)"
reg_size = r"(1|2|4|9|18|55)"
reg_subseries = r"(?:(B|C|S|X|P5)?)"
reg_package = r"((?:PG|UG|EQ|LQ|MG|M|QN|CS|FN)(?:\d+)(?:P?)(?:A|E|M|CF|C|D|G|H|F|S|T|U|X)?)"
reg_speed = r"((?:C\d{1}/I\d{1})|ES|A\d{1}|I\d{1})"
match = re.match(reg_series+reg_voltage+reg_size+reg_subseries+reg_package+reg_speed+"$",
self.part)
if not match:
raise ValueError("Supplied part name is invalid")
self.series = match.group(1)
self.voltage = match.group(2)
self.size = match.group(3)
self.subseries = match.group(4) or ""
self.package = match.group(5)
self.speed = match.group(6)
match = re.match(reg_series+reg_size+reg_subseries+"$", self.family)
if not match:
raise ValueError("Supplied device family name is invalid")
self.series_f = match.group(1)
self.size_f = match.group(2)
self.subseries_f = match.group(3) or ""
# subseries_f is usually more reliable than subseries.
if self.series != self.series_f:
raise ValueError("Series extracted from supplied part name does not match "
"supplied family series")
if self.size != self.size_f:
raise ValueError("Size extracted from supplied part name does not match "
"supplied family size")
# _chipdb_device is tied to available chipdb-*.bin files of nextpnr-gowin
@property
def _chipdb_device(self):
# GW1NR series does not have its own chipdb file, but works with GW1N
if self.series == "GW1NR":
return "GW1N-{}{}".format(self.size, self.subseries_f)
return self.family
_dev_osc_mapping = {
"GW1N-1" : "OSCH",
"GW1N-1P5" : "OSCO",
"GW1N-1P5B" : "OSCO",
"GW1N-1S" : "OSCH",
"GW1N-2" : "OSCO",
"GW1N-2B" : "OSCO",
"GW1N-4" : "OSC",
"GW1N-4B" : "OSC",
"GW1N-9" : "OSC",
"GW1N-9C" : "OSC",
"GW1NR-1" : "OSCH",
"GW1NR-2" : "OSCO",
"GW1NR-2B" : "OSCO",
"GW1NR-4" : "OSC",
"GW1NR-4B" : "OSC",
"GW1NR-9" : "OSC",
"GW1NR-9C" : "OSC",
"GW1NRF-4B" : "OSC",
"GW1NS-2" : "OSCF",
"GW1NS-2C" : "OSCF",
"GW1NS-4" : "OSCZ",
"GW1NS-4C" : "OSCZ",
"GW1NSE-2C" : "OSCF",
"GW1NSER-4C" : "OSCZ",
"GW1NSR-2" : "OSCF",
"GW1NSR-2C" : "OSCF",
"GW1NSR-4" : "OSCZ",
"GW1NSR-4C" : "OSCZ",
"GW1NZ-1" : "OSCZ",
"GW1NZ-1C" : "OSCZ",
"GW2A-18" : "OSC",
"GW2A-18C" : "OSC",
"GW2A-55" : "OSC",
"GW2A-55C" : "OSC",
"GW2AN-18X" : "OSCW",
"GW2AN-55C" : "OSC",
"GW2AN-9X" : "OSCW",
"GW2ANR-18C" : "OSC",
"GW2AR-18" : "OSC",
"GW2AR-18C" : "OSC"
}
@property
def _osc_type(self):
if self.family in self._dev_osc_mapping:
return self._dev_osc_mapping[self.family]
raise NotImplementedError("Device family {} does not have an assigned oscillator type"
.format(self.family))
@property
def _osc_base_freq(self):
osc = self._osc_type
if osc == "OSC":
if self.speed == 4 and self.subseries_f in ("B", "D"):
return 210_000_000
else:
return 250_000_000
elif osc in ("OSCZ", "OSCO"):
if self.series == "GW1NSR" and self.speed == "C7/I6":
return 260_000_000
else:
return 250_000_000
elif osc in ("OSCF", "OSCH"):
return 240_000_000
elif osc == "OSCW":
return 200_000_000
else:
assert False
@property
def _osc_div(self):
div_min = 2
div_max = 128
div_step = 2
div_frac = Fraction(self._osc_base_freq, self.osc_frequency)
if div_frac.denominator != 1 or div_frac not in range(div_min, div_max, div_step):
raise ValueError(
"On-chip oscillator frequency (platform.osc_frequency) must be chosen such that "
"the oscillator divider, calculated as ({}/{}), is an integer between {} and {} in "
"steps of {}"
.format(div_frac.numerator, div_frac.denominator, div_min, div_max, div_step))
return div_frac.numerator
# Common templates
_common_file_templates = {
"{{name}}.cst": r"""
// {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
IO_LOC "{{port_name}}" {{pin_name}};
{% for attr_name, attr_value in attrs.items() -%}
IO_PORT "{{port_name}}" {{attr_name}}={{attr_value}};
{% endfor %}
{% endfor %}
""",
}
# Apicula templates
_apicula_required_tools = [
"yosys",
"nextpnr-gowin",
"gowin_pack"
]
_apicula_file_templates = {
**TemplatedPlatform.build_script_templates,
**_common_file_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_gowin {{get_override("synth_opts")|options}} -top {{name}} -json {{name}}.syn.json
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
""",
}
_apicula_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-gowin")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
--device {{platform.part}}
--family {{platform._chipdb_device}}
--json {{name}}.syn.json
--cst {{name}}.cst
--write {{name}}.pnr.json
""",
r"""
{{invoke_tool("gowin_pack")}}
-d {{platform._chipdb_device}}
-o {{name}}.fs
{{get_override("gowin_pack_opts")|options}}
{{name}}.pnr.json
"""
]
# Vendor toolchain templates
_gowin_required_tools = ["gw_sh"]
_gowin_file_templates = {
**TemplatedPlatform.build_script_templates,
**_common_file_templates,
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.tcl": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v",".sv",".vhd",".vhdl") -%}
add_file {{file}}
{% endfor %}
add_file -type verilog {{name}}.v
add_file -type cst {{name}}.cst
add_file -type sdc {{name}}.sdc
set_device -name {{platform.family}} {{platform.part}}
set_option -verilog_std v2001 -print_all_synthesis_warning 1 -show_all_warn 1
{{get_override("add_options")|default("# (add_options placeholder)")}}
run all
file delete -force {{name}}.fs
file copy -force impl/pnr/project.fs {{name}}.fs
""",
"{{name}}.sdc": r"""
// {{autogenerated}}
{% for net_signal,port_signal,frequency in platform.iter_clock_constraints() -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
_gowin_command_templates = [
r"""
{{invoke_tool("gw_sh")}}
{{name}}.tcl
"""
]
def __init__(self, *, toolchain="Apicula"):
super().__init__()
assert toolchain in ("Apicula", "Gowin")
self.toolchain = toolchain
self.parse_part()
@property
def required_tools(self):
if self.toolchain == "Apicula":
return self._apicula_required_tools
elif self.toolchain == "Gowin":
return self._gowin_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "Apicula":
return self._apicula_file_templates
elif self.toolchain == "Gowin":
return self._gowin_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "Apicula":
return self._apicula_command_templates
elif self.toolchain == "Gowin":
return self._gowin_command_templates
assert False
def add_clock_constraint(self, clock, frequency):
super().add_clock_constraint(clock, frequency)
clock.attrs["keep"] = "true"
@property
def default_clk_constraint(self):
if self.default_clk == "OSC":
if not hasattr(self, "osc_frequency"):
raise AttributeError(
"Using the on-chip oscillator as the default clock source requires "
"the platform.osc_frequency attribute to be set")
return Clock(self.osc_frequency)
# Use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
if name == "sync" and self.default_clk is not None:
m = Module()
if self.default_clk == "OSC":
clk_i = Signal()
if self._osc_type == "OSCZ":
m.submodules += Instance(self._osc_type,
p_FREQ_DIV=self._osc_div,
i_OSCEN=Const(1),
o_OSCOUT=clk_i)
elif self._osc_type == "OSCO":
# TODO: Make use of regulator configurable
m.submodules += Instance(self._osc_type,
p_REGULATOR_EN=Const(1),
p_FREQ_DIV=self._osc_div,
i_OSCEN=Const(1),
o_OSCOUT=clk_i)
elif self._osc_type == "OSCF":
m.submodules += Instance(self._osc_type,
p_FREQ_DIV=self._osc_div,
o_OSCOUT30M=None,
o_OSCOUT=clk_i)
else:
m.submodules += Instance(self._osc_type,
p_FREQ_DIV=self._osc_div,
o_OSCOUT=clk_i)
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
m.domains += ClockDomain("sync")
m.d.comb += ClockSignal("sync").eq(clk_i)
return m
def _get_xdr_buffer(self, m, pin, i_invert=False, o_invert=False):
def get_ireg(clk,d,q):
for bit in range(len(q)):
m.submodules += Instance("DFF",
i_CLK=clk,
i_D=d[bit],
o_Q=q[bit],
)
def get_oreg(clk,d,q):
for bit in range(len(q)):
m.submodules += Instance("DFF",
i_CLK=clk,
i_D=d[bit],
o_Q=q[bit]
)
def get_iddr(clk,d,q0,q1):
for bit in range(len(d)):
m.submodules += Instance("IDDR",
i_CLK=clk,
i_D=d[bit],
o_Q0=q0[bit],
o_Q1=q1[bit]
)
def get_oddr(clk,d0,d1,q):
for bit in range(len(q)):
m.submodules += Instance("ODDR",
p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK
i_CLK=clk,
i_D0=d0[bit],
i_D1=d1[bit],
o_Q0=q[bit]
)
def get_oeddr(clk,d0,d1,tx,q0,q1):
for bit in range(len(q0)):
m.submodules += Instance("ODDR",
p_TXCLK_POL=0, # default -> Q1 changes on posedge of CLK
i_CLK=clk,
i_D0=d0[bit],
i_D1=d1[bit],
i_TX=tx,
o_Q0=q0[bit],
o_Q1=q1
)
def get_ineg(y, invert):
if invert:
a = Signal.like(y, name_suffix="_n")
m.d.comb += y.eq(~a)
return a
else:
return y
def get_oneg(a, invert):
if invert:
y = Signal.like(a, name_suffix="_n")
m.d.comb += y.eq(~a)
return y
else:
return a
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)
i = o = t = None
if "i" in pin.dir:
i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
if "o" in pin.dir:
o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
if pin.dir in ("oe", "io"):
t = Signal(1, name="{}_xdr_t".format(pin.name))
if pin.xdr == 0:
if "i" in pin.dir:
i = pin_i
if "o" in pin.dir:
o = pin_o
if pin.dir in ("oe", "io"):
t = ~pin.oe
elif pin.xdr == 1:
if "i" in pin.dir:
get_ireg(pin.i_clk, i, pin_i)
if "o" in pin.dir:
get_oreg(pin.o_clk, pin_o, o)
if pin.dir in ("oe", "io"):
get_oreg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 2:
if "i" in pin.dir:
get_iddr(pin.i_clk, i, pin_i0, pin_i1)
if pin.dir in ("o",):
get_oddr(pin.o_clk, pin_o0, pin_o1, o)
if pin.dir in ("oe", "io"):
get_oeddr(pin.o_clk, pin_o0, pin_o1, ~pin.oe, o, t)
else:
assert False
return (i, o, t)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IBUF",
i_I=port.io[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, port.io, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBUF",
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("TBUF",
i_OEN=t,
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IOBUF",
i_OEN=t,
i_I=o[bit],
o_O=i[bit],
io_IO=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.wodth):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IBUF",
i_I=port.p[bit],
i_IB=port.n[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_OBUF",
i_I=o[bit],
o_O=port.p[bit],
o_OB=port.n[bit],
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_TBUF",
i_OEN=t,
i_I=o[bit],
o_O=port.p[bit],
o_OB=port.n[bit]
)
return m
def get_diff_input_output(self, pin, port, atttr, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name,bit)] = Instance("TLVDS_IOBUF",
i_OEN=t,
i_I=o[bit],
o_O=i[bit],
io_IO=port.p[bit],
io_IOB=port.n[bit]
)
return m

View file

@ -1,571 +1,13 @@
from abc import abstractmethod # TODO(amaranth-0.5): remove module
from ..hdl import * import warnings
from ..build import * import importlib
from .. import vendor
__all__ = ["IntelPlatform"] def __getattr__(name):
if name in ("IntelPlatform",):
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
class IntelPlatform(TemplatedPlatform): DeprecationWarning, stacklevel=2)
""" return getattr(vendor, name)
.. rubric:: Quartus toolchain raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Required tools:
* ``quartus_map``
* ``quartus_fit``
* ``quartus_asm``
* ``quartus_sta``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_QUARTUS``, if present.
Available overrides:
* ``add_settings``: inserts commands at the end of the QSF file.
* ``add_constraints``: inserts commands at the end of the SDC file.
* ``nproc``: sets the number of cores used by all tools.
* ``quartus_map_opts``: adds extra options for ``quartus_map``.
* ``quartus_fit_opts``: adds extra options for ``quartus_fit``.
* ``quartus_asm_opts``: adds extra options for ``quartus_asm``.
* ``quartus_sta_opts``: adds extra options for ``quartus_sta``.
Build products:
* ``*.rpt``: toolchain reports.
* ``{{name}}.sof``: bitstream as SRAM object file.
* ``{{name}}.rbf``: bitstream as raw binary file.
.. rubric:: Mistral toolchain
Required tools:
* ``yosys``
* ``nextpnr-mistral``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_MISTRAL``, if present.
* ``verbose``: enables logging of informational messages to standard error.
* ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
* ``synth_opts``: adds options for ``synth_intel_alm`` Yosys command.
* ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
* ``script_after_synth``: inserts commands after ``synth_intel_alm`` in Yosys script.
* ``yosys_opts``: adds extra options for ``yosys``.
* ``nextpnr_opts``: adds extra options for ``nextpnr-mistral``.
"""
toolchain = None # selected when creating platform
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
speed = property(abstractmethod(lambda: None))
suffix = ""
# Quartus templates
quartus_suppressed_warnings = [
10264, # All case item expressions in this case statement are onehot
10270, # Incomplete Verilog case statement has no default case item
10335, # Unrecognized synthesis attribute
10763, # Verilog case statement has overlapping case item expressions with non-constant or don't care bits
10935, # Verilog casex/casez overlaps with a previous casex/vasez item expression
12125, # Using design file which is not specified as a design file for the current project, but contains definitions used in project
18236, # Number of processors not specified in QSF
292013, # Feature is only available with a valid subscription license
]
quartus_required_tools = [
"quartus_map",
"quartus_fit",
"quartus_asm",
"quartus_sta",
]
quartus_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
QUARTUS_ROOTDIR=$(dirname $(dirname "${{var}}"))
# Quartus' qenv.sh does not work with `set -e`.
. "${{var}}"
fi
{% endfor %}
set -e{{verbose("x")}}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.qsf": r"""
# {{autogenerated}}
{% if get_override("nproc") -%}
set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}}
{% endif %}
{% for file in platform.iter_files(".v") -%}
set_global_assignment -name VERILOG_FILE {{file|tcl_quote}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
set_global_assignment -name SYSTEMVERILOG_FILE {{file|tcl_quote}}
{% endfor %}
{% for file in platform.iter_files(".vhd", ".vhdl") -%}
set_global_assignment -name VHDL_FILE {{file|tcl_quote}}
{% endfor %}
set_global_assignment -name VERILOG_FILE {{name}}.v
set_global_assignment -name TOP_LEVEL_ENTITY {{name}}
set_global_assignment -name DEVICE {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}}
{% for key, value in attrs.items() -%}
set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}}
{% endfor %}
{% endfor %}
set_global_assignment -name GENERATE_RBF_FILE ON
{{get_override("add_settings")|default("# (add_settings placeholder)")}}
""",
"{{name}}.sdc": r"""
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_quote}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_quote}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("|")|tcl_quote}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.srf": r"""
{% for warning in platform.quartus_suppressed_warnings %}
{ "" "" "" "{{name}}.v" { } { } 0 {{warning}} "" 0 0 "Design Software" 0 -1 0 ""}
{% endfor %}
""",
}
quartus_command_templates = [
r"""
{{invoke_tool("quartus_map")}}
{{get_override("quartus_map_opts")|options}}
--rev={{name}} {{name}}
""",
r"""
{{invoke_tool("quartus_fit")}}
{{get_override("quartus_fit_opts")|options}}
--rev={{name}} {{name}}
""",
r"""
{{invoke_tool("quartus_asm")}}
{{get_override("quartus_asm_opts")|options}}
--rev={{name}} {{name}}
""",
r"""
{{invoke_tool("quartus_sta")}}
{{get_override("quartus_sta_opts")|options}}
--rev={{name}} {{name}}
""",
]
# Mistral templates
mistral_required_tools = [
"yosys",
"nextpnr-mistral"
]
mistral_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_intel_alm {{get_override("synth_opts")|options}} -top {{name}}
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
write_json {{name}}.json
""",
"{{name}}.qsf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_location_assignment -to {{port_name|tcl_quote}} PIN_{{pin_name}}
{% for key, value in attrs.items() -%}
set_instance_assignment -to {{port_name|tcl_quote}} -name {{key}} {{value|tcl_quote}}
{% endfor %}
{% endfor %}
""",
}
mistral_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-mistral")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
--device {{platform.device}}{{platform.package}}{{platform.speed}}{{platform.suffix}}
--json {{name}}.json
--qsf {{name}}.qsf
--rbf {{name}}.rbf
"""
]
# Common logic
def __init__(self, *, toolchain="Quartus"):
super().__init__()
assert toolchain in ("Quartus", "Mistral")
self.toolchain = toolchain
@property
def required_tools(self):
if self.toolchain == "Quartus":
return self.quartus_required_tools
if self.toolchain == "Mistral":
return self.mistral_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "Quartus":
return self.quartus_file_templates
if self.toolchain == "Mistral":
return self.mistral_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "Quartus":
return self.quartus_command_templates
if self.toolchain == "Mistral":
return self.mistral_command_templates
assert False
def add_clock_constraint(self, clock, frequency):
super().add_clock_constraint(clock, frequency)
clock.attrs["keep"] = "true"
@property
def default_clk_constraint(self):
# Internal high-speed oscillator on Cyclone V devices.
# It is specified to not be faster than 100MHz, but the actual
# frequency seems to vary a lot between devices. Measurements
# of 78 to 84 MHz have been observed.
if self.default_clk == "cyclonev_oscillator":
assert self.device.startswith("5C")
return Clock(100e6)
# Otherwise, use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
if name == "sync" and self.default_clk == "cyclonev_oscillator":
# Use the internal high-speed oscillator for Cyclone V devices
assert self.device.startswith("5C")
m = Module()
m.domains += ClockDomain("sync")
m.submodules += Instance("cyclonev_oscillator",
i_oscena=Const(1),
o_clkout=ClockSignal("sync"))
return m
else:
return super().create_missing_domain(name)
# The altiobuf_* and altddio_* primitives are explained in the following Intel documents:
# * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altiobuf.pdf
# * https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/ug/ug_altddio.pdf
# See also errata mentioned in: https://www.intel.com/content/www/us/en/programmable/support/support-resources/knowledge-base/solutions/rd11192012_735.html.
@staticmethod
def _get_ireg(m, pin, invert):
def get_ineg(i):
if invert:
i_neg = Signal.like(i, name_suffix="_neg")
m.d.comb += i.eq(~i_neg)
return i_neg
else:
return i
if pin.xdr == 0:
return get_ineg(pin.i)
elif pin.xdr == 1:
i_sdr = Signal(pin.width, name="{}_i_sdr")
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=pin.width,
i_CLK=pin.i_clk,
i_D=i_sdr,
o_Q=get_ineg(pin.i),
)
return i_sdr
elif pin.xdr == 2:
i_ddr = Signal(pin.width, name="{}_i_ddr".format(pin.name))
m.submodules["{}_i_ddr".format(pin.name)] = Instance("altddio_in",
p_width=pin.width,
i_datain=i_ddr,
i_inclock=pin.i_clk,
o_dataout_h=get_ineg(pin.i0),
o_dataout_l=get_ineg(pin.i1),
)
return i_ddr
assert False
@staticmethod
def _get_oreg(m, pin, invert):
def get_oneg(o):
if invert:
o_neg = Signal.like(o, name_suffix="_neg")
m.d.comb += o_neg.eq(~o)
return o_neg
else:
return o
if pin.xdr == 0:
return get_oneg(pin.o)
elif pin.xdr == 1:
o_sdr = Signal(pin.width, name="{}_o_sdr".format(pin.name))
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=pin.width,
i_CLK=pin.o_clk,
i_D=get_oneg(pin.o),
o_Q=o_sdr,
)
return o_sdr
elif pin.xdr == 2:
o_ddr = Signal(pin.width, name="{}_o_ddr".format(pin.name))
m.submodules["{}_o_ddr".format(pin.name)] = Instance("altddio_out",
p_width=pin.width,
o_dataout=o_ddr,
i_outclock=pin.o_clk,
i_datain_h=get_oneg(pin.o0),
i_datain_l=get_oneg(pin.o1),
)
return o_ddr
assert False
@staticmethod
def _get_oereg(m, pin):
# altiobuf_ requires an output enable signal for each pin, but pin.oe is 1 bit wide.
if pin.xdr == 0:
return pin.oe.replicate(pin.width)
elif pin.xdr in (1, 2):
oe_reg = Signal(pin.width, name="{}_oe_reg".format(pin.name))
oe_reg.attrs["useioff"] = "1"
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=pin.width,
i_CLK=pin.o_clk,
i_D=pin.oe,
o_Q=oe_reg,
)
return oe_reg
assert False
def get_input(self, pin, port, attrs, invert):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_in",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
i_datain=port.io,
o_dataout=self._get_ireg(m, pin, 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)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
p_use_oe="FALSE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.io,
)
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)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
p_use_oe="TRUE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.io,
i_oe=self._get_oereg(m, pin)
)
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)
if pin.xdr == 1:
port.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_bidir",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="FALSE",
i_datain=self._get_oreg(m, pin, invert),
io_dataio=port.io,
o_dataout=self._get_ireg(m, pin, invert),
i_oe=self._get_oereg(m, pin),
)
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)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_in",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
i_datain=port.p,
i_datain_b=port.n,
o_dataout=self._get_ireg(m, pin, 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)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
p_use_oe="FALSE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.p,
o_dataout_b=port.n,
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_out",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
p_use_oe="TRUE",
i_datain=self._get_oreg(m, pin, invert),
o_dataout=port.p,
o_dataout_b=port.n,
i_oe=self._get_oereg(m, pin),
)
return m
def get_diff_input_output(self, pin, port, attrs, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
if pin.xdr == 1:
port.p.attrs["useioff"] = 1
port.n.attrs["useioff"] = 1
m = Module()
m.submodules[pin.name] = Instance("altiobuf_bidir",
p_enable_bus_hold="FALSE",
p_number_of_channels=pin.width,
p_use_differential_mode="TRUE",
i_datain=self._get_oreg(m, pin, invert),
io_dataio=port.p,
io_dataio_b=port.n,
o_dataout=self._get_ireg(m, pin, invert),
i_oe=self._get_oereg(m, pin),
)
return m
# The altera_std_synchronizer{,_bundle} megafunctions embed SDC constraints that mark false
# paths, so use them instead of our default implementation.
def get_ff_sync(self, ff_sync):
return Instance("altera_std_synchronizer_bundle",
p_width=len(ff_sync.i),
p_depth=ff_sync._stages,
i_clk=ClockSignal(ff_sync._o_domain),
i_reset_n=Const(1),
i_din=ff_sync.i,
o_dout=ff_sync.o,
)
def get_async_ff_sync(self, async_ff_sync):
m = Module()
sync_output = Signal()
if async_ff_sync._edge == "pos":
m.submodules += Instance("altera_std_synchronizer",
p_depth=async_ff_sync._stages,
i_clk=ClockSignal(async_ff_sync._o_domain),
i_reset_n=~async_ff_sync.i,
i_din=Const(1),
o_dout=sync_output,
)
else:
m.submodules += Instance("altera_std_synchronizer",
p_depth=async_ff_sync._stages,
i_clk=ClockSignal(async_ff_sync._o_domain),
i_reset_n=async_ff_sync.i,
i_din=Const(1),
o_dout=sync_output,
)
m.d.comb += async_ff_sync.o.eq(~sync_output)
return m

View file

@ -1,668 +1,13 @@
from abc import abstractmethod # TODO(amaranth-0.5): remove module
from ..hdl import * import warnings
from ..build import * import importlib
from .. import vendor
__all__ = ["LatticeECP5Platform"] def __getattr__(name):
if name in ("LatticeECP5Platform",):
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
class LatticeECP5Platform(TemplatedPlatform): DeprecationWarning, stacklevel=2)
""" return getattr(vendor, name)
.. rubric:: Trellis toolchain raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Required tools:
* ``yosys``
* ``nextpnr-ecp5``
* ``ecppack``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_TRELLIS``, if present.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``read_verilog_opts``: adds options for ``read_verilog`` Yosys command.
* ``synth_opts``: adds options for ``synth_ecp5`` Yosys command.
* ``script_after_read``: inserts commands after ``read_ilang`` in Yosys script.
* ``script_after_synth``: inserts commands after ``synth_ecp5`` in Yosys script.
* ``yosys_opts``: adds extra options for ``yosys``.
* ``nextpnr_opts``: adds extra options for ``nextpnr-ecp5``.
* ``ecppack_opts``: adds extra options for ``ecppack``.
* ``add_preferences``: inserts commands at the end of the LPF file.
Build products:
* ``{{name}}.rpt``: Yosys log.
* ``{{name}}.json``: synthesized RTL.
* ``{{name}}.tim``: nextpnr log.
* ``{{name}}.config``: ASCII bitstream.
* ``{{name}}.bit``: binary bitstream.
* ``{{name}}.svf``: JTAG programming vector.
.. rubric:: Diamond toolchain
Required tools:
* ``pnmainc``
* ``ddtcmd``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond
itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``,
for instance) is known to work::
@echo off
set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH%
Available overrides:
* ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
* ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
* ``add_preferences``: inserts commands at the end of the LPF file.
* ``add_constraints``: inserts commands at the end of the XDC file.
Build products:
* ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
* ``{{name}}.bit``: binary bitstream.
* ``{{name}}.svf``: JTAG programming vector.
"""
toolchain = None # selected when creating platform
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
speed = property(abstractmethod(lambda: None))
grade = "C" # [C]ommercial, [I]ndustrial
# Trellis templates
_nextpnr_device_options = {
"LFE5U-12F": "--12k",
"LFE5U-25F": "--25k",
"LFE5U-45F": "--45k",
"LFE5U-85F": "--85k",
"LFE5UM-25F": "--um-25k",
"LFE5UM-45F": "--um-45k",
"LFE5UM-85F": "--um-85k",
"LFE5UM5G-25F": "--um5g-25k",
"LFE5UM5G-45F": "--um5g-45k",
"LFE5UM5G-85F": "--um5g-85k",
}
_nextpnr_package_options = {
"BG256": "caBGA256",
"MG285": "csfBGA285",
"BG381": "caBGA381",
"BG554": "caBGA554",
"BG756": "caBGA756",
}
_trellis_required_tools = [
"yosys",
"nextpnr-ecp5",
"ecppack"
]
_trellis_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_ecp5 {{get_override("synth_opts")|options}} -top {{name}}
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
write_json {{name}}.json
""",
"{{name}}.lpf": r"""
# {{autogenerated}}
BLOCK ASYNCPATHS;
BLOCK RESETPATHS;
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
{% if attrs -%}
IOBUF PORT "{{port_name}}"
{%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
{% endif %}
{% endfor %}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
FREQUENCY PORT "{{port_signal.name}}" {{frequency}} HZ;
{% else -%}
FREQUENCY NET "{{net_signal|hierarchy(".")}}" {{frequency}} HZ;
{% endif %}
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
"""
}
_trellis_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-ecp5")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
{{platform._nextpnr_device_options[platform.device]}}
--package {{platform._nextpnr_package_options[platform.package]|upper}}
--speed {{platform.speed}}
--json {{name}}.json
--lpf {{name}}.lpf
--textcfg {{name}}.config
""",
r"""
{{invoke_tool("ecppack")}}
{{verbose("--verbose")}}
{{get_override("ecppack_opts")|options}}
--input {{name}}.config
--bit {{name}}.bit
--svf {{name}}.svf
"""
]
# Diamond templates
_diamond_required_tools = [
"pnmainc",
"ddtcmd"
]
_diamond_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
bindir=$(dirname "${{var}}")
. "${{var}}"
fi
{% endfor %}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.tcl": r"""
prj_project new -name {{name}} -impl impl -impl_dir {{name}}_impl \
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
-lpf {{name}}.lpf \
-synthesis synplify
{% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
prj_src add {{file|tcl_escape}}
{% endfor %}
prj_src add {{name}}.v
prj_impl option top {{name}}
prj_src add {{name}}.sdc
{{get_override("script_project")|default("# (script_project placeholder)")}}
prj_project save
prj_run Synthesis -impl impl
prj_run Translate -impl impl
prj_run Map -impl impl
prj_run PAR -impl impl
prj_run Export -impl impl -task Bitgen
{{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
""",
"{{name}}.lpf": r"""
# {{autogenerated}}
BLOCK ASYNCPATHS;
BLOCK RESETPATHS;
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
{% if attrs -%}
IOBUF PORT "{{port_name}}"
{%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
{% endif %}
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
""",
"{{name}}.sdc": r"""
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
_diamond_command_templates = [
# These don't have any usable command-line option overrides.
r"""
{{invoke_tool("pnmainc")}}
{{name}}.tcl
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -bit
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -svfsingle -revd -op "Fast Program"
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.svf
""",
]
# Common logic
def __init__(self, *, toolchain="Trellis"):
super().__init__()
assert toolchain in ("Trellis", "Diamond")
self.toolchain = toolchain
@property
def required_tools(self):
if self.toolchain == "Trellis":
return self._trellis_required_tools
if self.toolchain == "Diamond":
return self._diamond_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "Trellis":
return self._trellis_file_templates
if self.toolchain == "Diamond":
return self._diamond_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "Trellis":
return self._trellis_command_templates
if self.toolchain == "Diamond":
return self._diamond_command_templates
assert False
@property
def default_clk_constraint(self):
if self.default_clk == "OSCG":
return Clock(310e6 / self.oscg_div)
return super().default_clk_constraint
def create_missing_domain(self, name):
# Lattice ECP5 devices have two global set/reset signals: PUR, which is driven at startup
# by the configuration logic and unconditionally resets every storage element, and GSR,
# which is driven by user logic and each storage element may be configured as affected or
# unaffected by GSR. PUR is purely asynchronous, so even though it is a low-skew global
# network, its deassertion may violate a setup/hold constraint with relation to a user
# clock. To avoid this, a GSR/SGSR instance should be driven synchronized to user clock.
if name == "sync" and self.default_clk is not None:
m = Module()
if self.default_clk == "OSCG":
if not hasattr(self, "oscg_div"):
raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
"and 128")
if not isinstance(self.oscg_div, int) or self.oscg_div < 2 or self.oscg_div > 128:
raise ValueError("OSCG divider (oscg_div) must be an integer between 2 "
"and 128, not {!r}"
.format(self.oscg_div))
clk_i = Signal()
m.submodules += Instance("OSCG", p_DIV=self.oscg_div, o_OSC=clk_i)
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
gsr0 = Signal()
gsr1 = Signal()
# There is no end-of-startup signal on ECP5, but PUR is released after IOB enable, so
# a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
m.submodules += [
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0, o_Q=gsr1),
# Although we already synchronize the reset input to user clock, SGSR has dedicated
# clock routing to the center of the FPGA; use that just in case it turns out to be
# more reliable. (None of this is documented.)
Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
]
# GSR implicitly connects to every appropriate storage element. As such, the sync
# domain is reset-less; domains driven by other clocks would need to have dedicated
# reset circuitry or otherwise meet setup/hold constraints on their own.
m.domains += ClockDomain("sync", reset_less=True)
m.d.comb += ClockSignal("sync").eq(clk_i)
return m
_single_ended_io_types = [
"HSUL12", "LVCMOS12", "LVCMOS15", "LVCMOS18", "LVCMOS25", "LVCMOS33", "LVTTL33",
"SSTL135_I", "SSTL135_II", "SSTL15_I", "SSTL15_II", "SSTL18_I", "SSTL18_II",
]
_differential_io_types = [
"BLVDS25", "BLVDS25E", "HSUL12D", "LVCMOS18D", "LVCMOS25D", "LVCMOS33D",
"LVDS", "LVDS25E", "LVPECL33", "LVPECL33E", "LVTTL33D", "MLVDS", "MLVDS25E",
"SLVS", "SSTL135D_I", "SSTL135D_II", "SSTL15D_I", "SSTL15D_II", "SSTL18D_I",
"SSTL18D_II", "SUBLVDS",
]
def should_skip_port_component(self, port, attrs, component):
# On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
# the PIOA or PIOC location, which is always the non-inverting pin.
if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
return True
return False
def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False):
def get_ireg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("IFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_oreg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("OFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_oereg(clk, oe, q):
for bit in range(len(q)):
m.submodules += Instance("OFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=oe,
o_Q=q[bit]
)
def get_iddr(sclk, d, q0, q1):
for bit in range(len(d)):
m.submodules += Instance("IDDRX1F",
i_SCLK=sclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit]
)
def get_iddrx2(sclk, eclk, d, q0, q1, q2, q3):
for bit in range(len(d)):
m.submodules += Instance("IDDRX2F",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit]
)
def get_iddr71b(sclk, eclk, d, q0, q1, q2, q3, q4, q5, q6):
for bit in range(len(d)):
m.submodules += Instance("IDDR71B",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit], o_Q2=q2[bit], o_Q3=q3[bit],
o_Q4=q4[bit], o_Q5=q5[bit], o_Q6=q6[bit],
)
def get_oddr(sclk, d0, d1, q):
for bit in range(len(q)):
m.submodules += Instance("ODDRX1F",
i_SCLK=sclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit],
o_Q=q[bit]
)
def get_oddrx2(sclk, eclk, d0, d1, d2, d3, q):
for bit in range(len(q)):
m.submodules += Instance("ODDRX2F",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit],
o_Q=q[bit]
)
def get_oddr71b(sclk, eclk, d0, d1, d2, d3, d4, d5, d6, q):
for bit in range(len(q)):
m.submodules += Instance("ODDR71B",
i_SCLK=sclk,
i_ECLK=eclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit], i_D2=d2[bit], i_D3=d3[bit],
i_D4=d4[bit], i_D5=d5[bit], i_D6=d6[bit],
o_Q=q[bit]
)
def get_ineg(z, invert):
if invert:
a = Signal.like(z, name_suffix="_n")
m.d.comb += z.eq(~a)
return a
else:
return z
def get_oneg(a, invert):
if invert:
z = Signal.like(a, name_suffix="_n")
m.d.comb += z.eq(~a)
return z
else:
return a
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)
elif pin.xdr == 4:
pin_i0 = get_ineg(pin.i0, i_invert)
pin_i1 = get_ineg(pin.i1, i_invert)
pin_i2 = get_ineg(pin.i2, i_invert)
pin_i3 = get_ineg(pin.i3, i_invert)
elif pin.xdr == 7:
pin_i0 = get_ineg(pin.i0, i_invert)
pin_i1 = get_ineg(pin.i1, i_invert)
pin_i2 = get_ineg(pin.i2, i_invert)
pin_i3 = get_ineg(pin.i3, i_invert)
pin_i4 = get_ineg(pin.i4, i_invert)
pin_i5 = get_ineg(pin.i5, i_invert)
pin_i6 = get_ineg(pin.i6, 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)
elif pin.xdr == 4:
pin_o0 = get_oneg(pin.o0, o_invert)
pin_o1 = get_oneg(pin.o1, o_invert)
pin_o2 = get_oneg(pin.o2, o_invert)
pin_o3 = get_oneg(pin.o3, o_invert)
elif pin.xdr == 7:
pin_o0 = get_oneg(pin.o0, o_invert)
pin_o1 = get_oneg(pin.o1, o_invert)
pin_o2 = get_oneg(pin.o2, o_invert)
pin_o3 = get_oneg(pin.o3, o_invert)
pin_o4 = get_oneg(pin.o4, o_invert)
pin_o5 = get_oneg(pin.o5, o_invert)
pin_o6 = get_oneg(pin.o6, o_invert)
i = o = t = None
if "i" in pin.dir:
i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
if "o" in pin.dir:
o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
if pin.dir in ("oe", "io"):
t = Signal(pin.width, name="{}_xdr_t".format(pin.name))
if pin.xdr == 0:
if "i" in pin.dir:
i = pin_i
if "o" in pin.dir:
o = pin_o
if pin.dir in ("oe", "io"):
t = (~pin.oe).replicate(pin.width)
elif pin.xdr == 1:
if "i" in pin.dir:
get_ireg(pin.i_clk, i, pin_i)
if "o" in pin.dir:
get_oreg(pin.o_clk, pin_o, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 2:
if "i" in pin.dir:
get_iddr(pin.i_clk, i, pin_i0, pin_i1)
if "o" in pin.dir:
get_oddr(pin.o_clk, pin_o0, pin_o1, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 4:
if "i" in pin.dir:
get_iddrx2(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3)
if "o" in pin.dir:
get_oddrx2(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 7:
if "i" in pin.dir:
get_iddr71b(pin.i_clk, pin.i_fclk, i, pin_i0, pin_i1, pin_i2, pin_i3, pin_i4, pin_i5, pin_i6)
if "o" in pin.dir:
get_oddr71b(pin.o_clk, pin.o_fclk, pin_o0, pin_o1, pin_o2, pin_o3, pin_o4, pin_o5, pin_o6, o)
if pin.dir in ("oe", "io"):
get_oereg(pin.o_clk, ~pin.oe, t)
else:
assert False
return (i, o, t)
def get_input(self, pin, port, attrs, invert):
self._check_feature("single-ended input", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.io[bit],
o_O=i[bit]
)
return m
def get_output(self, pin, port, attrs, invert):
self._check_feature("single-ended output", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.io[bit]
)
return m
def get_tristate(self, pin, port, attrs, invert):
self._check_feature("single-ended tristate", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t[bit],
i_I=o[bit],
o_O=port.io[bit]
)
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, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t[bit],
i_I=o[bit],
o_O=i[bit],
io_B=port.io[bit]
)
return m
def get_diff_input(self, pin, port, attrs, invert):
self._check_feature("differential input", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.p[bit],
o_O=i[bit]
)
return m
def get_diff_output(self, pin, port, attrs, invert):
self._check_feature("differential output", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t[bit],
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_input_output(self, pin, port, attrs, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2, 4, 7), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t[bit],
i_I=o[bit],
o_O=i[bit],
io_B=port.p[bit],
)
return m
# CDC primitives are not currently specialized for ECP5.
# While Diamond supports false path constraints; nextpnr-ecp5 does not.

View file

@ -1,627 +1,13 @@
from abc import abstractmethod # TODO(amaranth-0.5): remove module
from ..hdl import * import warnings
from ..lib.cdc import ResetSynchronizer import importlib
from ..build import * from .. import vendor
__all__ = ["LatticeICE40Platform"] def __getattr__(name):
if name in ("LatticeICE40Platform",):
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
class LatticeICE40Platform(TemplatedPlatform): DeprecationWarning, stacklevel=2)
""" return getattr(vendor, name)
.. rubric:: IceStorm toolchain raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Required tools:
* ``yosys``
* ``nextpnr-ice40``
* ``icepack``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_ICESTORM``, if present.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``read_verilog_opts``: adds options for ``read_verilog`` 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 options for ``nextpnr-ice40``.
* ``add_pre_pack``: inserts commands at the end in pre-pack Python script.
* ``add_constraints``: inserts commands at the end in the PCF file.
Build products:
* ``{{name}}.rpt``: Yosys log.
* ``{{name}}.json``: synthesized RTL.
* ``{{name}}.tim``: nextpnr log.
* ``{{name}}.asc``: ASCII bitstream.
* ``{{name}}.bin``: binary bitstream.
.. rubric:: iCECube2 toolchain
This toolchain comes in two variants: ``LSE-iCECube2`` and ``Synplify-iCECube2``.
Required tools:
* iCECube2 toolchain
* ``tclsh``
The environment is populated by setting the necessary environment variables based on
``AMARANTH_ENV_ICECUBE2``, which must point to the root of the iCECube2 installation, and
is required.
Available overrides:
* ``verbose``: enables logging of informational messages to standard error.
* ``lse_opts``: adds options for LSE.
* ``script_after_add``: inserts commands after ``add_file`` in Synplify Tcl script.
* ``script_after_options``: inserts commands after ``set_option`` in Synplify Tcl script.
* ``add_constraints``: inserts commands in SDC file.
* ``script_after_flow``: inserts commands after ``run_sbt_backend_auto`` in SBT
Tcl script.
Build products:
* ``{{name}}_lse.log`` (LSE) or ``{{name}}_design/{{name}}.htm`` (Synplify): synthesis log.
* ``sbt/outputs/router/{{name}}_timing.rpt``: timing report.
* ``{{name}}.edf``: EDIF netlist.
* ``{{name}}.bin``: binary bitstream.
"""
toolchain = None # selected when creating platform
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
# IceStorm templates
_nextpnr_device_options = {
"iCE40LP384": "--lp384",
"iCE40LP1K": "--lp1k",
"iCE40LP4K": "--lp8k",
"iCE40LP8K": "--lp8k",
"iCE40HX1K": "--hx1k",
"iCE40HX4K": "--hx8k",
"iCE40HX8K": "--hx8k",
"iCE40UP5K": "--up5k",
"iCE40UP3K": "--up5k",
"iCE5LP4K": "--u4k",
"iCE5LP2K": "--u4k",
"iCE5LP1K": "--u4k",
}
_nextpnr_package_options = {
"iCE40LP4K": ":4k",
"iCE40HX4K": ":4k",
"iCE40UP3K": "",
"iCE5LP2K": "",
"iCE5LP1K": "",
}
_icestorm_required_tools = [
"yosys",
"nextpnr-ice40",
"icepack",
]
_icestorm_file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.il": r"""
# {{autogenerated}}
{{emit_rtlil()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.ys": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v") -%}
read_verilog {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".sv") -%}
read_verilog -sv {{get_override("read_verilog_opts")|options}} {{file}}
{% endfor %}
{% for file in platform.iter_files(".il") -%}
read_ilang {{file}}
{% endfor %}
read_ilang {{name}}.il
delete w:$verilog_initial_trigger
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
synth_ice40 {{get_override("synth_opts")|options}} -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, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
set_frequency {{net_signal|hierarchy(".")}} {{frequency/1000000}}
{% endfor%}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
_icestorm_command_templates = [
r"""
{{invoke_tool("yosys")}}
{{quiet("-q")}}
{{get_override("yosys_opts")|options}}
-l {{name}}.rpt
{{name}}.ys
""",
r"""
{{invoke_tool("nextpnr-ice40")}}
{{quiet("--quiet")}}
{{get_override("nextpnr_opts")|options}}
--log {{name}}.tim
{{platform._nextpnr_device_options[platform.device]}}
--package
{{platform.package|lower}}{{platform._nextpnr_package_options[platform.device]|
default("")}}
--json {{name}}.json
--pcf {{name}}.pcf
--asc {{name}}.asc
""",
r"""
{{invoke_tool("icepack")}}
{{verbose("-v")}}
{{name}}.asc
{{name}}.bin
"""
]
# iCECube2 templates
_icecube2_required_tools = [
"synthesis",
"synpwrap",
"tclsh",
]
_icecube2_file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
# LSE environment
export LD_LIBRARY_PATH=${{var}}/LSE/bin/lin64:$LD_LIBRARY_PATH
export PATH=${{var}}/LSE/bin/lin64:$PATH
export FOUNDRY=${{var}}/LSE
# Synplify environment
export LD_LIBRARY_PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$LD_LIBRARY_PATH
export PATH=${{var}}/sbt_backend/bin/linux/opt/synpwrap:$PATH
export SYNPLIFY_PATH=${{var}}/synpbase
# Common environment
export SBT_DIR=${{var}}/sbt_backend
else
echo "Variable ${{platform._toolchain_env_var}} must be set" >&2; exit 1
fi
{% endfor %}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}_lse.prj": r"""
# {{autogenerated}}
-a SBT{{platform.family}}
-d {{platform.device}}
-t {{platform.package}}
{{get_override("lse_opts")|options|default("# (lse_opts placeholder)")}}
{% for file in platform.iter_files(".v") -%}
-ver {{file}}
{% endfor %}
-ver {{name}}.v
-sdc {{name}}.sdc
-top {{name}}
-output_edif {{name}}.edf
-logfile {{name}}_lse.log
""",
"{{name}}_syn.prj": r"""
# {{autogenerated}}
{% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
add_file -verilog {{file|tcl_escape}}
{% endfor %}
add_file -verilog {{name}}.v
add_file -constraint {{name}}.sdc
{{get_override("script_after_add")|default("# (script_after_add placeholder)")}}
impl -add {{name}}_design -type fpga
set_option -technology SBT{{platform.family}}
set_option -part {{platform.device}}
set_option -package {{platform.package}}
{{get_override("script_after_options")|default("# (script_after_options placeholder)")}}
project -result_format edif
project -result_file {{name}}.edf
impl -active {{name}}_design
project -run compile
project -run map
project -run fpga_mapper
file copy -force -- {{name}}_design/{{name}}.edf {{name}}.edf
""",
"{{name}}.sdc": r"""
# {{autogenerated}}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.tcl": r"""
# {{autogenerated}}
set device {{platform.device}}-{{platform.package}}
set top_module {{name}}
set proj_dir .
set output_dir .
set edif_file {{name}}
set tool_options ":edifparser -y {{name}}.pcf"
set sbt_root $::env(SBT_DIR)
append sbt_tcl $sbt_root "/tcl/sbt_backend_synpl.tcl"
source $sbt_tcl
run_sbt_backend_auto $device $top_module $proj_dir $output_dir $tool_options $edif_file
{{get_override("script_after_file")|default("# (script_after_file placeholder)")}}
file copy -force -- sbt/outputs/bitmap/{{name}}_bitmap.bin {{name}}.bin
exit
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
}
_lse_icecube2_command_templates = [
r"""synthesis -f {{name}}_lse.prj""",
r"""tclsh {{name}}.tcl""",
]
_synplify_icecube2_command_templates = [
r"""synpwrap -prj {{name}}_syn.prj -log {{name}}_syn.log""",
r"""tclsh {{name}}.tcl""",
]
# Common logic
def __init__(self, *, toolchain="IceStorm"):
super().__init__()
assert toolchain in ("IceStorm", "LSE-iCECube2", "Synplify-iCECube2")
self.toolchain = toolchain
@property
def family(self):
if self.device.startswith("iCE40"):
return "iCE40"
if self.device.startswith("iCE5"):
return "iCE5"
assert False
@property
def _toolchain_env_var(self):
if self.toolchain == "IceStorm":
return f"AMARANTH_ENV_{self.toolchain}"
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return f"AMARANTH_ENV_ICECUBE2"
assert False
@property
def required_tools(self):
if self.toolchain == "IceStorm":
return self._icestorm_required_tools
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return self._icecube2_required_tools
assert False
@property
def file_templates(self):
if self.toolchain == "IceStorm":
return self._icestorm_file_templates
if self.toolchain in ("LSE-iCECube2", "Synplify-iCECube2"):
return self._icecube2_file_templates
assert False
@property
def command_templates(self):
if self.toolchain == "IceStorm":
return self._icestorm_command_templates
if self.toolchain == "LSE-iCECube2":
return self._lse_icecube2_command_templates
if self.toolchain == "Synplify-iCECube2":
return self._synplify_icecube2_command_templates
assert False
@property
def default_clk_constraint(self):
# Internal high-speed oscillator: 48 MHz / (2 ^ div)
if self.default_clk == "SB_HFOSC":
return Clock(48e6 / 2 ** self.hfosc_div)
# Internal low-speed oscillator: 10 KHz
elif self.default_clk == "SB_LFOSC":
return Clock(10e3)
# Otherwise, use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
# For unknown reasons (no errata was ever published, and no documentation mentions this
# issue), iCE40 BRAMs read as zeroes for ~3 us after configuration and release of internal
# global reset. Note that this is a *time-based* delay, generated purely by the internal
# oscillator, which may not be observed nor influenced directly. For details, see links:
# * https://github.com/cliffordwolf/icestorm/issues/76#issuecomment-289270411
# * https://github.com/cliffordwolf/icotools/issues/2#issuecomment-299734673
#
# To handle this, it is necessary to have a global reset in any iCE40 design that may
# potentially instantiate BRAMs, and assert this reset for >3 us after configuration.
# (We add a margin of 5x to allow for PVT variation.) If the board includes a dedicated
# reset line, this line is ORed with the power on reset.
#
# If an internal oscillator is selected as the default clock source, the power-on-reset
# delay is increased to 100 us, since the oscillators are only stable after that long.
#
# The power-on reset timer counts up because the vendor tools do not support initialization
# of flip-flops.
if name == "sync" and self.default_clk is not None:
m = Module()
# Internal high-speed clock: 6 MHz, 12 MHz, 24 MHz, or 48 MHz depending on the divider.
if self.default_clk == "SB_HFOSC":
if not hasattr(self, "hfosc_div"):
raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
"between 0 and 3")
if not isinstance(self.hfosc_div, int) or self.hfosc_div < 0 or self.hfosc_div > 3:
raise ValueError("SB_HFOSC divider exponent (hfosc_div) must be an integer "
"between 0 and 3, not {!r}"
.format(self.hfosc_div))
clk_i = Signal()
m.submodules += Instance("SB_HFOSC",
i_CLKHFEN=1,
i_CLKHFPU=1,
p_CLKHF_DIV="0b{0:02b}".format(self.hfosc_div),
o_CLKHF=clk_i)
delay = int(100e-6 * self.default_clk_frequency)
# Internal low-speed clock: 10 KHz.
elif self.default_clk == "SB_LFOSC":
clk_i = Signal()
m.submodules += Instance("SB_LFOSC",
i_CLKLFEN=1,
i_CLKLFPU=1,
o_CLKLF=clk_i)
delay = int(100e-6 * self.default_clk_frequency)
# User-defined clock signal.
else:
clk_i = self.request(self.default_clk).i
delay = int(15e-6 * self.default_clk_frequency)
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
# Power-on-reset domain
m.domains += ClockDomain("por", reset_less=True, local=True)
timer = Signal(range(delay))
ready = Signal()
m.d.comb += ClockSignal("por").eq(clk_i)
with m.If(timer == delay):
m.d.por += ready.eq(1)
with m.Else():
m.d.por += timer.eq(timer + 1)
# Primary domain
m.domains += ClockDomain("sync")
m.d.comb += ClockSignal("sync").eq(clk_i)
if self.default_rst is not None:
m.submodules.reset_sync = ResetSynchronizer(~ready | rst_i, domain="sync")
else:
m.d.comb += ResetSignal("sync").eq(~ready)
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):
m.submodules += Instance("$dff",
p_CLK_POLARITY=1,
p_WIDTH=len(d),
i_CLK=clk,
i_D=d,
o_Q=q)
def get_ineg(y, invert):
if invert_lut:
a = Signal.like(y, name_suffix="_x{}".format(1 if invert else 0))
for bit in range(len(y)):
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 a
elif invert:
a = Signal.like(y, name_suffix="_n")
m.d.comb += y.eq(~a)
return a
else:
return y
def get_oneg(a, invert):
if invert_lut:
y = Signal.like(a, name_suffix="_x{}".format(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
if "GLOBAL" in attrs:
is_global_input = bool(attrs["GLOBAL"])
del attrs["GLOBAL"]
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)
for bit in range(len(port)):
io_args = [
("io", "PACKAGE_PIN", port[bit]),
*(("p", key, value) for key, value in attrs.items()),
]
if "i" not in pin.dir:
# 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:
i_type = 0b01 # PIN_INPUT
elif pin.xdr > 0:
i_type = 0b00 # PIN_INPUT_REGISTERED aka PIN_INPUT_DDR
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", 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 is_global_input:
m.submodules["{}_{}".format(pin.name, bit)] = Instance("SB_GB_IO", *io_args)
else:
m.submodules["{}_{}".format(pin.name, 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.
# CDC primitives are not currently specialized for iCE40. It is not known if iCECube2 supports
# the necessary attributes; nextpnr-ice40 does not.

View file

@ -1,456 +1,13 @@
from abc import abstractmethod # TODO(amaranth-0.5): remove module
from ..hdl import * import warnings
from ..build import * import importlib
from .. import vendor
__all__ = ["LatticeMachXO2Platform", "LatticeMachXO3LPlatform"] def __getattr__(name):
if name in ("LatticeMachXO2Platform", "LatticeMachXO3LPlatform"):
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
# MachXO2 and MachXO3L primitives are the same. Handle both using DeprecationWarning, stacklevel=2)
# one class and expose user-aliases for convenience. return getattr(vendor, name)
class LatticeMachXO2Or3LPlatform(TemplatedPlatform): raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
"""
Required tools:
* ``pnmainc``
* ``ddtcmd``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_DIAMOND``, if present. On Linux, diamond_env as provided by Diamond
itself is a good candidate. On Windows, the following script (named ``diamond_env.bat``,
for instance) is known to work::
@echo off
set PATH=C:\\lscc\\diamond\\%DIAMOND_VERSION%\\bin\\nt64;%PATH%
Available overrides:
* ``script_project``: inserts commands before ``prj_project save`` in Tcl script.
* ``script_after_export``: inserts commands after ``prj_run Export`` in Tcl script.
* ``add_preferences``: inserts commands at the end of the LPF file.
* ``add_constraints``: inserts commands at the end of the XDC file.
Build products:
* ``{{name}}_impl/{{name}}_impl.htm``: consolidated log.
* ``{{name}}.jed``: JEDEC fuse file.
* ``{{name}}.bit``: binary bitstream.
* ``{{name}}.svf``: JTAG programming vector for FLASH programming.
* ``{{name}}_flash.svf``: JTAG programming vector for FLASH programming.
* ``{{name}}_sram.svf``: JTAG programming vector for SRAM programming.
"""
toolchain = "Diamond"
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
speed = property(abstractmethod(lambda: None))
grade = "C" # [C]ommercial, [I]ndustrial
required_tools = [
"pnmainc",
"ddtcmd"
]
file_templates = {
**TemplatedPlatform.build_script_templates,
"build_{{name}}.sh": r"""
# {{autogenerated}}
set -e{{verbose("x")}}
if [ -z "$BASH" ] ; then exec /bin/bash "$0" "$@"; fi
{% for var in platform._all_toolchain_env_vars %}
if [ -n "${{var}}" ]; then
bindir=$(dirname "${{var}}")
. "${{var}}"
fi
{% endfor %}
{{emit_commands("sh")}}
""",
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.tcl": r"""
prj_project new -name {{name}} -impl impl -impl_dir {{name}}_impl \
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}} \
-lpf {{name}}.lpf \
-synthesis synplify
{% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%}
prj_src add {{file|tcl_escape}}
{% endfor %}
prj_src add {{name}}.v
prj_impl option top {{name}}
prj_src add {{name}}.sdc
{{get_override("script_project")|default("# (script_project placeholder)")}}
prj_project save
prj_run Synthesis -impl impl
prj_run Translate -impl impl
prj_run Map -impl impl
prj_run PAR -impl impl
prj_run Export -impl impl -task Bitgen
prj_run Export -impl impl -task Jedecgen
{{get_override("script_after_export")|default("# (script_after_export placeholder)")}}
""",
"{{name}}.lpf": r"""
# {{autogenerated}}
BLOCK ASYNCPATHS;
BLOCK RESETPATHS;
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
LOCATE COMP "{{port_name}}" SITE "{{pin_name}}";
{% if attrs -%}
IOBUF PORT "{{port_name}}"
{%- for key, value in attrs.items() %} {{key}}={{value}}{% endfor %};
{% endif %}
{% endfor %}
{{get_override("add_preferences")|default("# (add_preferences placeholder)")}}
""",
"{{name}}.sdc": r"""
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -name {{port_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_ports {{port_signal.name|tcl_escape}}]
{% else -%}
create_clock -name {{net_signal.name|tcl_escape}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("/")|tcl_escape}}]
{% endif %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
}
command_templates = [
# These don't have any usable command-line option overrides.
r"""
{{invoke_tool("pnmainc")}}
{{name}}.tcl
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -bit
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}.bit
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -jed
-dev {{platform.device}}-{{platform.speed}}{{platform.package}}{{platform.grade}}
-if {{name}}_impl/{{name}}_impl.jed -of {{name}}.jed
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -svfsingle -revd -op "FLASH Erase,Program,Verify"
-if {{name}}_impl/{{name}}_impl.jed -of {{name}}_flash.svf
""",
# TODO(amaranth-0.4): remove
r"""
{% if syntax == "bat" -%}
copy {{name}}_flash.svf {{name}}.svf
{% else -%}
cp {{name}}_flash.svf {{name}}.svf
{% endif %}
""",
r"""
{{invoke_tool("ddtcmd")}}
-oft -svfsingle -revd -op "SRAM Fast Program"
-if {{name}}_impl/{{name}}_impl.bit -of {{name}}_sram.svf
""",
]
# These numbers were extracted from
# "MachXO2 sysCLOCK PLL Design and Usage Guide"
_supported_osch_freqs = [
2.08, 2.15, 2.22, 2.29, 2.38, 2.46, 2.56, 2.66, 2.77, 2.89,
3.02, 3.17, 3.33, 3.50, 3.69, 3.91, 4.16, 4.29, 4.43, 4.59,
4.75, 4.93, 5.12, 5.32, 5.54, 5.78, 6.05, 6.33, 6.65, 7.00,
7.39, 7.82, 8.31, 8.58, 8.87, 9.17, 9.50, 9.85, 10.23, 10.64,
11.08, 11.57, 12.09, 12.67, 13.30, 14.00, 14.78, 15.65, 15.65, 16.63,
17.73, 19.00, 20.46, 22.17, 24.18, 26.60, 29.56, 33.25, 38.00, 44.33,
53.20, 66.50, 88.67, 133.00
]
@property
def default_clk_constraint(self):
# Internal high-speed oscillator on MachXO2/MachXO3L devices.
# It can have a range of frequencies.
if self.default_clk == "OSCH":
assert self.osch_frequency in self._supported_osch_freqs
return Clock(int(self.osch_frequency * 1e6))
# Otherwise, use the defined Clock resource.
return super().default_clk_constraint
def create_missing_domain(self, name):
# Lattice MachXO2/MachXO3L devices have two global set/reset signals: PUR, which is driven at
# startup by the configuration logic and unconditionally resets every storage element,
# and GSR, which is driven by user logic and each storage element may be configured as
# affected or unaffected by GSR. PUR is purely asynchronous, so even though it is
# a low-skew global network, its deassertion may violate a setup/hold constraint with
# relation to a user clock. To avoid this, a GSR/SGSR instance should be driven
# synchronized to user clock.
if name == "sync" and self.default_clk is not None:
using_osch = False
if self.default_clk == "OSCH":
using_osch = True
clk_i = Signal()
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
gsr0 = Signal()
gsr1 = Signal()
m = Module()
# There is no end-of-startup signal on MachXO2/MachXO3L, but PUR is released after IOB
# enable, so a simple reset synchronizer (with PUR as the asynchronous reset) does the job.
m.submodules += [
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=~rst_i, o_Q=gsr0),
Instance("FD1S3AX", p_GSR="DISABLED", i_CK=clk_i, i_D=gsr0, o_Q=gsr1),
# Although we already synchronize the reset input to user clock, SGSR has dedicated
# clock routing to the center of the FPGA; use that just in case it turns out to be
# more reliable. (None of this is documented.)
Instance("SGSR", i_CLK=clk_i, i_GSR=gsr1),
]
if using_osch:
osch_freq = self.osch_frequency
if osch_freq not in self._supported_osch_freqs:
raise ValueError("Frequency {!r} is not valid for OSCH clock. Valid frequencies are {!r}"
.format(osch_freq, self._supported_osch_freqs))
osch_freq_param = "{:.2f}".format(float(osch_freq))
m.submodules += [ Instance("OSCH", p_NOM_FREQ=osch_freq_param, i_STDBY=Const(0), o_OSC=clk_i, o_SEDSTDBY=Signal()) ]
# GSR implicitly connects to every appropriate storage element. As such, the sync
# domain is reset-less; domains driven by other clocks would need to have dedicated
# reset circuitry or otherwise meet setup/hold constraints on their own.
m.domains += ClockDomain("sync", reset_less=True)
m.d.comb += ClockSignal("sync").eq(clk_i)
return m
_single_ended_io_types = [
"PCI33", "LVTTL33", "LVCMOS33", "LVCMOS25", "LVCMOS18", "LVCMOS15", "LVCMOS12",
"LVCMOS25R33", "LVCMOS18R33", "LVCMOS18R25", "LVCMOS15R33", "LVCMOS15R25", "LVCMOS12R33",
"LVCMOS12R25", "LVCMOS10R33", "LVCMOS10R25", "SSTL25_I", "SSTL25_II", "SSTL18_I",
"SSTL18_II", "HSTL18_I", "HSTL18_II",
]
_differential_io_types = [
"LVDS25", "LVDS25E", "RSDS25", "RSDS25E", "BLVDS25", "BLVDS25E", "MLVDS25", "MLVDS25E",
"LVPECL33", "LVPECL33E", "SSTL25D_I", "SSTL25D_II", "SSTL18D_I", "SSTL18D_II",
"HSTL18D_I", "HSTL18D_II", "LVTTL33D", "LVCMOS33D", "LVCMOS25D", "LVCMOS18D", "LVCMOS15D",
"LVCMOS12D", "MIPI",
]
def should_skip_port_component(self, port, attrs, component):
# On ECP5, a differential IO is placed by only instantiating an IO buffer primitive at
# the PIOA or PIOC location, which is always the non-inverting pin.
if attrs.get("IO_TYPE", "LVCMOS25") in self._differential_io_types and component == "n":
return True
return False
def _get_xdr_buffer(self, m, pin, *, i_invert=False, o_invert=False):
def get_ireg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("IFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_oreg(clk, d, q):
for bit in range(len(q)):
m.submodules += Instance("OFS1P3DX",
i_SCLK=clk,
i_SP=Const(1),
i_CD=Const(0),
i_D=d[bit],
o_Q=q[bit]
)
def get_iddr(sclk, d, q0, q1):
for bit in range(len(d)):
m.submodules += Instance("IDDRXE",
i_SCLK=sclk,
i_RST=Const(0),
i_D=d[bit],
o_Q0=q0[bit], o_Q1=q1[bit]
)
def get_oddr(sclk, d0, d1, q):
for bit in range(len(q)):
m.submodules += Instance("ODDRXE",
i_SCLK=sclk,
i_RST=Const(0),
i_D0=d0[bit], i_D1=d1[bit],
o_Q=q[bit]
)
def get_ineg(z, invert):
if invert:
a = Signal.like(z, name_suffix="_n")
m.d.comb += z.eq(~a)
return a
else:
return z
def get_oneg(a, invert):
if invert:
z = Signal.like(a, name_suffix="_n")
m.d.comb += z.eq(~a)
return z
else:
return a
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)
i = o = t = None
if "i" in pin.dir:
i = Signal(pin.width, name="{}_xdr_i".format(pin.name))
if "o" in pin.dir:
o = Signal(pin.width, name="{}_xdr_o".format(pin.name))
if pin.dir in ("oe", "io"):
t = Signal(1, name="{}_xdr_t".format(pin.name))
if pin.xdr == 0:
if "i" in pin.dir:
i = pin_i
if "o" in pin.dir:
o = pin_o
if pin.dir in ("oe", "io"):
t = ~pin.oe
elif pin.xdr == 1:
# Note that currently nextpnr will not pack an FF (*FS1P3DX) into the PIO.
if "i" in pin.dir:
get_ireg(pin.i_clk, i, pin_i)
if "o" in pin.dir:
get_oreg(pin.o_clk, pin_o, o)
if pin.dir in ("oe", "io"):
get_oreg(pin.o_clk, ~pin.oe, t)
elif pin.xdr == 2:
if "i" in pin.dir:
get_iddr(pin.i_clk, i, pin_i0, pin_i1)
if "o" in pin.dir:
get_oddr(pin.o_clk, pin_o0, pin_o1, o)
if pin.dir in ("oe", "io"):
# It looks like Diamond will not pack an OREG as a tristate register in a DDR PIO.
# It is not clear what is the recommended set of primitives for this task.
# Similarly, nextpnr will not pack anything as a tristate register in a DDR PIO.
get_oreg(pin.o_clk, ~pin.oe, t)
else:
assert False
return (i, o, t)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.io[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t,
i_I=o[bit],
o_O=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(len(port)):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t,
i_I=o[bit],
o_O=i[bit],
io_B=port.io[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("IB",
i_I=port.p[bit],
o_O=i[bit]
)
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()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OB",
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_tristate(self, pin, port, attrs, invert):
self._check_feature("differential tristate", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("OBZ",
i_T=t,
i_I=o[bit],
o_O=port.p[bit],
)
return m
def get_diff_input_output(self, pin, port, attrs, invert):
self._check_feature("differential input/output", pin, attrs,
valid_xdrs=(0, 1, 2), valid_attrs=True)
m = Module()
i, o, t = self._get_xdr_buffer(m, pin, i_invert=invert, o_invert=invert)
for bit in range(pin.width):
m.submodules["{}_{}".format(pin.name, bit)] = Instance("BB",
i_T=t,
i_I=o[bit],
o_O=i[bit],
io_B=port.p[bit],
)
return m
# CDC primitives are not currently specialized for MachXO2/MachXO3L.
LatticeMachXO2Platform = LatticeMachXO2Or3LPlatform
LatticeMachXO3LPlatform = LatticeMachXO2Or3LPlatform

View file

@ -1,184 +1,13 @@
from abc import abstractmethod # TODO(amaranth-0.5): remove module
from ..hdl import * import warnings
from ..lib.cdc import ResetSynchronizer import importlib
from ..build import * from .. import vendor
__all__ = ["QuicklogicPlatform"] def __getattr__(name):
if name in ("QuicklogicPlatform",):
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
class QuicklogicPlatform(TemplatedPlatform): DeprecationWarning, stacklevel=2)
""" return getattr(vendor, name)
.. rubric:: Symbiflow toolchain raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
Required tools:
* ``symbiflow_synth``
* ``symbiflow_pack``
* ``symbiflow_place``
* ``symbiflow_route``
* ``symbiflow_write_fasm``
* ``symbiflow_write_bitstream``
The environment is populated by running the script specified in the environment variable
``AMARANTH_ENV_QLSYMBIFLOW``, if present.
Available overrides:
* ``add_constraints``: inserts commands in XDC file.
"""
device = property(abstractmethod(lambda: None))
package = property(abstractmethod(lambda: None))
# Since the QuickLogic version of SymbiFlow toolchain is not upstreamed yet
# we should distinguish the QuickLogic version from mainline one.
# QuickLogic toolchain: https://github.com/QuickLogic-Corp/quicklogic-fpga-toolchain/releases
toolchain = "QLSymbiflow"
required_tools = [
"symbiflow_synth",
"symbiflow_pack",
"symbiflow_place",
"symbiflow_route",
"symbiflow_write_fasm",
"symbiflow_write_bitstream",
"symbiflow_write_openocd",
]
file_templates = {
**TemplatedPlatform.build_script_templates,
"{{name}}.v": r"""
/* {{autogenerated}} */
{{emit_verilog()}}
""",
"{{name}}.debug.v": r"""
/* {{autogenerated}} */
{{emit_debug_verilog()}}
""",
"{{name}}.pcf": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
set_io {{port_name}} {{pin_name}}
{% endfor %}
""",
"{{name}}.xdc": r"""
# {{autogenerated}}
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
{% for attr_name, attr_value in attrs.items() -%}
set_property {{attr_name}} {{attr_value}} [get_ports {{port_name|tcl_escape}} }]
{% endfor %}
{% endfor %}
{{get_override("add_constraints")|default("# (add_constraints placeholder)")}}
""",
"{{name}}.sdc": r"""
# {{autogenerated}}
{% for net_signal, port_signal, frequency in platform.iter_clock_constraints() -%}
{% if port_signal is not none -%}
create_clock -period {{100000000/frequency}} {{port_signal.name|ascii_escape}}
{% endif %}
{% endfor %}
"""
}
command_templates = [
r"""
{{invoke_tool("symbiflow_synth")}}
-t {{name}}
-v {% for file in platform.iter_files(".v", ".sv", ".vhd", ".vhdl") -%} {{file}} {% endfor %} {{name}}.v
-d {{platform.device}}
-p {{name}}.pcf
-P {{platform.package}}
-x {{name}}.xdc
""",
r"""
{{invoke_tool("symbiflow_pack")}}
-e {{name}}.eblif
-d {{platform.device}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_place")}}
-e {{name}}.eblif
-d {{platform.device}}
-p {{name}}.pcf
-n {{name}}.net
-P {{platform.package}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_route")}}
-e {{name}}.eblif
-d {{platform.device}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_write_fasm")}}
-e {{name}}.eblif
-d {{platform.device}}
-s {{name}}.sdc
""",
r"""
{{invoke_tool("symbiflow_write_bitstream")}}
-f {{name}}.fasm
-d {{platform.device}}
-P {{platform.package}}
-b {{name}}.bit
""",
# This should be `invoke_tool("symbiflow_write_openocd")`, but isn't because of a bug in
# the QLSymbiflow v1.3.0 toolchain release.
r"""
python3 -m quicklogic_fasm.bitstream_to_openocd
{{name}}.bit
{{name}}.openocd
--osc-freq {{platform.osc_freq}}
--fpga-clk-divider {{platform.osc_div}}
""",
]
# Common logic
@property
def default_clk_constraint(self):
if self.default_clk == "sys_clk0":
return Clock(self.osc_freq / self.osc_div)
return super().default_clk_constraint
def add_clock_constraint(self, clock, frequency):
super().add_clock_constraint(clock, frequency)
clock.attrs["keep"] = "TRUE"
def create_missing_domain(self, name):
if name == "sync" and self.default_clk is not None:
m = Module()
if self.default_clk == "sys_clk0":
if not hasattr(self, "osc_div"):
raise ValueError("OSC divider (osc_div) must be an integer between 2 "
"and 512")
if not isinstance(self.osc_div, int) or self.osc_div < 2 or self.osc_div > 512:
raise ValueError("OSC divider (osc_div) must be an integer between 2 "
"and 512, not {!r}"
.format(self.osc_div))
if not hasattr(self, "osc_freq"):
raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 "
"and 80000000")
if not isinstance(self.osc_freq, int) or self.osc_freq < 2100000 or self.osc_freq > 80000000:
raise ValueError("OSC frequency (osc_freq) must be an integer between 2100000 "
"and 80000000, not {!r}"
.format(self.osc_freq))
clk_i = Signal()
sys_clk0 = Signal()
m.submodules += Instance("qlal4s3b_cell_macro",
o_Sys_Clk0=sys_clk0)
m.submodules += Instance("gclkbuff",
o_A=sys_clk0,
o_Z=clk_i)
else:
clk_i = self.request(self.default_clk).i
if self.default_rst is not None:
rst_i = self.request(self.default_rst).i
else:
rst_i = Const(0)
m.domains += ClockDomain("sync")
m.d.comb += ClockSignal("sync").eq(clk_i)
m.submodules.reset_sync = ResetSynchronizer(rst_i, domain="sync")
return m

File diff suppressed because it is too large Load diff

View file

@ -25,6 +25,7 @@ Apply the following changes to code written against Amaranth 0.3 to migrate it t
* Update shell environment to use ``AMARANTH_*`` environment variables instead of ``NMIGEN_*`` environment variables. * Update shell environment to use ``AMARANTH_*`` environment variables instead of ``NMIGEN_*`` environment variables.
* Update shell environment to use ``AMARANTH_ENV_<TOOLCHAIN>`` (with all-uppercase ``<TOOLCHAIN>`` name) environment variable names instead of ``AMARANTH_ENV_<Toolchain>`` or ``NMIGEN_ENV_<Toolchain>`` (with mixed-case ``<Toolchain>`` name). * Update shell environment to use ``AMARANTH_ENV_<TOOLCHAIN>`` (with all-uppercase ``<TOOLCHAIN>`` name) environment variable names instead of ``AMARANTH_ENV_<Toolchain>`` or ``NMIGEN_ENV_<Toolchain>`` (with mixed-case ``<Toolchain>`` name).
* Update imports of the form ``from amaranth.vendor.some_vendor import SomeVendorPlatform`` to ``from amaranth.vendor import SomeVendorPlatform``. This change will reduce future churn.
While code that uses the features listed as deprecated below will work in Amaranth 0.4, they will be removed in the next version. While code that uses the features listed as deprecated below will work in Amaranth 0.4, they will be removed in the next version.
@ -42,6 +43,7 @@ Implemented RFCs
.. _RFC 9: https://amaranth-lang.org/rfcs/0009-const-init-shape-castable.html .. _RFC 9: https://amaranth-lang.org/rfcs/0009-const-init-shape-castable.html
.. _RFC 10: https://amaranth-lang.org/rfcs/0010-move-repl-to-value.html .. _RFC 10: https://amaranth-lang.org/rfcs/0010-move-repl-to-value.html
.. _RFC 15: https://amaranth-lang.org/rfcs/0015-lifting-shape-castables.html .. _RFC 15: https://amaranth-lang.org/rfcs/0015-lifting-shape-castables.html
.. _RFC 18: https://amaranth-lang.org/rfcs/0018-reorganize-vendor-platforms.html
.. _RFC 22: https://amaranth-lang.org/rfcs/0022-valuecastable-shape.html .. _RFC 22: https://amaranth-lang.org/rfcs/0022-valuecastable-shape.html
* `RFC 1`_: Aggregate data structure library * `RFC 1`_: Aggregate data structure library
@ -54,6 +56,7 @@ Implemented RFCs
* `RFC 8`_: Aggregate extensibility * `RFC 8`_: Aggregate extensibility
* `RFC 9`_: Constant initialization for shape-castable objects * `RFC 9`_: Constant initialization for shape-castable objects
* `RFC 10`_: Move ``Repl`` to ``Value.replicate`` * `RFC 10`_: Move ``Repl`` to ``Value.replicate``
* `RFC 18`_: Reorganize vendor platforms
* `RFC 15`_: Lifting shape-castable objects * `RFC 15`_: Lifting shape-castable objects
* `RFC 22`_: Define ``ValueCastable.shape()`` * `RFC 22`_: Define ``ValueCastable.shape()``
@ -110,9 +113,10 @@ Platform integration changes
.. currentmodule:: amaranth.vendor .. currentmodule:: amaranth.vendor
* Added: ``OSCH`` as ``default_clk`` clock source in :class:`vendor.lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform`. * Added: ``OSCH`` as ``default_clk`` clock source in :class:`vendor.LatticeMachXO2Platform`, :class:`vendor.LatticeMachXO3LPlatform`.
* Added: Xray toolchain support in :class:`vendor.xilinx.XilinxPlatform`. * Added: Xray toolchain support in :class:`vendor.XilinxPlatform`.
* Added: :class:`vendor.gowin.GowinPlatform`. * Added: :class:`vendor.GowinPlatform`.
* Deprecated: :mod:`vendor.intel`, :mod:`vendor.lattice_ecp5`, :mod:`vendor.lattice_ice40`, :mod:`vendor.lattice_machxo2_3l`, :mod:`vendor.quicklogic`, :mod:`vendor.xilinx`; import platforms directly from :mod:`vendor` instead. (`RFC 18`_)
* Removed: (deprecated in 0.3) :mod:`lattice_machxo2` * Removed: (deprecated in 0.3) :mod:`lattice_machxo2`
* Removed: (deprecated in 0.3) :class:`lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform` SVF programming vector ``{{name}}.svf``. * Removed: (deprecated in 0.3) :class:`lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform` SVF programming vector ``{{name}}.svf``.
* Removed: (deprecated in 0.3) :class:`xilinx_spartan_3_6.XilinxSpartan3APlatform`, :class:`xilinx_spartan_3_6.XilinxSpartan6Platform`, :class:`xilinx_7series.Xilinx7SeriesPlatform`, :class:`xilinx_ultrascale.XilinxUltrascalePlatform`. * Removed: (deprecated in 0.3) :class:`xilinx_spartan_3_6.XilinxSpartan3APlatform`, :class:`xilinx_spartan_3_6.XilinxSpartan6Platform`, :class:`xilinx_7series.Xilinx7SeriesPlatform`, :class:`xilinx_ultrascale.XilinxUltrascalePlatform`.

View file

@ -1,9 +1,9 @@
Gowin Gowin
##### #####
.. py:module:: amaranth.vendor.gowin .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.gowin` module provides a base platform to support Gowin toolchains. The :class:`GowinPlatform` class provides a base platform to support Gowin toolchains.
The Apicula and Gowin toolchains are supported. The Apicula and Gowin toolchains are supported.

View file

@ -1,9 +1,9 @@
Intel Intel
##### #####
.. py:module:: amaranth.vendor.intel .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.intel` module provides a base platform to support Intel toolchains. The :class:`IntelPlatform` class provides a base platform to support Intel toolchains.
The Quartus and Mistral toolchains are supported. The Quartus and Mistral toolchains are supported.

View file

@ -1,9 +1,9 @@
Lattice ECP5 Lattice ECP5
############ ############
.. py:module:: amaranth.vendor.lattice_ecp5 .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.lattice_ecp5` module provides a base platform to support Lattice ECP5 devices. The :class:`LatticeECP5Platform` class provides a base platform to support Lattice ECP5 devices.
The Trellis and Diamond toolchains are supported. The Trellis and Diamond toolchains are supported.

View file

@ -1,9 +1,9 @@
Lattice iCE40 Lattice iCE40
############# #############
.. py:module:: amaranth.vendor.lattice_ice40 .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.lattice_ice40` module provides a base platform to support Lattice iCE40 devices. The :class:`LatticeICE40Platform` class provides a base platform to support Lattice iCE40 devices.
The IceStorm and iCECube2 toolchains are supported. The IceStorm and iCECube2 toolchains are supported.

View file

@ -1,19 +1,12 @@
Lattice MachXO2 and MachXO3L Lattice MachXO2 and MachXO3L
############################ ############################
.. py:module:: amaranth.vendor.lattice_machxo_2_3l .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.lattice_machxo_2_3l` module provides a base platform to support Lattice The :class:`LatticeMachXO2Platform` and :class:`LatticeMachXO3LPlatform` classes provide base platforms to support Lattice MachXO2 and MachXO3L devices.
MachXO2 and MachXO3L devices.
The Diamond toolchain is supported. The Diamond toolchain is supported.
.. autoclass:: amaranth.vendor._lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform
.. autoclass:: LatticeMachXO2Platform .. autoclass:: LatticeMachXO2Platform
.. autoclass:: LatticeMachXO3LPlatform .. autoclass:: LatticeMachXO3LPlatform
.. note:: Both of the above are aliases for the actual platform below, however only the aliased
definitions are actually exported from the module for use.
.. autoclass:: LatticeMachXO2Or3LPlatform

View file

@ -1,9 +1,9 @@
Quicklogic Quicklogic
########## ##########
.. py:module:: amaranth.vendor.quicklogic .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.quicklogic` module provides a base platform to support Quicklogic toolchains. The :class:`QuicklogicPlatform` class provides a base platform to support Quicklogic toolchains.
The Symbiflow toolchain is supported. The Symbiflow toolchain is supported.

View file

@ -1,9 +1,9 @@
Xilinx Xilinx
###### ######
.. py:module:: amaranth.vendor.xilinx .. currentmodule:: amaranth.vendor
The :mod:`amaranth.vendor.xilinx` module provides a base platform to support Xilinx toolchains. The :class:`XilinxPlatform` class provides a base platform to support Xilinx toolchains.
The ISE, Vivado, and Symbiflow toolchains are supported. The ISE, Vivado, and Symbiflow toolchains are supported.