Repository: amaranth-lang/amaranth (python)

Issue:
Question: reset-less signals in interfaces
This is more of a question (or perhaps a feature request) than an issue.

I was reading the [documentation of the wiring module](https://amaranth-lang.org/docs/amaranth/latest/stdlib/wiring.html#) in the context of https://github.com/amaranth-lang/rfcs/pull/61. I've seen that there isn't a straightforward way to specify if a signal is reset-less or not in a `Component` that uses a `Signature`. Also, the documentation about `is_compliant()` mentions the following:

> for port members, is a [value-like](https://amaranth-lang.org/docs/amaranth/latest/guide.html#lang-valuelike) object casting to a Signal or a Const whose width and signedness is the same as that of the member, and (in case of a Signal) **which is not reset-less** and whose initial value is that of the member;

Usually, when implementing an AXI-Stream producer, I will make TDATA reset-less. This makes sense because the consumer isn't allowed to look at the value of TDATA after the producer has asserted TVALID some time after reset is deasserted (and when asserting TVALID, a good value has been put into TDATA). Doing this helps reduce the load on the reset net, which can be important in large designs.

For example, consider the following AXI-S producer (the example can be pasted in the playground). Here `self.tdata` is declared as reset-less on purpose, reducing the load on the reset net and not affecting the functionality. I wonder if/how this example can be re-written with a `Component` that uses a `Signature` for the AXI-S port. A workaround is to have an "internal" `tdata = Signal(64, reset_less=True)` created in `elaborate()` and then doing `m.d.comb += self.tdata.eq(tdata)`, but being able to do this directly would save some boilerplate code.

```python
from amaranth import *
from amaranth.sim import Simulator, Tick
from amaranth.back import rtlil, verilog
import amaranth_playground
import random

class AXISProducer(Elaboratable):
    def __init__(self):
        self.tdata = Signal(64, reset_less=True)
        self.tvalid = Signal()
        self.tready = Signal()

    def elaborate(self, platform):
        m = Module()
        counter_a = Signal(64)
        counter_b = Signal(64)
        with m.If(~self.tvalid):
            m.d.sync += [
                counter_a.eq(counter_a + 3),
                counter_b.eq(counter_b + 5),
                self.tdata.eq(counter_a ^ counter_b),
                self.tvalid.eq(1),
            ]
        with m.If(self.tvalid & self.tready):
            m.d.sync += self.tvalid.eq(0)
        return m

m = Module()
m.submodules.producer = producer = AXISProducer()

def testbench():
    for _ in range(100):
        yield Tick("sync")
        yield producer.tready.eq(random.randrange(2))

sim = Simulator(m)
sim.add_clock(1e-6)
sim.add_process(testbench)
with amaranth_playground.show_waveforms(sim):
    sim.run()

amaranth_playground.show_verilog(verilog.convert(m, ports=[producer.tdata, producer.tvalid, producer.tready]))
# amaranth_playground.show_rtlil(rtlil.convert(m, ports=[producer.tdata, producer.tvalid, producer.tready]))
```


Pull request:
lib.wiring: allow reset-less signals in interfaces
This check was originally added out of abundance of caution, but since then it was observed that reset-less-ness is purely an implementation detail (see #1220), and furthermore it interferes with adaptation of `FIFOInterface` signals (where `[rw]_data` are reset-less) for RFC 61.

Fixes #1220.

Patch:
diff --git a/amaranth/lib/wiring.py b/amaranth/lib/wiring.py
index 2fcad85..03f8de1 100644
--- a/amaranth/lib/wiring.py
+++ b/amaranth/lib/wiring.py
@@ -893,10 +893,6 @@ class Signature(metaclass=SignatureMeta):
                                            f"the initial value {member.init!r}, but it has "
                                            f"the initial value {attr_value_cast.init!r}")
                         return False
-                    if attr_value_cast.reset_less:
-                        if reasons is not None:
-                            reasons.append(f"{_format_path(path)} is expected to not be reset-less")
-                        return False
                 return True
             if member.is_signature:
                 return member.signature.is_compliant(attr_value, reasons=reasons, path=path)
diff --git a/docs/changes.rst b/docs/changes.rst
index 0c48234..354df3c 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -107,6 +107,7 @@ Standard library changes
 * Added: :mod:`amaranth.lib.memory`. (`RFC 45`_)
 * Added: :class:`amaranth.lib.data.Const` class. (`RFC 51`_)
 * Changed: :meth:`amaranth.lib.data.Layout.const` returns a :class:`amaranth.lib.data.Const`, not a view (`RFC 51`_)
+* Changed: :meth:`amaranth.lib.wiring.Signature.is_compliant` no longer rejects reset-less signals.
 * Added: :class:`amaranth.lib.io.SingleEndedPort`, :class:`amaranth.lib.io.DifferentialPort`. (`RFC 55`_)
 * Added: :class:`amaranth.lib.io.Buffer`, :class:`amaranth.lib.io.FFBuffer`, :class:`amaranth.lib.io.DDRBuffer`. (`RFC 55`_)
 * Removed: (deprecated in 0.4) :mod:`amaranth.lib.scheduler`. (`RFC 19`_)


Test patch:
diff --git a/tests/test_lib_wiring.py b/tests/test_lib_wiring.py
index b1471c0..4602f27 100644
--- a/tests/test_lib_wiring.py
+++ b/tests/test_lib_wiring.py
@@ -453,10 +453,10 @@ class SignatureTestCase(unittest.TestCase):
             r"^'obj\.a' is expected to have the initial value 1, but it has the initial value 0$",
             sig=Signature({"a": In(1, init=1)}),
             obj=NS(a=Signal(1)))
-        self.assertNotCompliant(
-            r"^'obj\.a' is expected to not be reset-less$",
-            sig=Signature({"a": In(1)}),
-            obj=NS(a=Signal(1, reset_less=True)))
+        self.assertTrue(
+            Signature({"a": In(1)}).is_compliant(
+                NS(signature=Signature({"a": In(1)}),
+                   a=Signal(1, reset_less=True))))
         self.assertNotCompliant(
             r"^'obj\.a' does not have an attribute 'b'$",
             sig=Signature({"a": Out(Signature({"b": In(1)}))}),