
If the clock signal is not a top-level port and has aliases, it can be optimized out, and then the constraint will no longer apply. To prevent this, make sure the constrained signal is preferred over any aliases by using the `keep` attribute. Vivado does not parse attributes like (* keep = 32'd1 *) as valid even though, AFAICT, they are equivalent to (* keep = 1 *) or simply (* keep *) per IEEE 1364. To work around this, use the solution we currently use for Quartus, which is `write_verilog -decimal`. Fixes #373.
422 lines
16 KiB
Python
422 lines
16 KiB
Python
from abc import abstractproperty
|
|
|
|
from ..hdl import *
|
|
from ..build import *
|
|
|
|
|
|
__all__ = ["IntelPlatform"]
|
|
|
|
|
|
class IntelPlatform(TemplatedPlatform):
|
|
"""
|
|
Required tools:
|
|
* ``quartus_map``
|
|
* ``quartus_fit``
|
|
* ``quartus_asm``
|
|
* ``quartus_sta``
|
|
|
|
The environment is populated by running the script specified in the environment variable
|
|
``NMIGEN_ENV_Quartus``, if present.
|
|
|
|
Available overrides:
|
|
* ``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.
|
|
"""
|
|
|
|
toolchain = "Quartus"
|
|
|
|
device = abstractproperty()
|
|
package = abstractproperty()
|
|
speed = abstractproperty()
|
|
suffix = ""
|
|
|
|
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
|
|
]
|
|
|
|
required_tools = [
|
|
"quartus_map",
|
|
"quartus_fit",
|
|
"quartus_asm",
|
|
"quartus_sta",
|
|
]
|
|
|
|
file_templates = {
|
|
**TemplatedPlatform.build_script_templates,
|
|
"build_{{name}}.sh": r"""
|
|
# {{autogenerated}}
|
|
if [ -n "${{platform._toolchain_env_var}}" ]; then
|
|
QUARTUS_ROOTDIR=$(dirname $(dirname "${{platform._toolchain_env_var}}"))
|
|
# Quartus' qenv.sh does not work with `set -e`.
|
|
. "${{platform._toolchain_env_var}}"
|
|
fi
|
|
set -e{{verbose("x")}}
|
|
{{emit_commands("sh")}}
|
|
""",
|
|
# Quartus doesn't like constructs like (* keep = 32'd1 *), even though they mean the same
|
|
# thing as (* keep = 1 *); use -decimal to work around that.
|
|
"{{name}}.v": r"""
|
|
/* {{autogenerated}} */
|
|
{{emit_verilog(["-decimal"])}}
|
|
""",
|
|
"{{name}}.debug.v": r"""
|
|
/* {{autogenerated}} */
|
|
{{emit_debug_verilog(["-decimal"])}}
|
|
""",
|
|
"{{name}}.qsf": r"""
|
|
# {{autogenerated}}
|
|
{% if get_override("nproc") -%}
|
|
set_global_assignment -name NUM_PARALLEL_PROCESSORS {{get_override("nproc")}}
|
|
{% endif %}
|
|
|
|
{% for file in platform.iter_extra_files(".v") -%}
|
|
set_global_assignment -name VERILOG_FILE {{file|tcl_escape}}
|
|
{% endfor %}
|
|
{% for file in platform.iter_extra_files(".sv") -%}
|
|
set_global_assignment -name SYSTEMVERILOG_FILE {{file|tcl_escape}}
|
|
{% endfor %}
|
|
{% for file in platform.iter_extra_files(".vhd", ".vhdl") -%}
|
|
set_global_assignment -name VHDL_FILE {{file|tcl_escape}}
|
|
{% 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_escape}} PIN_{{pin_name}}
|
|
{% for key, value in attrs.items() -%}
|
|
set_instance_assignment -to {{port_name|tcl_escape}} -name {{key}} {{value|tcl_escape}}
|
|
{% endfor %}
|
|
{% endfor %}
|
|
|
|
set_global_assignment -name GENERATE_RBF_FILE ON
|
|
""",
|
|
"{{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 %}
|
|
""",
|
|
"{{name}}.srf": r"""
|
|
{% for warning in platform.quartus_suppressed_warnings %}
|
|
{ "" "" "" "{{name}}.v" { } { } 0 {{warning}} "" 0 0 "Design Software" 0 -1 0 ""}
|
|
{% endfor %}
|
|
""",
|
|
}
|
|
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}}
|
|
""",
|
|
]
|
|
|
|
def add_clock_constraint(self, clock, frequency):
|
|
super().add_clock_constraint(clock, frequency)
|
|
clock.attrs["keep"] = 1
|
|
|
|
# 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 Repl(pin.oe, 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,
|
|
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,
|
|
)
|
|
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,
|
|
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,
|
|
o_dataout=self._get_ireg(m, pin, invert),
|
|
i_oe=self._get_oereg(m, pin),
|
|
)
|
|
return m
|
|
|
|
def get_diff_input(self, pin, p_port, n_port, attrs, invert):
|
|
self._check_feature("differential input", pin, attrs,
|
|
valid_xdrs=(0, 1, 2), valid_attrs=True)
|
|
if pin.xdr == 1:
|
|
p_port.attrs["useioff"] = 1
|
|
n_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="TRUE",
|
|
i_datain=p_port,
|
|
i_datain_b=n_port,
|
|
o_dataout=self._get_ireg(m, pin, invert)
|
|
)
|
|
return m
|
|
|
|
def get_diff_output(self, pin, p_port, n_port, attrs, invert):
|
|
self._check_feature("differential output", pin, attrs,
|
|
valid_xdrs=(0, 1, 2), valid_attrs=True)
|
|
if pin.xdr == 1:
|
|
p_port.attrs["useioff"] = 1
|
|
n_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="TRUE",
|
|
p_use_oe="FALSE",
|
|
i_datain=self._get_oreg(m, pin, invert),
|
|
o_dataout=p_port,
|
|
o_dataout_b=n_port,
|
|
)
|
|
return m
|
|
|
|
def get_diff_tristate(self, pin, p_port, n_port, attrs, invert):
|
|
self._check_feature("differential tristate", pin, attrs,
|
|
valid_xdrs=(0, 1, 2), valid_attrs=True)
|
|
if pin.xdr == 1:
|
|
p_port.attrs["useioff"] = 1
|
|
n_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="TRUE",
|
|
p_use_oe="TRUE",
|
|
i_datain=self._get_oreg(m, pin, invert),
|
|
o_dataout=p_port,
|
|
o_dataout_b=n_port,
|
|
i_oe=self._get_oereg(m, pin),
|
|
)
|
|
return m
|
|
|
|
def get_diff_input_output(self, pin, p_port, n_port, attrs, invert):
|
|
self._check_feature("differential input/output", pin, attrs,
|
|
valid_xdrs=(0, 1, 2), valid_attrs=True)
|
|
if pin.xdr == 1:
|
|
p_port.attrs["useioff"] = 1
|
|
n_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="TRUE",
|
|
i_datain=self._get_oreg(m, pin, invert),
|
|
io_dataio=p_port,
|
|
io_dataio_b=n_port,
|
|
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._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._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
|