Skip to content

Commit 7445760

Browse files
wanda-phiwhitequarkmcclure
committed
Implement RFC 53: Low-level I/O primitives.
Co-authored-by: Catherine <whitequark@whitequark.org> Co-authored-by: mcclure <mcclure@users.noreply.github.com>
1 parent 18b54de commit 7445760

File tree

16 files changed

+1357
-429
lines changed

16 files changed

+1357
-429
lines changed

amaranth/back/rtlil.py

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def __init__(self, memid):
272272

273273

274274
class ModuleEmitter:
275-
def __init__(self, builder, netlist, module, name_map, empty_checker):
275+
def __init__(self, builder, netlist: _nir.Netlist, module: _nir.Module, name_map, empty_checker):
276276
self.builder = builder
277277
self.netlist = netlist
278278
self.module = module
@@ -293,6 +293,7 @@ def __init__(self, builder, netlist, module, name_map, empty_checker):
293293
self.sigport_wires = {} # signal or port name -> (wire, value)
294294
self.driven_sigports = set() # set of signal or port name
295295
self.nets = {} # net -> (wire name, bit idx)
296+
self.ionets = {} # ionet -> (wire name, bit idx)
296297
self.cell_wires = {} # cell idx -> wire name
297298
self.instance_wires = {} # (cell idx, output name) -> wire name
298299

@@ -302,6 +303,7 @@ def emit(self):
302303
self.collect_init_attrs()
303304
self.emit_signal_wires()
304305
self.emit_port_wires()
306+
self.emit_io_port_wires()
305307
self.emit_cell_wires()
306308
self.emit_submodule_wires()
307309
self.emit_connects()
@@ -406,11 +408,28 @@ def emit_port_wires(self):
406408
self.sigport_wires[name] = (wire, value)
407409
if flow == _nir.ModuleNetFlow.Output:
408410
continue
409-
# If we just emitted an input or inout port, it is driving the value.
411+
# If we just emitted an input port, it is driving the value.
410412
self.driven_sigports.add(name)
411413
for bit, net in enumerate(value):
412414
self.nets[net] = (wire, bit)
413415

416+
def emit_io_port_wires(self):
417+
for idx, (name, (value, dir)) in enumerate(self.module.io_ports.items()):
418+
port_id = idx + len(self.module.ports)
419+
if self.module.parent is None:
420+
port = self.netlist.io_ports[value[0].port]
421+
attrs = port.attrs
422+
src_loc = port.src_loc
423+
else:
424+
attrs = {}
425+
src_loc = None
426+
wire = self.builder.wire(width=len(value),
427+
port_id=port_id, port_kind=dir.value,
428+
name=name, attrs=attrs,
429+
src=_src(src_loc))
430+
for bit, net in enumerate(value):
431+
self.ionets[net] = (wire, bit)
432+
414433
def emit_driven_wire(self, value):
415434
# Emits a wire for a value, in preparation for driving it.
416435
if value in self.value_names:
@@ -454,7 +473,9 @@ def emit_cell_wires(self):
454473
elif isinstance(cell, _nir.Initial):
455474
width = 1
456475
elif isinstance(cell, _nir.IOBuffer):
457-
width = len(cell.pad)
476+
if cell.dir is _nir.IODirection.Output:
477+
continue # No outputs.
478+
width = len(cell.port)
458479
else:
459480
assert False # :nocov:
460481
# Single output cell connected to a wire.
@@ -503,6 +524,28 @@ def sigspec(self, *parts: '_nir.Net | Iterable[_nir.Net]'):
503524
return chunks[0]
504525
return "{ " + " ".join(reversed(chunks)) + " }"
505526

