Skip to content

Commit c6bc9b4

Browse files
wanda-phiwhitequark
authored andcommitted
hdl.ir: add IOBufferInstance.
1 parent 85bb5ee commit c6bc9b4

File tree

4 files changed

+193
-23
lines changed

4 files changed

+193
-23
lines changed

amaranth/build/plat.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from .. import __version__
1010
from .._toolchain import *
1111
from ..hdl import *
12+
from ..hdl._ir import IOBufferInstance
1213
from ..hdl._xfrm import DomainLowerer
1314
from ..lib.cdc import ResetSynchronizer
1415
from ..back import rtlil, verilog
@@ -221,11 +222,10 @@ def get_tristate(self, pin, port, attrs, invert):
221222
valid_xdrs=(0,), valid_attrs=None)
222223

223224
m = Module()
224-
m.submodules += Instance("$tribuf",
225-
p_WIDTH=pin.width,
226-
i_EN=pin.oe,
227-
i_A=self._invert_if(invert, pin.o),
228-
o_Y=port,
225+
m.submodules += IOBufferInstance(
226+
pad=port,
227+
o=self._invert_if(invert, pin.o),
228+
oe=pin.oe,
229229
)
230230
return m
231231

@@ -234,13 +234,14 @@ def get_input_output(self, pin, port, attrs, invert):
234234
valid_xdrs=(0,), valid_attrs=None)
235235

236236
m = Module()
237-
m.submodules += Instance("$tribuf",
238-
p_WIDTH=pin.width,
239-
i_EN=pin.oe,
240-
i_A=self._invert_if(invert, pin.o),
241-
o_Y=port,
237+
i = Signal.like(pin.i)
238+
m.submodules += IOBufferInstance(
239+
pad=port,
240+
i=i,
241+
o=self._invert_if(invert, pin.o),
242+
oe=pin.oe,
242243
)
243-
m.d.comb += pin.i.eq(self._invert_if(invert, port))
244+
m.d.comb += pin.i.eq(self._invert_if(invert, i))
244245
return m
245246

246247
def get_diff_input(self, pin, port, attrs, invert):

amaranth/hdl/_ir.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
__all__ = [
1313
"UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance",
14-
"PortDirection", "Design", "build_netlist",
14+
"IOBufferInstance", "PortDirection", "Design", "build_netlist",
1515
]
1616

1717

@@ -196,7 +196,7 @@ def add_subfrag(registry, entity, entry):
196196
# Always flatten subfragments that explicitly request it.
197197
flatten_subfrags.add((subfrag, subfrag_hierarchy))
198198

199-
if isinstance(subfrag, (Instance, MemoryInstance)):
199+
if isinstance(subfrag, (Instance, MemoryInstance, IOBufferInstance)):
200200
# Never flatten instances.
201201
continue
202202

@@ -460,6 +460,36 @@ def __init__(self, type, *args, src_loc=None, src_loc_at=0, **kwargs):
460460
.format(kw, arg))
461461

462462

