Implement RFC 18: Reorganize vendor platforms
This commit is contained in:
parent
88cbf30128
commit
796068a192
|
@ -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
603
amaranth/vendor/_gowin.py
Normal 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
568
amaranth/vendor/_intel.py
Normal 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
|
665
amaranth/vendor/_lattice_ecp5.py
Normal file
665
amaranth/vendor/_lattice_ecp5.py
Normal 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.
|
624
amaranth/vendor/_lattice_ice40.py
Normal file
624
amaranth/vendor/_lattice_ice40.py
Normal 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.
|
449
amaranth/vendor/_lattice_machxo_2_3l.py
Normal file
449
amaranth/vendor/_lattice_machxo_2_3l.py
Normal 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.
|
184
amaranth/vendor/_quicklogic.py
Normal file
184
amaranth/vendor/_quicklogic.py
Normal 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
1218
amaranth/vendor/_xilinx.py
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,606 +1,13 @@
|
|||
from abc import abstractproperty
|
||||
from fractions import Fraction
|
||||
import re
|
||||
# TODO(amaranth-0.5): remove module
|
||||
|
||||
from ..hdl import *
|
||||
from ..lib.cdc import ResetSynchronizer
|
||||
from ..build import *
|
||||
import warnings
|
||||
import importlib
|
||||
from .. import vendor
|
||||
|
||||
# Acknowledgments:
|
||||
# Parts of this file originate from https://github.com/tcjie/Gowin
|
||||
|
||||
|
||||
__all__ = ["GowinPlatform"]
|
||||
|
||||
|
||||
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
|
||||
def __getattr__(name):
|
||||
if name in ("GowinPlatform",):
|
||||
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return getattr(vendor, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
|
@ -1,571 +1,13 @@
|
|||
from abc import abstractmethod
|
||||
# TODO(amaranth-0.5): remove module
|
||||
|
||||
from ..hdl import *
|
||||
from ..build import *
|
||||
import warnings
|
||||
import importlib
|
||||
from .. import vendor
|
||||
|
||||
|
||||
__all__ = ["IntelPlatform"]
|
||||
|
||||
|
||||
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
|
||||
def __getattr__(name):
|
||||
if name in ("IntelPlatform",):
|
||||
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return getattr(vendor, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
|
@ -1,668 +1,13 @@
|
|||
from abc import abstractmethod
|
||||
# TODO(amaranth-0.5): remove module
|
||||
|
||||
from ..hdl import *
|
||||
from ..build import *
|
||||
import warnings
|
||||
import importlib
|
||||
from .. import vendor
|
||||
|
||||
|
||||
__all__ = ["LatticeECP5Platform"]
|
||||
|
||||
|
||||
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.
|
||||
def __getattr__(name):
|
||||
if name in ("LatticeECP5Platform",):
|
||||
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return getattr(vendor, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
|
@ -1,627 +1,13 @@
|
|||
from abc import abstractmethod
|
||||
# TODO(amaranth-0.5): remove module
|
||||
|
||||
from ..hdl import *
|
||||
from ..lib.cdc import ResetSynchronizer
|
||||
from ..build import *
|
||||
import warnings
|
||||
import importlib
|
||||
from .. import vendor
|
||||
|
||||
|
||||
__all__ = ["LatticeICE40Platform"]
|
||||
|
||||
|
||||
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.
|
||||
def __getattr__(name):
|
||||
if name in ("LatticeICE40Platform",):
|
||||
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return getattr(vendor, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
|
@ -1,456 +1,13 @@
|
|||
from abc import abstractmethod
|
||||
# TODO(amaranth-0.5): remove module
|
||||
|
||||
from ..hdl import *
|
||||
from ..build import *
|
||||
import warnings
|
||||
import importlib
|
||||
from .. import vendor
|
||||
|
||||
|
||||
__all__ = ["LatticeMachXO2Platform", "LatticeMachXO3LPlatform"]
|
||||
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
LatticeMachXO2Platform = LatticeMachXO2Or3LPlatform
|
||||
LatticeMachXO3LPlatform = LatticeMachXO2Or3LPlatform
|
||||
def __getattr__(name):
|
||||
if name in ("LatticeMachXO2Platform", "LatticeMachXO3LPlatform"):
|
||||
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return getattr(vendor, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
|
@ -1,184 +1,13 @@
|
|||
from abc import abstractmethod
|
||||
# TODO(amaranth-0.5): remove module
|
||||
|
||||
from ..hdl import *
|
||||
from ..lib.cdc import ResetSynchronizer
|
||||
from ..build import *
|
||||
import warnings
|
||||
import importlib
|
||||
from .. import vendor
|
||||
|
||||
|
||||
__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
|
||||
def __getattr__(name):
|
||||
if name in ("QuicklogicPlatform",):
|
||||
warnings.warn(f"instead of `{__name__}.{name}`, use `amaranth.vendor.{name}",
|
||||
DeprecationWarning, stacklevel=2)
|
||||
return getattr(vendor, name)
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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_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.
|
||||
|
||||
|
@ -42,6 +43,7 @@ Implemented RFCs
|
|||
.. _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 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 1`_: Aggregate data structure library
|
||||
|
@ -54,6 +56,7 @@ Implemented RFCs
|
|||
* `RFC 8`_: Aggregate extensibility
|
||||
* `RFC 9`_: Constant initialization for shape-castable objects
|
||||
* `RFC 10`_: Move ``Repl`` to ``Value.replicate``
|
||||
* `RFC 18`_: Reorganize vendor platforms
|
||||
* `RFC 15`_: Lifting shape-castable objects
|
||||
* `RFC 22`_: Define ``ValueCastable.shape()``
|
||||
|
||||
|
@ -110,9 +113,10 @@ Platform integration changes
|
|||
|
||||
.. currentmodule:: amaranth.vendor
|
||||
|
||||
* Added: ``OSCH`` as ``default_clk`` clock source in :class:`vendor.lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform`.
|
||||
* Added: Xray toolchain support in :class:`vendor.xilinx.XilinxPlatform`.
|
||||
* Added: :class:`vendor.gowin.GowinPlatform`.
|
||||
* Added: ``OSCH`` as ``default_clk`` clock source in :class:`vendor.LatticeMachXO2Platform`, :class:`vendor.LatticeMachXO3LPlatform`.
|
||||
* Added: Xray toolchain support in :class:`vendor.XilinxPlatform`.
|
||||
* 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) :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`.
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
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
|
||||
MachXO2 and MachXO3L devices.
|
||||
The :class:`LatticeMachXO2Platform` and :class:`LatticeMachXO3LPlatform` classes provide base platforms to support Lattice MachXO2 and MachXO3L devices.
|
||||
|
||||
The Diamond toolchain is supported.
|
||||
|
||||
|
||||
|
||||
.. autoclass:: amaranth.vendor._lattice_machxo_2_3l.LatticeMachXO2Or3LPlatform
|
||||
.. autoclass:: LatticeMachXO2Platform
|
||||
.. 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
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
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.
|
||||
|
||||
|
|
Loading…
Reference in a new issue