add GateMate backend
Some checks are pending
CI / test (${{ matrix.python-version }}) (false, 3.10) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.11) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.12) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.8) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.9) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, pypy-3.10) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, pypy-3.8) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, pypy-3.9) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (true, 3.13-dev) (push) Waiting to run
CI / smoke (${{ matrix.project }}) (amaranth-lang/amaranth-boards) (push) Waiting to run
CI / smoke (${{ matrix.project }}) (amaranth-lang/amaranth-soc) (push) Waiting to run
CI / smoke (${{ matrix.project }}) (amaranth-lang/amaranth-stdio) (push) Waiting to run
CI / document (push) Waiting to run
CI / check-links (push) Waiting to run
CI / required (push) Blocked by required conditions
CI / publish-docs (push) Blocked by required conditions
CI / publish-docs-dev (push) Blocked by required conditions
CI / publish-schemas (push) Blocked by required conditions
CI / publish-package (push) Blocked by required conditions
CI / publish-release (push) Blocked by required conditions
Some checks are pending
CI / test (${{ matrix.python-version }}) (false, 3.10) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.11) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.12) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.8) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, 3.9) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, pypy-3.10) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, pypy-3.8) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (false, pypy-3.9) (push) Waiting to run
CI / test (${{ matrix.python-version }}) (true, 3.13-dev) (push) Waiting to run
CI / smoke (${{ matrix.project }}) (amaranth-lang/amaranth-boards) (push) Waiting to run
CI / smoke (${{ matrix.project }}) (amaranth-lang/amaranth-soc) (push) Waiting to run
CI / smoke (${{ matrix.project }}) (amaranth-lang/amaranth-stdio) (push) Waiting to run
CI / document (push) Waiting to run
CI / check-links (push) Waiting to run
CI / required (push) Blocked by required conditions
CI / publish-docs (push) Blocked by required conditions
CI / publish-docs-dev (push) Blocked by required conditions
CI / publish-schemas (push) Blocked by required conditions
CI / publish-package (push) Blocked by required conditions
CI / publish-release (push) Blocked by required conditions
This commit is contained in:
parent
bf4f22d2d5
commit
02f63ba874
|
@ -6,6 +6,7 @@
|
|||
__all__ = [
|
||||
"AlteraPlatform",
|
||||
"AMDPlatform",
|
||||
"GateMatePlatform",
|
||||
"GowinPlatform",
|
||||
"IntelPlatform",
|
||||
"LatticeECP5Platform",
|
||||
|
@ -30,6 +31,9 @@ def __getattr__(name):
|
|||
if name == "GowinPlatform":
|
||||
from ._gowin import GowinPlatform
|
||||
return GowinPlatform
|
||||
if name == "GateMatePlatform":
|
||||
from ._colognechip import GateMatePlatform
|
||||
return GateMatePlatform
|
||||
if name in ("LatticePlatform", "LatticeECP5Platform", "LatticeMachXO2Platform",
|
||||
"LatticeMachXO3LPlatform"):
|
||||
from ._lattice import LatticePlatform
|
||||
|
|
399
amaranth/vendor/_colognechip.py
Normal file
399
amaranth/vendor/_colognechip.py
Normal file
|
@ -0,0 +1,399 @@
|
|||
from abc import abstractmethod
|
||||
|
||||
from ..hdl import *
|
||||
from ..hdl._ir import RequirePosedge
|
||||
from ..lib import io, wiring
|
||||
from ..build import *
|
||||
|
||||
# FIXME: be sure attribtues are handled correctly
|
||||
|
||||
|
||||
class InnerBuffer(wiring.Component):
|
||||
"""A private component used to implement ``lib.io`` buffers.
|
||||
|
||||
Works like ``lib.io.Buffer``, with the following differences:
|
||||
|
||||
- ``port.invert`` is ignored (handling the inversion is the outer buffer's responsibility)
|
||||
- ``t`` is per-pin inverted output enable
|
||||
- ``merge_ff`` specifies this is to be used with a simple FF, and P&R should merge these
|
||||
"""
|
||||
def __init__(self, direction, port, merge_ff=False):
|
||||
self.direction = direction
|
||||
self.port = port
|
||||
self.merge_ff = merge_ff
|
||||
|
||||
members = {}
|
||||
|
||||
if direction is not io.Direction.Output:
|
||||
members["i"] = wiring.In(len(port))
|
||||
|
||||
if direction is not io.Direction.Input:
|
||||
members["o"] = wiring.Out(len(port))
|
||||
members["t"] = wiring.Out(len(port))
|
||||
|
||||
super().__init__(wiring.Signature(members).flip())
|
||||
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
if isinstance(self.port, io.SingleEndedPort):
|
||||
io_port = self.port.io
|
||||
|
||||
for bit in range(len(self.port)):
|
||||
name = f"buf{bit}"
|
||||
if self.direction is io.Direction.Input:
|
||||
m.submodules[name] = Instance("CC_IBUF",
|
||||
i_I=io_port[bit],
|
||||
o_Y=self.i[bit],
|
||||
p_FF_IBF=self.merge_ff,
|
||||
)
|
||||
elif self.direction is io.Direction.Output:
|
||||
m.submodules[name] = Instance("CC_TOBUF",
|
||||
i_T=self.t[bit],
|
||||
i_A=self.o[bit],
|
||||
o_O=io_port[bit],
|
||||
p_FF_OBF=self.merge_ff,
|
||||
)
|
||||
elif self.direction is io.Direction.Bidir:
|
||||
m.submodules[name] = Instance("CC_IOBUF",
|
||||
i_T=self.t[bit],
|
||||
i_Y=self.o[bit],
|
||||
o_A=self.i[bit],
|
||||
io_IO=io_port[bit],
|
||||
p_FF_IBF=self.merge_ff,
|
||||
p_FF_OBF=self.merge_ff,
|
||||
)
|
||||
else:
|
||||
assert False # :nocov:
|
||||
|
||||
elif isinstance(self.port, io.DifferentialPort):
|
||||
p_port = self.port.p
|
||||
n_port = self.port.n
|
||||
|
||||
for bit in range(len(self.port)):
|
||||
name = f"buf{bit}"
|
||||
if self.direction is io.Direction.Input:
|
||||
m.submodules[name] = Instance("CC_LVDS_IBUF",
|
||||
i_I_P=p_port[bit],
|
||||
i_I_N=n_port[bit],
|
||||
o_Y=self.i[bit],
|
||||
p_FF_IBF=self.merge_ff,
|
||||
)
|
||||
elif self.direction is io.Direction.Output:
|
||||
m.submodules[name] = Instance("CC_LVDS_TOBUF",
|
||||
i_T=self.t[bit],
|
||||
i_A=self.o[bit],
|
||||
o_O_P=p_port[bit],
|
||||
o_O_N=n_port[bit],
|
||||
p_FF_OBF=self.merge_ff,
|
||||
)
|
||||
elif self.direction is io.Direction.Bidir:
|
||||
m.submodules[name] = Instance("CC_LVDS_IOBUF",
|
||||
i_T=self.t[bit],
|
||||
i_Y=self.o[bit],
|
||||
o_A=self.i[bit],
|
||||
io_P=p_port[bit],
|
||||
io_N=n_port[bit],
|
||||
p_FF_IBF=self.merge_ff,
|
||||
p_FF_OBF=self.merge_ff,
|
||||
)
|
||||
else:
|
||||
assert False # :nocov:
|
||||
|
||||
else:
|
||||
raise TypeError(f"Unknown port type {self.port!r}")
|
||||
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class IOBuffer(io.Buffer):
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
m.submodules.buf = buf = InnerBuffer(self.direction, self.port)
|
||||
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
|
||||
|
||||
if self.direction is not io.Direction.Output:
|
||||
m.d.comb += self.i.eq(buf.i ^ inv_mask)
|
||||
|
||||
if self.direction is not io.Direction.Input:
|
||||
m.d.comb += buf.o.eq(self.o ^ inv_mask)
|
||||
m.d.comb += buf.t.eq(~self.oe.replicate(len(self.port)))
|
||||
|
||||
return m
|
||||
|
||||
|
||||
def _make_oereg(m, domain, oe, q):
|
||||
for bit in range(len(q)):
|
||||
m.submodules[f"oe_ff{bit}"] = Instance("CC_DFF",
|
||||
i_CLK=ClockSignal(domain),
|
||||
i_EN=Const(1),
|
||||
i_SR=Const(0),
|
||||
i_D=oe,
|
||||
o_Q=q[bit],
|
||||
)
|
||||
|
||||
|
||||
class FFBuffer(io.FFBuffer):
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
m.submodules.buf = buf = InnerBuffer(self.direction, self.port, true)
|
||||
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
|
||||
|
||||
if self.direction is not io.Direction.Output:
|
||||
m.submodules += RequirePosedge(self.i_domain)
|
||||
i_inv = Signal.like(self.i)
|
||||
for bit in range(len(self.port)):
|
||||
m.submodules[f"i_ff{bit}"] = Instance("CC_DFF",
|
||||
i_CLK=ClockSignal(self.i_domain),
|
||||
i_EN=Const(1),
|
||||
i_SR=Const(0),
|
||||
i_D=buf.i[bit],
|
||||
o_Q=i_inv[bit],
|
||||
)
|
||||
m.d.comb += self.i.eq(i_inv ^ inv_mask)
|
||||
|
||||
if self.direction is not io.Direction.Input:
|
||||
m.submodules += RequirePosedge(self.o_domain)
|
||||
o_inv = Signal.like(self.o)
|
||||
m.d.comb += o_inv.eq(self.o ^ inv_mask)
|
||||
for bit in range(len(self.port)):
|
||||
m.submodules[f"o_ff{bit}"] = Instance("CC_DFF",
|
||||
i_CLK=ClockSignal(self.o_domain),
|
||||
i_EN=Const(1),
|
||||
i_SR=Const(0),
|
||||
i_D=o_inv[bit],
|
||||
o_Q=buf.o[bit],
|
||||
)
|
||||
_make_oereg(m, self.o_domain, ~self.oe, buf.t)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class DDRBuffer(io.DDRBuffer):
|
||||
def elaborate(self, platform):
|
||||
m = Module()
|
||||
|
||||
m.submodules.buf = buf = InnerBuffer(self.direction, self.port)
|
||||
inv_mask = sum(inv << bit for bit, inv in enumerate(self.port.invert))
|
||||
|
||||
if self.direction is not io.Direction.Output:
|
||||
m.submodules += RequirePosedge(self.i_domain)
|
||||
i0_inv = Signal(len(self.port))
|
||||
i1_inv = Signal(len(self.port))
|
||||
for bit in range(len(self.port)):
|
||||
m.submodules[f"i_ddr{bit}"] = Instance("CC_IDDR",
|
||||
i_CLK=ClockSignal(self.i_domain),
|
||||
i_D=buf.i[bit],
|
||||
o_Q0=i0_inv[bit],
|
||||
o_Q1=i1_inv[bit],
|
||||
)
|
||||
m.d.comb += self.i[0].eq(i0_inv ^ inv_mask)
|
||||
m.d.comb += self.i[1].eq(i1_inv ^ inv_mask)
|
||||
|
||||
if self.direction is not io.Direction.Input:
|
||||
m.submodules += RequirePosedge(self.o_domain)
|
||||
o0_inv = Signal(len(self.port))
|
||||
o1_inv = Signal(len(self.port))
|
||||
m.d.comb += [
|
||||
o0_inv.eq(self.o[0] ^ inv_mask),
|
||||
o1_inv.eq(self.o[1] ^ inv_mask),
|
||||
]
|
||||
for bit in range(len(self.port)):
|
||||
m.submodules[f"o_ddr{bit}"] = Instance("CC_ODDR",
|
||||
i_CLK=ClockSignal(self.o_domain),
|
||||
i_DDR=ClockSignal(self.i_domain), # FIXME
|
||||
i_D0=o0_inv[bit],
|
||||
i_D1=o1_inv[bit],
|
||||
o_Q=buf.o[bit],
|
||||
)
|
||||
_make_oereg(m, self.o_domain, ~self.oe, buf.t)
|
||||
|
||||
return m
|
||||
|
||||
|
||||
class GateMatePlatform(TemplatedPlatform):
|
||||
"""
|
||||
.. rubric:: Peppercorn toolchain
|
||||
|
||||
Required tools:
|
||||
* ``yosys``
|
||||
* ``nextpnr-himbaechel`` built with the gatemate uarch
|
||||
* ``gmpack``
|
||||
|
||||
The environment is populated by running the script specified in the environment variable
|
||||
``AMARANTH_ENV_PEPPERCORN``, 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_<family>`` Yosys command.
|
||||
* ``script_after_read``: inserts commands after ``read_rtlil`` in Yosys script.
|
||||
* ``script_after_synth``: inserts commands after ``synth_<family>`` in Yosys script.
|
||||
* ``yosys_opts``: adds extra options for ``yosys``.
|
||||
* ``nextpnr_opts``: adds extra options for ``nextpnr-<family>``.
|
||||
* ``gmpack_opts``: adds extra options for ``gmpack``.
|
||||
* ``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.
|
||||
|
||||
"""
|
||||
|
||||
toolchain = "pepercorn"
|
||||
device = property(abstractmethod(lambda: None))
|
||||
|
||||
_required_tools = [
|
||||
"yosys",
|
||||
"nextpnr-himbaechel",
|
||||
"gmpack"
|
||||
]
|
||||
|
||||
_file_templates = {
|
||||
**TemplatedPlatform.build_script_templates,
|
||||
"{{name}}.il": r"""
|
||||
# {{autogenerated}}
|
||||
{{emit_rtlil()}}
|
||||
""",
|
||||
"{{name}}.debug.v": r"""
|
||||
/* {{autogenerated}} */
|
||||
{{emit_debug_verilog()}}
|
||||
""",
|
||||
# Note: synth with -luttree is currently basically required to fit anything significant on the GateMate,
|
||||
# so we're adopting it.
|
||||
"{{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_rtlil {{file}}
|
||||
{% endfor %}
|
||||
read_rtlil {{name}}.il
|
||||
{{get_override("script_after_read")|default("# (script_after_read placeholder)")}}
|
||||
synth_gatemate {{get_override("synth_opts")|options}} -top {{name}} -luttree
|
||||
{{get_override("script_after_synth")|default("# (script_after_synth placeholder)")}}
|
||||
write_json {{name}}.json
|
||||
""",
|
||||
"{{name}}.ccf": r"""
|
||||
# {{autogenerated}}
|
||||
{% for port_name, pin_name, attrs in platform.iter_port_constraints_bits() -%}
|
||||
NET "{{port_name}}" Loc = "IO_{{pin_name}}" {%- for key, value in attrs.items() %} | {{key}}={{value}}{% endfor %};
|
||||
{% 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_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)")}}
|
||||
""",
|
||||
}
|
||||
_command_templates = [
|
||||
r"""
|
||||
{{invoke_tool("yosys")}}
|
||||
{{quiet("-q")}}
|
||||
{{get_override("yosys_opts")|options}}
|
||||
-l {{name}}.rpt
|
||||
{{name}}.ys
|
||||
""",
|
||||
r"""
|
||||
{{invoke_tool("nextpnr-himbaechel")}}
|
||||
{{quiet("--quiet")}}
|
||||
{{get_override("nextpnr_opts")|options}}
|
||||
--log {{name}}.tim
|
||||
--device {{platform.device}}
|
||||
--json {{name}}.json
|
||||
--sdc {{name}}.sdc
|
||||
-o ccf={{name}}.ccf
|
||||
-o out={{name}}.config
|
||||
""",
|
||||
r"""
|
||||
{{invoke_tool("gmpack")}}
|
||||
{{verbose("--verbose")}}
|
||||
{{get_override("gmpack_opts")|options}}
|
||||
--input {{name}}.config
|
||||
--bit {{name}}.bit
|
||||
"""
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
device = self.device.lower()
|
||||
|
||||
if device.startswith("ccgm1a"):
|
||||
self.family = "gatemate"
|
||||
else:
|
||||
raise ValueError(f"Device '{self.device}' is not recognized")
|
||||
|
||||
@property
|
||||
def required_tools(self):
|
||||
return self._required_tools
|
||||
|
||||
@property
|
||||
def file_templates(self):
|
||||
return self._file_templates
|
||||
|
||||
@property
|
||||
def command_templates(self):
|
||||
return self._command_templates
|
||||
|
||||
|
||||
def create_missing_domain(self, name):
|
||||
if name == "sync" and self.default_clk is not None:
|
||||
m = Module()
|
||||
|
||||
clk_io = self.request(self.default_clk, dir="-")
|
||||
m.submodules.clk_buf = clk_buf = io.Buffer("i", clk_io)
|
||||
clk_i = clk_buf.i
|
||||
|
||||
if self.default_rst is not None:
|
||||
rst_io = self.request(self.default_rst, dir="-")
|
||||
m.submodules.rst_buf = rst_buf = io.Buffer("i", rst_io)
|
||||
rst_i = rst_buf.i
|
||||
else:
|
||||
rst_i = Const(0)
|
||||
|
||||
# The post-configuration reset 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
|
||||
|
||||
def get_io_buffer(self, buffer):
|
||||
if isinstance(buffer, io.Buffer):
|
||||
result = IOBuffer(buffer.direction, buffer.port)
|
||||
elif isinstance(buffer, io.FFBuffer):
|
||||
result = FFBuffer(buffer.direction, buffer.port,
|
||||
i_domain=buffer.i_domain,
|
||||
o_domain=buffer.o_domain)
|
||||
elif isinstance(buffer, io.DDRBuffer):
|
||||
result = DDRBuffer(buffer.direction, buffer.port,
|
||||
i_domain=buffer.i_domain,
|
||||
o_domain=buffer.o_domain)
|
||||
else:
|
||||
raise TypeError(f"Unsupported buffer type {buffer!r}") # :nocov:
|
||||
|
||||
if buffer.direction is not io.Direction.Output:
|
||||
result.i = buffer.i
|
||||
if buffer.direction is not io.Direction.Input:
|
||||
result.o = buffer.o
|
||||
result.oe = buffer.oe
|
||||
|
||||
return result
|
Loading…
Reference in a new issue