527+
def io_sigspec(self, value: _nir.IOValue):
528+
chunks = []
529+
begin_pos = 0
530+
while begin_pos < len(value):
531+
end_pos = begin_pos
532+
wire, start_bit = self.ionets[value[begin_pos]]
533+
bit = start_bit
534+
while (end_pos < len(value) and
535+
self.ionets[value[end_pos]] == (wire, bit)):
536+
end_pos += 1
537+
bit += 1
538+
width = end_pos - begin_pos
539+
if width == 1:
540+
chunks.append(f"{wire} [{start_bit}]")
541+
else:
542+
chunks.append(f"{wire} [{start_bit + width - 1}:{start_bit}]")
543+
begin_pos = end_pos
544+
545+
if len(chunks) == 1:
546+
return chunks[0]
547+
return "{ " + " ".join(reversed(chunks)) + " }"
548+
506549
def emit_connects(self):
507550
for name, (wire, value) in self.sigport_wires.items():
508551
if name not in self.driven_sigports:
@@ -513,10 +556,13 @@ def emit_submodules(self):
513556
submodule = self.netlist.modules[submodule_idx]
514557
if not self.empty_checker.is_empty(submodule_idx):
515558
dotted_name = ".".join(submodule.name)
516-
self.builder.cell(f"\\{dotted_name}", submodule.name[-1], ports={
517-
name: self.sigspec(value)
518-
for name, (value, _flow) in submodule.ports.items()
519-
}, src=_src(submodule.cell_src_loc))
559+
ports = {}
560+
for name, (value, _flow) in submodule.ports.items():
561+
ports[name] = self.sigspec(value)
562+
for name, (value, _dir) in submodule.io_ports.items():
563+
ports[name] = self.io_sigspec(value)
564+
self.builder.cell(f"\\{dotted_name}", submodule.name[-1], ports=ports,
565+
src=_src(submodule.cell_src_loc))
520566

521567
def emit_assignment_list(self, cell_idx, cell):
522568
def emit_assignments(case, cond):
@@ -761,14 +807,19 @@ def emit_flip_flop(self, cell_idx, cell):
761807
self.builder.cell(cell_type, ports=ports, params=params, src=_src(cell.src_loc))
762808

763809
def emit_io_buffer(self, cell_idx, cell):
764-
self.builder.cell("$tribuf", ports={
765-
"Y": self.sigspec(cell.pad),
766-
"A": self.sigspec(cell.o),
767-
"EN": self.sigspec(cell.oe),
768-
}, params={
769-
"WIDTH": len(cell.pad),
770-
}, src=_src(cell.src_loc))
771-
self.builder.connect(self.cell_wires[cell_idx], self.sigspec(cell.pad))
810+
if cell.dir is not _nir.IODirection.Input:
811+
if cell.dir is _nir.IODirection.Output and cell.oe == _nir.Net.from_const(1):
812+
self.builder.connect(self.io_sigspec(cell.port), self.sigspec(cell.o))
813+
else:
814+
self.builder.cell("$tribuf", ports={
815+
"Y": self.io_sigspec(cell.port),
816+
"A": self.sigspec(cell.o),
817+
"EN": self.sigspec(cell.oe),
818+
}, params={
819+
"WIDTH": len(cell.port),
820+
}, src=_src(cell.src_loc))
821+
if cell.dir is not _nir.IODirection.Output:
822+
self.builder.connect(self.cell_wires[cell_idx], self.io_sigspec(cell.port))
772823

773824
def emit_memory(self, cell_idx, cell):
774825
memory_info = self.memories[cell_idx]
@@ -950,8 +1001,8 @@ def emit_instance(self, cell_idx, cell):
9501001
ports[name] = self.sigspec(nets)
9511002
for name in cell.ports_o:
9521003
ports[name] = self.instance_wires[cell_idx, name]
953-
for name, nets in cell.ports_io.items():
954-
ports[name] = self.sigspec(nets)
1004+
for name, (ionets, _dir) in cell.ports_io.items():
1005+
ports[name] = self.io_sigspec(ionets)
9551006
self.builder.cell(f"\\{cell.type}", cell.name, ports=ports, params=cell.parameters,
9561007
attrs=cell.attributes, src=_src(cell.src_loc))
9571008

amaranth/build/plat.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ def get_tristate(self, pin, port, attrs, invert):
222222

