424 lines
16 KiB
Python
424 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}}"
|
|
{% endfor %}
|
|
{% for file in platform.iter_extra_files(".sv") -%}
|
|
set_global_assignment -name SYSTEMVERILOG_FILE "{{file}}"
|
|
{% endfor %}
|
|
{% for file in platform.iter_extra_files(".vhd", ".vhdl") -%}
|
|
set_global_assignment -name VHDL_FILE "{{file}}"
|
|
{% 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, extras in platform.iter_port_constraints_bits() -%}
|
|
set_location_assignment -to "{{port_name}}" PIN_{{pin_name}}
|
|
{% for key, value in extras.items() -%}
|
|
set_instance_assignment -to "{{port_name}}" -name {{key}} "{{value}}"
|
|
{% 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}} -period {{1000000000/frequency}} [get_ports {{port_signal.name}}]
|
|
{% else -%}
|
|
create_clock -name {{net_signal.name}} -period {{1000000000/frequency}} [get_nets {{net_signal|hierarchy("|")}}]
|
|
{% 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)
|
|
# Make sure the net constrained in the SDC file is kept through synthesis; it is redundant
|
|
# after Quartus flattens the hierarchy and will be eliminated if not explicitly kept.
|
|
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
|