463+
class IOBufferInstance(Fragment):
464+
def __init__(self, pad, *, i=None, o=None, oe=None, src_loc_at=0, src_loc=None):
465+
super().__init__()
466+
467+
self.pad = _ast.Value.cast(pad)
468+
if i is None:
469+
self.i = None
470+
else:
471+
self.i = _ast.Value.cast(i)
472+
if len(self.pad) != len(self.i):
473+
raise ValueError(f"`pad` length ({len(self.pad)}) doesn't match `i` length ({len(self.i)})")
474+
if o is None:
475+
if oe is not None:
476+
raise ValueError("`oe` must not be used if `o` is not used")
477+
self.o = _ast.Const(0, len(self.pad))
478+
self.oe = _ast.Const(0)
479+
else:
480+
self.o = _ast.Value.cast(o)
481+
if len(self.pad) != len(self.o):
482+
raise ValueError(f"`pad` length ({len(self.pad)}) doesn't match `o` length ({len(self.o)})")
483+
if oe is None:
484+
self.oe = _ast.Const(1)
485+
else:
486+
self.oe = _ast.Value.cast(oe)
487+
if len(self.oe) != 1:
488+
raise ValueError(f"`oe` length ({len(self.oe)}) must be 1")
489+
490+
self.src_loc = src_loc or tracer.get_src_loc(src_loc_at)
491+
492+
463493
class Design:
464494
"""Represents a design ready for simulation or netlist building.
465495
@@ -943,13 +973,15 @@ def emit_stmt(self, module_idx: int, fragment: _ir.Fragment, domain: str,
943973
else:
944974
assert False # :nocov:
945975

946-
def emit_tribuf(self, module_idx: int, instance: _ir.Instance):
947-
pad = self.emit_lhs(instance.named_ports["Y"][0])
948-
o, _signed = self.emit_rhs(module_idx, instance.named_ports["A"][0])
949-
(oe,), _signed = self.emit_rhs(module_idx, instance.named_ports["EN"][0])
976+
def emit_iobuffer(self, module_idx: int, instance: _ir.IOBufferInstance):
977+
pad = self.emit_lhs(instance.pad)
978+
o, _signed = self.emit_rhs(module_idx, instance.o)
979+
(oe,), _signed = self.emit_rhs(module_idx, instance.oe)
950980
assert len(pad) == len(o)
951981
cell = _nir.IOBuffer(module_idx, pad=pad, o=o, oe=oe, src_loc=instance.src_loc)
952-
self.netlist.add_cell(cell)
982+
value = self.netlist.add_value_cell(len(pad), cell)
983+
if instance.i is not None:
984+
self.connect(self.emit_lhs(instance.i), value, src_loc=instance.src_loc)
953985

954986
def emit_memory(self, module_idx: int, fragment: '_mem.MemoryInstance', name: str):
955987
cell = _nir.Memory(module_idx,
@@ -1130,10 +1162,7 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
11301162
fragment_name = self.design.fragment_names[fragment]
11311163
if isinstance(fragment, _ir.Instance):
11321164
assert parent_module_idx is not None
1133-
if fragment.type == "$tribuf":
1134-
self.emit_tribuf(parent_module_idx, fragment)
1135-
else:
1136-
self.emit_instance(parent_module_idx, fragment, name=fragment_name[-1])
1165+
self.emit_instance(parent_module_idx, fragment, name=fragment_name[-1])
11371166
elif isinstance(fragment, _mem.MemoryInstance):
11381167
assert parent_module_idx is not None
11391168
memory = self.emit_memory(parent_module_idx, fragment, name=fragment_name[-1])
@@ -1142,6 +1171,9 @@ def emit_fragment(self, fragment: _ir.Fragment, parent_module_idx: 'int | None',
11421171
write_ports.append(self.emit_write_port(parent_module_idx, fragment, port, memory))
11431172
for port in fragment._read_ports:
11441173
self.emit_read_port(parent_module_idx, fragment, port, memory, write_ports)
1174+
elif isinstance(fragment, _ir.IOBufferInstance):
1175+
assert parent_module_idx is not None
1176+
self.emit_iobuffer(parent_module_idx, fragment)
11451177
elif type(fragment) is _ir.Fragment:
11461178
module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc)
11471179
signal_names = self.design.signal_names[fragment]

amaranth/hdl/_xfrm.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,23 @@ def on_fragment(self, fragment):
277277
new_fragment = Instance(fragment.type, src_loc=fragment.src_loc)
278278
new_fragment.parameters = OrderedDict(fragment.parameters)
279279
self.map_named_ports(fragment, new_fragment)
280+
elif isinstance(fragment, IOBufferInstance):
281+
if hasattr(self, "on_value"):
282+
new_fragment = IOBufferInstance(
283+
pad=self.on_value(fragment.pad),
284+
i=self.on_value(fragment.i) if fragment.i is not None else None,
285+
o=self.on_value(fragment.o),
286+
oe=self.on_value(fragment.oe),
287+
src_loc=fragment.src_loc,
288+
)
289+
else:
290+
new_fragment = IOBufferInstance(
291+
pad=fragment.pad,
292+
i=fragment.i,
293+
o=fragment.o,
294+
oe=fragment.oe,
295+
src_loc=fragment.src_loc,
296+
)
280297
else:
281298
new_fragment = Fragment(src_loc=fragment.src_loc)
282299
new_fragment.flatten = fragment.flatten

tests/test_hdl_ir.py

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,4 +938,124 @@ def test_origins_disable(self):
938938
del inst.origins
939939
elab = ElaboratesTo(inst)
940940
frag = Fragment.get(elab, platform=None)
941-
self.assertFalse(hasattr(frag, "_origins"))
941+
self.assertFalse(hasattr(frag, "_origins"))
942+
943+
944+
class IOBufferTestCase(FHDLTestCase):
945+
def test_nir_i(self):
946+
pad = Signal(4)
947+
i = Signal(4)
948+
f = Fragment()
949+
f.add_subfragment(IOBufferInstance(pad, i=i))
950+
nl = build_netlist(f, ports=[pad, i])
951+
self.assertRepr(nl, """
952+
(
953+
(module 0 None ('top')
954+
(inout 'pad' 0.2:6)
955+
(output 'i' 1.0:4)
956+
)
957+
(cell 0 0 (top
958+
(output 'i' 1.0:4)
959+
(inout 'pad' 2:6)
960+
))
961+
(cell 1 0 (iob 0.2:6 4'd0 0))
962+
)
963+
""")
964+
965+
def test_nir_o(self):
966+
pad = Signal(4)
967+
o = Signal(4)
968+
f = Fragment()
969+
f.add_subfragment(IOBufferInstance(pad, o=o))
970+
nl = build_netlist(f, ports=[pad, o])
971+
self.assertRepr(nl, """
972+
(
973+
(module 0 None ('top')
974+
(input 'o' 0.6:10)
975+
(inout 'pad' 0.2:6)
976+
)
977+
(cell 0 0 (top
978+
(input 'o' 6:10)
979+
(inout 'pad' 2:6)
980+
))
981+
(cell 1 0 (iob 0.2:6 0.6:10 1))
982+
)
983+
""")
984+
985+
def test_nir_oe(self):
986+
pad = Signal(4)
987+
o = Signal(4)
988+
oe = Signal()
989+
f = Fragment()
990+
f.add_subfragment(IOBufferInstance(pad, o=o, oe=oe))
991+
nl = build_netlist(f, ports=[pad, o, oe])
992+
self.assertRepr(nl, """
993+
(
994+
(module 0 None ('top')
995+
(input 'o' 0.6:10)
996+
(input 'oe' 0.10)
997+
(inout 'pad' 0.2:6)
998+
)
999+
(cell 0 0 (top
1000+
(input 'o' 6:10)
1001+
(input 'oe' 10:11)
1002+
(inout 'pad' 2:6)
1003+
))
1004+
(cell 1 0 (iob 0.2:6 0.6:10 0.10))
1005+
)
1006+
""")
1007+
1008+
def test_nir_io(self):
1009+
pad = Signal(4)
1010+
i = Signal(4)
1011+
o = Signal(4)
1012+
oe = Signal()
1013+
f = Fragment()
1014+
f.add_subfragment(IOBufferInstance(pad, i=i, o=o, oe=oe))
1015+
nl = build_netlist(f, ports=[pad, i, o, oe])
1016+
self.assertRepr(nl, """
1017+
(
1018+
(module 0 None ('top')
1019+
(input 'o' 0.6:10)
1020+
(input 'oe' 0.10)
1021+
(inout 'pad' 0.2:6)
1022+
(output 'i' 1.0:4)
1023+
)
1024+
(cell 0 0 (top
1025+
(output 'i' 1.0:4)
1026+
(input 'o' 6:10)
1027+
(input 'oe' 10:11)
1028+
(inout 'pad' 2:6)
1029+
))
1030+
(cell 1 0 (iob 0.2:6 0.6:10 0.10))
1031+
)
1032+
""")
1033+
1034+
def test_wrong_i(self):
1035+
pad = Signal(4)
1036+
i = Signal()
1037+
with self.assertRaisesRegex(ValueError,
1038+
r"^`pad` length \(4\) doesn't match `i` length \(1\)"):
1039+
IOBufferInstance(pad, i=i)
1040+
1041+
def test_wrong_o(self):
1042+
pad = Signal(4)
1043+
o = Signal()
1044+
with self.assertRaisesRegex(ValueError,
1045+
r"^`pad` length \(4\) doesn't match `o` length \(1\)"):
1046+
IOBufferInstance(pad, o=o)
1047+
1048+
def test_wrong_oe(self):
1049+
pad = Signal(4)
1050+
o = Signal(4)
1051+
oe = Signal(4)
1052+
with self.assertRaisesRegex(ValueError,
1053+
r"^`oe` length \(4\) must be 1"):
1054+
IOBufferInstance(pad, o=o, oe=oe)
1055+
1056+
def test_wrong_oe_without_o(self):
1057+
pad = Signal(4)
1058+
oe = Signal()
1059+
with self.assertRaisesRegex(ValueError,
1060+
r"^`oe` must not be used if `o` is not used"):
1061+
IOBufferInstance(pad, oe=oe)

0 commit comments

Comments
 (0)