223223
m = Module()
224224
m.submodules += IOBufferInstance(
225-
pad=port,
225+
port=port,
226226
o=self._invert_if(invert, pin.o),
227227
oe=pin.oe,
228228
)
@@ -235,7 +235,7 @@ def get_input_output(self, pin, port, attrs, invert):
235235
m = Module()
236236
i = Signal.like(pin.i)
237237
m.submodules += IOBufferInstance(
238-
pad=port,
238+
port=port,
239239
i=i,
240240
o=self._invert_if(invert, pin.o),
241241
oe=pin.oe,

amaranth/build/res.py

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ class ResourceError(Exception):
1414
pass
1515

1616

17+
class SingleEndedPort:
18+
def __init__(self, io):
19+
self.io = io
20+
21+
22+
class DifferentialPort:
23+
def __init__(self, p, n):
24+
self.p = p
25+
self.n = n
26+
27+
28+
class PortGroup:
29+
pass
30+
31+
1732
class ResourceManager:
1833
def __init__(self, resources, connectors):
1934
self.resources = OrderedDict()
@@ -113,21 +128,13 @@ def resolve(resource, dir, xdr, path, attrs):
113128
attrs[attr_key] = attr_value
114129

115130
if isinstance(resource.ios[0], Subsignal):
116-
members = OrderedDict()
117-
sig_members = OrderedDict()
131+
res = PortGroup()
118132
for sub in resource.ios:
119133
member = resolve(sub, dir[sub.name], xdr[sub.name],
120134
path=path + (sub.name,),
121135
attrs={**attrs, **sub.attrs})
122-
members[sub.name] = member
123-
sig_members[sub.name] = wiring.Out(member.signature)
124-
signature = wiring.Signature(sig_members)
125-
# Provide members ourselves instead of having the constructor
126-
# create ones for us.
127-
intf = object.__new__(wiring.PureInterface)
128-
intf.signature = signature
129-
intf.__dict__.update(members)
130-
return intf
136+
setattr(res, sub.name, member)
137+
return res
131138

132139
elif isinstance(resource.ios[0], (Pins, DiffPairs)):
133140
phys = resource.ios[0]
@@ -136,17 +143,21 @@ def resolve(resource, dir, xdr, path, attrs):
136143
# ignore it as well.
137144
if isinstance(phys, Pins):
138145
phys_names = phys.names
139-
port = wiring.Signature({"io": wiring.In(len(phys))}).create(path=path)
146+
io = IOPort(len(phys), name="__".join(path) + "__io")
147+
port = SingleEndedPort(io)
140148
if isinstance(phys, DiffPairs):
141149
phys_names = []
142-
sig_members = {}
143150
if not self.should_skip_port_component(None, attrs, "p"):
151+
p = IOPort(len(phys), name="__".join(path) + "__p")
144152
phys_names += phys.p.names
145-
sig_members["p"] = wiring.In(len(phys))
153+
else:
154+
p = None
146155
if not self.should_skip_port_component(None, attrs, "n"):
156+
n = IOPort(len(phys), name="__".join(path) + "__n")
147157
phys_names += phys.n.names
148-
sig_members["n"] = wiring.In(len(phys))
149-
port = wiring.Signature(sig_members).create(path=path)
158+
else:
159+
n = None
160+
port = DifferentialPort(p, n)
150161
if dir == "-":
151162
pin = None
152163
else:

amaranth/hdl/__init__.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
from ._ast import Value, ValueCastable, ValueLike
33
from ._ast import Const, C, Mux, Cat, Array, Signal, ClockSignal, ResetSignal
44
from ._ast import Format, Print, Assert, Assume, Cover
5+
from ._ast import IOValue, IOPort
56
from ._dsl import SyntaxError, SyntaxWarning, Module
67
from ._cd import DomainError, ClockDomain
7-
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment, Instance
8+
from ._ir import UnusedElaboratable, Elaboratable, DriverConflict, Fragment
9+
from ._ir import Instance, IOBufferInstance
810
from ._mem import MemoryIdentity, MemoryInstance, Memory, ReadPort, WritePort, DummyPort
911
from ._rec import Record
1012
from ._xfrm import DomainRenamer, ResetInserter, EnableInserter
@@ -16,12 +18,14 @@
1618
"Value", "ValueCastable", "ValueLike",
1719
"Const", "C", "Mux", "Cat", "Array", "Signal", "ClockSignal", "ResetSignal",
1820
"Format", "Print", "Assert", "Assume", "Cover",
21+
"IOValue", "IOPort",
1922
# _dsl
2023
"SyntaxError", "SyntaxWarning", "Module",
2124
# _cd
2225
"DomainError", "ClockDomain",
2326
# _ir
24-
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance",
27+
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment",
28+
"Instance", "IOBufferInstance",
2529
# _mem
2630
"MemoryIdentity", "MemoryInstance", "Memory", "ReadPort", "WritePort", "DummyPort",
2731
# _rec

0 commit comments

Comments
 (0)