diff --git a/amaranth/build/plat.py b/amaranth/build/plat.py index 834f5df..52afd37 100644 --- a/amaranth/build/plat.py +++ b/amaranth/build/plat.py @@ -9,6 +9,7 @@ import jinja2 from .. import __version__ from .._toolchain import * from ..hdl import * +from ..hdl._ir import IOBufferInstance from ..hdl._xfrm import DomainLowerer from ..lib.cdc import ResetSynchronizer from ..back import rtlil, verilog @@ -221,11 +222,10 @@ class Platform(ResourceManager, metaclass=ABCMeta): valid_xdrs=(0,), valid_attrs=None) m = Module() - m.submodules += Instance("$tribuf", - p_WIDTH=pin.width, - i_EN=pin.oe, - i_A=self._invert_if(invert, pin.o), - o_Y=port, + m.submodules += IOBufferInstance( + pad=port, + o=self._invert_if(invert, pin.o), + oe=pin.oe, ) return m @@ -234,13 +234,14 @@ class Platform(ResourceManager, metaclass=ABCMeta): valid_xdrs=(0,), valid_attrs=None) m = Module() - m.submodules += Instance("$tribuf", - p_WIDTH=pin.width, - i_EN=pin.oe, - i_A=self._invert_if(invert, pin.o), - o_Y=port, + i = Signal.like(pin.i) + m.submodules += IOBufferInstance( + pad=port, + i=i, + o=self._invert_if(invert, pin.o), + oe=pin.oe, ) - m.d.comb += pin.i.eq(self._invert_if(invert, port)) + m.d.comb += pin.i.eq(self._invert_if(invert, i)) return m def get_diff_input(self, pin, port, attrs, invert): diff --git a/amaranth/hdl/_ir.py b/amaranth/hdl/_ir.py index 43a9ed9..16c1371 100644 --- a/amaranth/hdl/_ir.py +++ b/amaranth/hdl/_ir.py @@ -11,7 +11,7 @@ from . import _ast, _cd, _ir, _nir __all__ = [ "UnusedElaboratable", "Elaboratable", "DriverConflict", "Fragment", "Instance", - "PortDirection", "Design", "build_netlist", + "IOBufferInstance", "PortDirection", "Design", "build_netlist", ] @@ -196,7 +196,7 @@ class Fragment: # Always flatten subfragments that explicitly request it. flatten_subfrags.add((subfrag, subfrag_hierarchy)) - if isinstance(subfrag, (Instance, MemoryInstance)): + if isinstance(subfrag, (Instance, MemoryInstance, IOBufferInstance)): # Never flatten instances. continue @@ -460,6 +460,36 @@ class Instance(Fragment): .format(kw, arg)) +class IOBufferInstance(Fragment): + def __init__(self, pad, *, i=None, o=None, oe=None, src_loc_at=0, src_loc=None): + super().__init__() + + self.pad = _ast.Value.cast(pad) + if i is None: + self.i = None + else: + self.i = _ast.Value.cast(i) + if len(self.pad) != len(self.i): + raise ValueError(f"`pad` length ({len(self.pad)}) doesn't match `i` length ({len(self.i)})") + if o is None: + if oe is not None: + raise ValueError("`oe` must not be used if `o` is not used") + self.o = _ast.Const(0, len(self.pad)) + self.oe = _ast.Const(0) + else: + self.o = _ast.Value.cast(o) + if len(self.pad) != len(self.o): + raise ValueError(f"`pad` length ({len(self.pad)}) doesn't match `o` length ({len(self.o)})") + if oe is None: + self.oe = _ast.Const(1) + else: + self.oe = _ast.Value.cast(oe) + if len(self.oe) != 1: + raise ValueError(f"`oe` length ({len(self.oe)}) must be 1") + + self.src_loc = src_loc or tracer.get_src_loc(src_loc_at) + + class Design: """Represents a design ready for simulation or netlist building. @@ -943,13 +973,15 @@ class NetlistEmitter: else: assert False # :nocov: - def emit_tribuf(self, module_idx: int, instance: _ir.Instance): - pad = self.emit_lhs(instance.named_ports["Y"][0]) - o, _signed = self.emit_rhs(module_idx, instance.named_ports["A"][0]) - (oe,), _signed = self.emit_rhs(module_idx, instance.named_ports["EN"][0]) + def emit_iobuffer(self, module_idx: int, instance: _ir.IOBufferInstance): + pad = self.emit_lhs(instance.pad) + o, _signed = self.emit_rhs(module_idx, instance.o) + (oe,), _signed = self.emit_rhs(module_idx, instance.oe) assert len(pad) == len(o) cell = _nir.IOBuffer(module_idx, pad=pad, o=o, oe=oe, src_loc=instance.src_loc) - self.netlist.add_cell(cell) + value = self.netlist.add_value_cell(len(pad), cell) + if instance.i is not None: + self.connect(self.emit_lhs(instance.i), value, src_loc=instance.src_loc) def emit_memory(self, module_idx: int, fragment: '_mem.MemoryInstance', name: str): cell = _nir.Memory(module_idx, @@ -1130,10 +1162,7 @@ class NetlistEmitter: fragment_name = self.design.fragment_names[fragment] if isinstance(fragment, _ir.Instance): assert parent_module_idx is not None - if fragment.type == "$tribuf": - self.emit_tribuf(parent_module_idx, fragment) - else: - self.emit_instance(parent_module_idx, fragment, name=fragment_name[-1]) + self.emit_instance(parent_module_idx, fragment, name=fragment_name[-1]) elif isinstance(fragment, _mem.MemoryInstance): assert parent_module_idx is not None memory = self.emit_memory(parent_module_idx, fragment, name=fragment_name[-1]) @@ -1142,6 +1171,9 @@ class NetlistEmitter: write_ports.append(self.emit_write_port(parent_module_idx, fragment, port, memory)) for port in fragment._read_ports: self.emit_read_port(parent_module_idx, fragment, port, memory, write_ports) + elif isinstance(fragment, _ir.IOBufferInstance): + assert parent_module_idx is not None + self.emit_iobuffer(parent_module_idx, fragment) elif type(fragment) is _ir.Fragment: module_idx = self.netlist.add_module(parent_module_idx, fragment_name, src_loc=fragment.src_loc, cell_src_loc=cell_src_loc) signal_names = self.design.signal_names[fragment] diff --git a/amaranth/hdl/_xfrm.py b/amaranth/hdl/_xfrm.py index 2dbd266..8a02338 100644 --- a/amaranth/hdl/_xfrm.py +++ b/amaranth/hdl/_xfrm.py @@ -277,6 +277,23 @@ class FragmentTransformer: new_fragment = Instance(fragment.type, src_loc=fragment.src_loc) new_fragment.parameters = OrderedDict(fragment.parameters) self.map_named_ports(fragment, new_fragment) + elif isinstance(fragment, IOBufferInstance): + if hasattr(self, "on_value"): + new_fragment = IOBufferInstance( + pad=self.on_value(fragment.pad), + i=self.on_value(fragment.i) if fragment.i is not None else None, + o=self.on_value(fragment.o), + oe=self.on_value(fragment.oe), + src_loc=fragment.src_loc, + ) + else: + new_fragment = IOBufferInstance( + pad=fragment.pad, + i=fragment.i, + o=fragment.o, + oe=fragment.oe, + src_loc=fragment.src_loc, + ) else: new_fragment = Fragment(src_loc=fragment.src_loc) new_fragment.flatten = fragment.flatten diff --git a/tests/test_hdl_ir.py b/tests/test_hdl_ir.py index 94251b6..a89879b 100644 --- a/tests/test_hdl_ir.py +++ b/tests/test_hdl_ir.py @@ -938,4 +938,124 @@ class OriginsTestCase(FHDLTestCase): del inst.origins elab = ElaboratesTo(inst) frag = Fragment.get(elab, platform=None) - self.assertFalse(hasattr(frag, "_origins")) \ No newline at end of file + self.assertFalse(hasattr(frag, "_origins")) + + +class IOBufferTestCase(FHDLTestCase): + def test_nir_i(self): + pad = Signal(4) + i = Signal(4) + f = Fragment() + f.add_subfragment(IOBufferInstance(pad, i=i)) + nl = build_netlist(f, ports=[pad, i]) + self.assertRepr(nl, """ + ( + (module 0 None ('top') + (inout 'pad' 0.2:6) + (output 'i' 1.0:4) + ) + (cell 0 0 (top + (output 'i' 1.0:4) + (inout 'pad' 2:6) + )) + (cell 1 0 (iob 0.2:6 4'd0 0)) + ) + """) + + def test_nir_o(self): + pad = Signal(4) + o = Signal(4) + f = Fragment() + f.add_subfragment(IOBufferInstance(pad, o=o)) + nl = build_netlist(f, ports=[pad, o]) + self.assertRepr(nl, """ + ( + (module 0 None ('top') + (input 'o' 0.6:10) + (inout 'pad' 0.2:6) + ) + (cell 0 0 (top + (input 'o' 6:10) + (inout 'pad' 2:6) + )) + (cell 1 0 (iob 0.2:6 0.6:10 1)) + ) + """) + + def test_nir_oe(self): + pad = Signal(4) + o = Signal(4) + oe = Signal() + f = Fragment() + f.add_subfragment(IOBufferInstance(pad, o=o, oe=oe)) + nl = build_netlist(f, ports=[pad, o, oe]) + self.assertRepr(nl, """ + ( + (module 0 None ('top') + (input 'o' 0.6:10) + (input 'oe' 0.10) + (inout 'pad' 0.2:6) + ) + (cell 0 0 (top + (input 'o' 6:10) + (input 'oe' 10:11) + (inout 'pad' 2:6) + )) + (cell 1 0 (iob 0.2:6 0.6:10 0.10)) + ) + """) + + def test_nir_io(self): + pad = Signal(4) + i = Signal(4) + o = Signal(4) + oe = Signal() + f = Fragment() + f.add_subfragment(IOBufferInstance(pad, i=i, o=o, oe=oe)) + nl = build_netlist(f, ports=[pad, i, o, oe]) + self.assertRepr(nl, """ + ( + (module 0 None ('top') + (input 'o' 0.6:10) + (input 'oe' 0.10) + (inout 'pad' 0.2:6) + (output 'i' 1.0:4) + ) + (cell 0 0 (top + (output 'i' 1.0:4) + (input 'o' 6:10) + (input 'oe' 10:11) + (inout 'pad' 2:6) + )) + (cell 1 0 (iob 0.2:6 0.6:10 0.10)) + ) + """) + + def test_wrong_i(self): + pad = Signal(4) + i = Signal() + with self.assertRaisesRegex(ValueError, + r"^`pad` length \(4\) doesn't match `i` length \(1\)"): + IOBufferInstance(pad, i=i) + + def test_wrong_o(self): + pad = Signal(4) + o = Signal() + with self.assertRaisesRegex(ValueError, + r"^`pad` length \(4\) doesn't match `o` length \(1\)"): + IOBufferInstance(pad, o=o) + + def test_wrong_oe(self): + pad = Signal(4) + o = Signal(4) + oe = Signal(4) + with self.assertRaisesRegex(ValueError, + r"^`oe` length \(4\) must be 1"): + IOBufferInstance(pad, o=o, oe=oe) + + def test_wrong_oe_without_o(self): + pad = Signal(4) + oe = Signal() + with self.assertRaisesRegex(ValueError, + r"^`oe` must not be used if `o` is not used"): + IOBufferInstance(pad, oe=oe)