Skip to content

Commit bf8dbd3

Browse files
committed
Implement RFC 49: GPIO peripheral.
1 parent 1923558 commit bf8dbd3

File tree

2 files changed

+705
-0
lines changed

2 files changed

+705
-0
lines changed

amaranth_soc/gpio.py

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,352 @@
1+
from amaranth import *
2+
from amaranth.lib import enum, wiring
3+
from amaranth.lib.wiring import In, Out, flipped, connect
4+
5+
from . import csr
6+
7+
8+
__all__ = ["PinMode", "PinSignature", "Peripheral"]
9+
10+
11+
class PinMode(enum.Enum, shape=unsigned(2)):
12+
"""GPIO pin mode.
13+
14+
The 2-bit values of this enumeration can be written to a :class:`Peripheral.Mode` field to
15+
configure the pins of a :class:`Peripheral`.
16+
"""
17+
18+
#: `Input-only` mode.
19+
#:
20+
#: The pin output is disabled but remains connected to its :class:`Peripheral.Output` field.
21+
#: Its :attr:`Peripheral.alt_mode` bit is wired to 0.
22+
INPUT_ONLY = 0b00
23+
24+
#: `Push-pull` mode.
25+
#:
26+
#: The pin output is enabled and connected to its :class:`Peripheral.Output` field. Its
27+
#: :attr:`Peripheral.alt_mode` bit is wired to 0.
28+
PUSH_PULL = 0b01
29+
30+
#: `Open-drain` mode.
31+
#:
32+
#: The pin output is enabled when the value of its :class:`Peripheral.Output` field is 0, and
33+
#: is itself wired to 0. Its :attr:`Peripheral.alt_mode` bit is wired to 0.
34+
OPEN_DRAIN = 0b10
35+
36+
#: `Alternate` mode.
37+
#:
38+
#: The pin output is disabled but remains connected to its :class:`Peripheral.Output` field.
39+
#: Its :attr:`Peripheral.alt_mode` bit is wired to 1.
40+
ALTERNATE = 0b11
41+
42+
43+
class PinSignature(wiring.Signature):
44+
"""GPIO pin signature.
45+
46+
Interface attributes
47+
--------------------
48+
i : :class:`Signal`
49+
Input.
50+
o : :class:`Signal`
51+
Output.
52+
oe : :class:`Signal`
53+
Output enable.
54+
"""
55+
def __init__(self):
56+
super().__init__({
57+
"i": In(unsigned(1)),
58+
"o": Out(unsigned(1)),
59+
"oe": Out(unsigned(1)),
60+
})
61+
62+
63+
class Peripheral(wiring.Component):
64+
class Mode(csr.Register, access="rw"):
65+
"""Mode register.
66+
67+
This :class:`csr.Register` contains an array of ``pin_count`` read/write fields. Each field
68+
is 2-bit wide and its possible values are defined by the :class:`PinMode` enumeration.
69+
70+
If ``pin_count`` is 8, then the register has the following fields:
71+
72+
.. bitfield::
73+
:bits: 16
74+
75+
[
76+
{ "name": "pin[0]", "bits": 2, "attr": "RW" },
77+
{ "name": "pin[1]", "bits": 2, "attr": "RW" },
78+
{ "name": "pin[2]", "bits": 2, "attr": "RW" },
79+
{ "name": "pin[3]", "bits": 2, "attr": "RW" },
80+
{ "name": "pin[4]", "bits": 2, "attr": "RW" },
81+
{ "name": "pin[5]", "bits": 2, "attr": "RW" },
82+
{ "name": "pin[6]", "bits": 2, "attr": "RW" },
83+
{ "name": "pin[7]", "bits": 2, "attr": "RW" },
84+
]
85+
86+
Parameters
87+
----------
88+
pin_count : :class:`int`
89+
Number of GPIO pins.
90+
"""
91+
def __init__(self, pin_count):
92+
super().__init__({
93+
"pin": [csr.Field(csr.action.RW, PinMode) for _ in range(pin_count)],
94+
})
95+
96+
class Input(csr.Register, access="r"):
97+
"""Input register.
98+
99+
This :class:`csr.Register` contains an array of ``pin_count`` read-only fields. Each field
100+
is 1-bit wide and driven by the input of its associated pin in the :attr:`Peripheral.pins`
101+
array.
102+
103+
Values sampled from pin inputs go through :attr:`Peripheral.input_stages` synchronization
104+
stages (on a rising edge of ``ClockSignal("sync")``) before reaching the register.
105+
106+
If ``pin_count`` is 8, then the register has the following fields:
107+
108+
.. bitfield::
109+
:bits: 8
110+
111+
[
112+
{ "name": "pin[0]", "bits": 1, "attr": "R" },
113+
{ "name": "pin[1]", "bits": 1, "attr": "R" },
114+
{ "name": "pin[2]", "bits": 1, "attr": "R" },
115+
{ "name": "pin[3]", "bits": 1, "attr": "R" },
116+
{ "name": "pin[4]", "bits": 1, "attr": "R" },
117+
{ "name": "pin[5]", "bits": 1, "attr": "R" },
118+
{ "name": "pin[6]", "bits": 1, "attr": "R" },
119+
{ "name": "pin[7]", "bits": 1, "attr": "R" },
120+
]
121+
122+
Parameters
123+
----------
124+
pin_count : :class:`int`
125+
Number of GPIO pins.
126+
"""
127+
def __init__(self, pin_count):
128+
super().__init__({
129+
"pin": [csr.Field(csr.action.R, unsigned(1)) for _ in range(pin_count)],
130+
})
131+
132+
class Output(csr.Register, access="rw"):
133+
"""Output register.
134+
135+
This :class:`csr.Register` contains an array of ``pin_count`` read/write fields. Each field
136+
is 1-bit wide and drives the output of its associated pin in the :attr:`Peripheral.pins`
137+
array, depending on its associated :class:`~Peripheral.Mode` field.
138+
139+
If ``pin_count`` is 8, then the register has the following fields:
140+
141+
.. bitfield::
142+
:bits: 8
143+
144+
[
145+
{ "name": "pin[0]", "bits": 1, "attr": "RW" },
146+
{ "name": "pin[1]", "bits": 1, "attr": "RW" },
147+
{ "name": "pin[2]", "bits": 1, "attr": "RW" },
148+
{ "name": "pin[3]", "bits": 1, "attr": "RW" },
149+
{ "name": "pin[4]", "bits": 1, "attr": "RW" },
150+
{ "name": "pin[5]", "bits": 1, "attr": "RW" },
151+
{ "name": "pin[6]", "bits": 1, "attr": "RW" },
152+
{ "name": "pin[7]", "bits": 1, "attr": "RW" },
153+
]
154+
155+
Parameters
156+
----------
157+
pin_count : :class:`int`
158+
Number of GPIO pins.
159+
"""
160+
class _FieldAction(csr.FieldAction):
161+
def __init__(self):
162+
super().__init__(shape=unsigned(1), access="rw", members=(
163+
("data", Out(unsigned(1))),
164+
("set", In(unsigned(1))),
165+
("clr", In(unsigned(1))),
166+
))
167+
self._storage = Signal(unsigned(1))
168+
169+
def elaborate(self, platform):
170+
m = Module()
171+
172+
with m.If(self.set != self.clr):
173+
m.d.sync += self._storage.eq(self.set)
174+
with m.Elif(self.port.w_stb):
175+
m.d.sync += self._storage.eq(self.port.w_data)
176+
177+
m.d.comb += [
178+
self.port.r_data.eq(self._storage),
179+
self.data.eq(self._storage),
180+
]
181+
182+
return m
183+
184+
def __init__(self, pin_count):
185+
super().__init__({
186+
"pin": [csr.Field(self._FieldAction) for _ in range(pin_count)],
187+
})
188+
189+
class SetClr(csr.Register, access="w"):
190+
"""Output set/clear register.
191+
192+
This :class:`csr.Register` contains an array of ``pin_count`` write-only fields. Each field
193+
is 2-bit wide; writing it can modify its associated :class:`~Peripheral.Output` field as a
194+
side-effect.
195+
196+
If ``pin_count`` is 8, then the register has the following fields:
197+
198+
.. bitfield::
199+
:bits: 16
200+
201+
[
202+
{ "name": "pin[0]", "bits": 2, "attr": "W" },
203+
{ "name": "pin[1]", "bits": 2, "attr": "W" },
204+
{ "name": "pin[2]", "bits": 2, "attr": "W" },
205+
{ "name": "pin[3]", "bits": 2, "attr": "W" },
206+
{ "name": "pin[4]", "bits": 2, "attr": "W" },
207+
{ "name": "pin[5]", "bits": 2, "attr": "W" },
208+
{ "name": "pin[6]", "bits": 2, "attr": "W" },
209+
{ "name": "pin[7]", "bits": 2, "attr": "W" },
210+
]
211+
212+
- Writing `0b01` to a field sets its associated :class:`~Peripheral.Output` field.
213+
- Writing `0b10` to a field clears its associated :class:`~Peripheral.Output` field.
214+
- Writing `0b00` or `0b11` to a field has no side-effect.
215+
216+
Parameters
217+
----------
218+
pin_count : :class:`int`
219+
Number of GPIO pins.
220+
"""
221+
def __init__(self, pin_count):
222+
pin_fields = {
223+
"set": csr.Field(csr.action.W, unsigned(1)),
224+
"clr": csr.Field(csr.action.W, unsigned(1)),
225+
}
226+
super().__init__({
227+
"pin": [pin_fields for _ in range(pin_count)],
228+
})
229+
230+
"""GPIO peripheral.
231+
232+
Parameters
233+
----------
234+
pin_count : :class:`int`
235+
Number of GPIO pins.
236+
addr_width : :class:`int`
237+
CSR bus address width.
238+
data_width : :class:`int`
239+
CSR bus data width.
240+
name : :class:`str`
241+
CSR bus window name. Optional.
242+
input_stages : :class:`int`
243+
Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input`
244+
register. Optional. Defaults to ``2``.
245+
246+
Attributes
247+
----------
248+
bus : :class:`csr.Interface`
249+
CSR bus interface providing access to registers.
250+
pins : :class:`list` of :class:`wiring.PureInterface` of :class:`PinSignature`
251+
GPIO pin interfaces.
252+
alt_mode : :class:`Signal`
253+
Indicates which members of the :attr:`Peripheral.pins` array are in alternate mode.
254+
255+
Raises
256+
------
257+
:exc:`TypeError`
258+
If ``pin_count`` is not a positive integer.
259+
:exc:`TypeError`
260+
If ``input_stages`` is not a non-negative integer.
261+
"""
262+
def __init__(self, *, pin_count, addr_width, data_width, name=None, input_stages=2):
263+
if not isinstance(pin_count, int) or pin_count <= 0:
264+
raise TypeError(f"Pin count must be a positive integer, not {pin_count!r}")
265+
if not isinstance(input_stages, int) or input_stages < 0:
266+
raise TypeError(f"Input stages must be a non-negative integer, not {input_stages!r}")
267+
268+
regs = csr.Builder(addr_width=addr_width, data_width=data_width, name=name)
269+
270+
self._mode = regs.add("Mode", self.Mode(pin_count))
271+
self._input = regs.add("Input", self.Input(pin_count))
272+
self._output = regs.add("Output", self.Output(pin_count))
273+
self._setclr = regs.add("SetClr", self.SetClr(pin_count))
274+
275+
self._bridge = csr.Bridge(regs.as_memory_map())
276+
277+
super().__init__({
278+
"bus": In(csr.Signature(addr_width=addr_width, data_width=data_width)),
279+
"pins": Out(PinSignature()).array(pin_count),
280+
"alt_mode": Out(unsigned(pin_count)),
281+
})
282+
self.bus.memory_map = self._bridge.bus.memory_map
283+
284+
self._pin_count = pin_count
285+
self._input_stages = input_stages
286+
287+
@property
288+
def pin_count(self):
289+
"""Number of GPIO pins.
290+
291+
Returns
292+
-------
293+
:class:`int`
294+
"""
295+
return self._pin_count
296+
297+
@property
298+
def input_stages(self):
299+
"""Number of synchronization stages between pin inputs and the :class:`~Peripheral.Input`
300+
register.
301+
302+
Returns
303+
-------
304+
:class:`int`
305+
"""
306+
return self._input_stages
307+
308+
def elaborate(self, platform):
309+
m = Module()
310+
m.submodules.bridge = self._bridge
311+
312+
connect(m, flipped(self.bus), self._bridge.bus)
313+
314+
for n, pin in enumerate(self.pins):
315+
pin_i_sync = pin.i
316+
for stage in range(self.input_stages):
317+
pin_i_sync_ff = Signal(reset_less=True, name=f"pin_{n}_i_sync_ff_{stage}")
318+
m.d.sync += pin_i_sync_ff.eq(pin_i_sync)
319+
pin_i_sync = pin_i_sync_ff
320+
del pin_i_sync_ff
321+
322+
m.d.comb += self._input.f.pin[n].r_data.eq(pin_i_sync)
323+
324+
with m.If(self._setclr.f.pin[n].set.w_stb & self._setclr.f.pin[n].set.w_data):
325+
m.d.comb += self._output.f.pin[n].set.eq(1)
326+
with m.If(self._setclr.f.pin[n].clr.w_stb & self._setclr.f.pin[n].clr.w_data):
327+
m.d.comb += self._output.f.pin[n].clr.eq(1)
328+
329+
with m.Switch(self._mode.f.pin[n].data):
330+
with m.Case(PinMode.INPUT_ONLY):
331+
m.d.comb += [
332+
pin.o .eq(self._output.f.pin[n].data),
333+
pin.oe.eq(0),
334+
]
335+
with m.Case(PinMode.PUSH_PULL):
336+
m.d.comb += [
337+
pin.o .eq(self._output.f.pin[n].data),
338+
pin.oe.eq(1),
339+
]
340+
with m.Case(PinMode.OPEN_DRAIN):
341+
m.d.comb += [
342+
pin.o .eq(0),
343+
pin.oe.eq(~self._output.f.pin[n].data),
344+
]
345+
with m.Case(PinMode.ALTERNATE):
346+
m.d.comb += [
347+
pin.o .eq(self._output.f.pin[n].data),
348+
pin.oe.eq(0),
349+
]
350+
m.d.comb += self.alt_mode[n].eq(1)
351+
352+
return m

0 commit comments

Comments
 (0)