|
1 | 1 | # SPDX-License-Identifier: BSD-2-Clause |
2 | 2 |
|
3 | 3 | import os |
4 | | -import sys |
5 | | -import tempfile |
6 | 4 | import subprocess |
7 | 5 |
|
8 | 6 | from amaranth import * |
| 7 | +from amaranth.lib import io |
| 8 | + |
9 | 9 | from amaranth.back import rtlil |
10 | 10 | from amaranth.hdl import Fragment |
11 | | -from amaranth.lib.io import Direction, Buffer |
12 | 11 | from amaranth.hdl._ir import PortDirection |
13 | 12 |
|
14 | 13 | from .. import ChipFlowError |
15 | 14 |
|
16 | 15 |
|
17 | | -__all__ = ["SiliconPlatform"] |
| 16 | +__all__ = ["SiliconPlatformPort", "SiliconPlatform"] |
18 | 17 |
|
19 | 18 |
|
20 | | -class SiliconPlatform: |
21 | | - from ..providers import silicon as providers |
| 19 | +class SiliconPlatformPort(io.PortLike): |
| 20 | + def __init__(self, name, direction, width, *, invert=False): |
| 21 | + if not isinstance(name, str): |
| 22 | + raise TypeError(f"Name must be a string, not {name!r}") |
| 23 | + if not (isinstance(width, int) and width >= 0): |
| 24 | + raise TypeError(f"Width must be a non-negative integer, not {width!r}") |
| 25 | + if not isinstance(invert, bool): |
| 26 | + raise TypeError(f"'invert' must be a bool, not {invert!r}") |
| 27 | + |
| 28 | + self._direction = io.Direction(direction) |
| 29 | + self._invert = invert |
| 30 | + |
| 31 | + self._i = self._o = self._oe = None |
| 32 | + if self._direction in (io.Direction.Input, io.Direction.Bidir): |
| 33 | + self._i = Signal(width, name=f"{name}__i") |
| 34 | + if self._direction in (io.Direction.Output, io.Direction.Bidir): |
| 35 | + self._o = Signal(width, name=f"{name}__o") |
| 36 | + self._oe = Signal(width, name=f"{name}__oe") |
| 37 | + |
| 38 | + @property |
| 39 | + def i(self): |
| 40 | + if self._i is None: |
| 41 | + raise AttributeError("SiliconPlatformPort with output direction does not have an " |
| 42 | + "input signal") |
| 43 | + return self._i |
| 44 | + |
| 45 | + @property |
| 46 | + def o(self): |
| 47 | + if self._o is None: |
| 48 | + raise AttributeError("SiliconPlatformPort with input direction does not have an " |
| 49 | + "output signal") |
| 50 | + return self._o |
| 51 | + |
| 52 | + @property |
| 53 | + def oe(self): |
| 54 | + if self._oe is None: |
| 55 | + raise AttributeError("SiliconPlatformPort with input direction does not have an " |
| 56 | + "output enable signal") |
| 57 | + return self._oe |
| 58 | + |
| 59 | + @property |
| 60 | + def direction(self): |
| 61 | + return self._direction |
| 62 | + |
| 63 | + @property |
| 64 | + def invert(self): |
| 65 | + return self._invert |
| 66 | + |
| 67 | + def __len__(self): |
| 68 | + if self._direction is io.Direction.Input: |
| 69 | + return len(self._i) |
| 70 | + if self._direction is io.Direction.Output: |
| 71 | + assert len(self._o) == len(self._oe) |
| 72 | + return len(self._o) |
| 73 | + if self._direction is io.Direction.Bidir: |
| 74 | + assert len(self._i) == len(self._o) == len(self._oe) |
| 75 | + return len(self._i) |
| 76 | + assert False # :nocov: |
| 77 | + |
| 78 | + def __getitem__(self, key): |
| 79 | + result = object.__new__(type(self)) |
| 80 | + result._i = None if self._i is None else self._i[key] |
| 81 | + result._o = None if self._o is None else self._o[key] |
| 82 | + result._oe = None if self._oe is None else self._oe[key] |
| 83 | + result._invert = self._invert |
| 84 | + result._direction = self._direction |
| 85 | + return result |
| 86 | + |
| 87 | + def __invert__(self): |
| 88 | + result = object.__new__(type(self)) |
| 89 | + result._i = self._i |
| 90 | + result._o = self._o |
| 91 | + result._oe = self._oe |
| 92 | + result._invert = not self._invert |
| 93 | + result._direction = self._direction |
| 94 | + return result |
| 95 | + |
| 96 | + def __add__(self, other): |
| 97 | + direction = self._direction & other._direction |
| 98 | + result = object.__new__(type(self)) |
| 99 | + result._i = None if direction is io.Direction.Output else Cat(self._i, other._i) |
| 100 | + result._o = None if direction is io.Direction.Input else Cat(self._o, other._o) |
| 101 | + result._oe = None if direction is io.Direction.Input else Cat(self._oe, other._oe) |
| 102 | + result._invert = self._invert |
| 103 | + result._direction = direction |
| 104 | + return result |
| 105 | + |
| 106 | + |
| 107 | +class IOBuffer(io.Buffer): |
| 108 | + def elaborate(self, platform): |
| 109 | + if not isinstance(self.port, SiliconPlatformPort): |
| 110 | + raise TypeError(f"Cannot elaborate SiliconPlatform buffer with port {self.port!r}") |
| 111 | + |
| 112 | + m = Module() |
| 113 | + |
| 114 | + if self.direction is not io.Direction.Input: |
| 115 | + if self.port.invert: |
| 116 | + o_inv = Signal.like(self.o) |
| 117 | + m.d.comb += o_inv.eq(~self.o) |
| 118 | + else: |
| 119 | + o_inv = self.o |
22 | 120 |
|
| 121 | + if self.direction is not io.Direction.Output: |
| 122 | + if self.port.invert: |
| 123 | + i_inv = Signal.like(self.i) |
| 124 | + m.d.comb += self.i.eq(~i_inv) |
| 125 | + else: |
| 126 | + i_inv = self.i |
| 127 | + |
| 128 | + if self.direction in (io.Direction.Input, io.Direction.Bidir): |
| 129 | + m.d.comb += i_inv.eq(self.port.i) |
| 130 | + if self.direction in (io.Direction.Output, io.Direction.Bidir): |
| 131 | + m.d.comb += self.port.o.eq(o_inv) |
| 132 | + m.d.comb += self.port.oe.eq(self.oe) |
| 133 | + |
| 134 | + return m |
| 135 | + |
| 136 | + |
| 137 | +class FFBuffer(io.FFBuffer): |
| 138 | + def elaborate(self, platform): |
| 139 | + if not isinstance(self.port, SiliconPlatformPort): |
| 140 | + raise TypeError(f"Cannot elaborate SiliconPlatform buffer with port {self.port!r}") |
| 141 | + |
| 142 | + m = Module() |
| 143 | + |
| 144 | + m.submodules.io_buffer = io_buffer = IOBuffer(self.direction, self.port) |
| 145 | + |
| 146 | + if self.direction is not io.Direction.Output: |
| 147 | + i_ff = Signal(reset_less=True) |
| 148 | + m.d[self.i_domain] += i_ff.eq(io_buffer.i) |
| 149 | + m.d.comb += self.i.eq(i_ff) |
| 150 | + |
| 151 | + if self.direction is not io.Direction.Input: |
| 152 | + o_ff = Signal(reset_less=True) |
| 153 | + oe_ff = Signal(reset_less=True) |
| 154 | + m.d[self.o_domain] += o_ff.eq(self.o) |
| 155 | + m.d[self.o_domain] += oe_ff.eq(self.oe) |
| 156 | + m.d.comb += io_buffer.o.eq(o_ff) |
| 157 | + m.d.comb += io_buffer.oe.eq(oe_ff) |
| 158 | + |
| 159 | + return m |
| 160 | + |
| 161 | + |
| 162 | +class SiliconPlatform: |
23 | 163 | def __init__(self, pads): |
24 | 164 | self._pads = pads |
25 | | - self._pins = {} |
| 165 | + self._ports = {} |
26 | 166 | self._files = {} |
27 | 167 |
|
28 | 168 | def request(self, name): |
29 | 169 | if "$" in name: |
30 | 170 | raise NameError(f"Reserved character `$` used in pad name `{name}`") |
31 | 171 | if name not in self._pads: |
32 | 172 | raise NameError(f"Pad `{name}` is not defined in chipflow.toml") |
33 | | - if name in self._pins: |
| 173 | + if name in self._ports: |
34 | 174 | raise NameError(f"Pad `{name}` has already been requested") |
35 | 175 |
|
36 | 176 | pad_type = self._pads[name]["type"] |
37 | 177 | # `clk` is used for clock tree synthesis, but treated as `i` in frontend |
38 | 178 | if pad_type in ("i", "clk"): |
39 | | - direction = Direction.Input |
| 179 | + direction = io.Direction.Input |
40 | 180 | elif pad_type in ("o", "oe"): |
41 | | - direction = Direction.Output |
| 181 | + direction = io.Direction.Output |
42 | 182 | elif pad_type == "io": |
43 | | - direction = Direction.Bidir |
| 183 | + direction = io.Direction.Bidir |
44 | 184 | else: |
45 | 185 | assert False |
46 | 186 |
|
47 | | - self._pins[name] = pin = Buffer.Signature(direction, 1).create(path=(name,)) |
48 | | - return pin |
| 187 | + self._ports[name] = port = SiliconPlatformPort(name, direction, 1) |
| 188 | + return port |
| 189 | + |
| 190 | + def get_io_buffer(self, buffer): |
| 191 | + if isinstance(buffer, io.Buffer): |
| 192 | + result = IOBuffer(buffer.direction, buffer.port) |
| 193 | + elif isinstance(buffer, io.FFBuffer): |
| 194 | + result = FFBuffer(buffer.direction, buffer.port, |
| 195 | + i_domain=buffer.i_domain, o_domain=buffer.o_domain) |
| 196 | + else: |
| 197 | + raise TypeError(f"Unsupported buffer type {buffer!r}") |
| 198 | + |
| 199 | + if buffer.direction is not io.Direction.Output: |
| 200 | + result.i = buffer.i |
| 201 | + if buffer.direction is not io.Direction.Input: |
| 202 | + result.o = buffer.o |
| 203 | + result.oe = buffer.oe |
| 204 | + |
| 205 | + return result |
49 | 206 |
|
50 | 207 | def add_file(self, filename, content): |
51 | 208 | if hasattr(content, "read"): |
@@ -73,12 +230,12 @@ def _prepare(self, elaboratable, name="top"): |
73 | 230 |
|
74 | 231 | # Prepare toplevel ports according to chipflow.toml. |
75 | 232 | ports = [] |
76 | | - for pin_name, pin in self._pins.items(): |
77 | | - if pin.signature.direction in (Direction.Input, Direction.Bidir): |
78 | | - ports.append((f"io${pin_name}$i", pin.i, PortDirection.Input)) |
79 | | - if pin.signature.direction in (Direction.Output, Direction.Bidir): |
80 | | - ports.append((f"io${pin_name}$o", pin.o, PortDirection.Output)) |
81 | | - ports.append((f"io${pin_name}$oe", pin.oe, PortDirection.Output)) |
| 233 | + for port_name, port in self._ports.items(): |
| 234 | + if port.direction in (io.Direction.Input, io.Direction.Bidir): |
| 235 | + ports.append((f"io${port_name}$i", port.i, PortDirection.Input)) |
| 236 | + if port.direction in (io.Direction.Output, io.Direction.Bidir): |
| 237 | + ports.append((f"io${port_name}$o", port.o, PortDirection.Output)) |
| 238 | + ports.append((f"io${port_name}$oe", port.oe, PortDirection.Output)) |
82 | 239 |
|
83 | 240 | # Prepare design for RTLIL conversion. |
84 | 241 | return fragment.prepare(ports) |
|
0 commit comments