lib.coding: add GrayEncoder and GrayDecoder.
authorwhitequark <cz@m-labs.hk>
Sun, 20 Jan 2019 02:20:34 +0000 (02:20 +0000)
committerwhitequark <cz@m-labs.hk>
Sun, 20 Jan 2019 02:20:34 +0000 (02:20 +0000)
Unlike the Migen ones, these are purely combinatorial.

nmigen/lib/coding.py
nmigen/test/test_lib_coding.py

index 081858ba303c33f87be507dce66875c5dea2609b..653882f65eed74d797921d1ba7895fcf820aa560 100644 (file)
@@ -3,6 +3,13 @@
 from .. import *
 
 
+__all__ = [
+    "Encoder", "Decoder",
+    "PriorityEncoder", "PriorityDecoder",
+    "GrayEncoder", "GrayDecoder",
+]
+
+
 class Encoder:
     """Encode one-hot to binary.
 
@@ -121,3 +128,59 @@ class PriorityDecoder(Decoder):
 
     Identical to :class:`Decoder`.
     """
+
+
+class GrayEncoder:
+    """Encode binary to Gray code.
+
+    Parameters
+    ----------
+    width : int
+        Bit width.
+
+    Attributes
+    ----------
+    i : Signal(width), in
+        Input natural binary.
+    o : Signal(width), out
+        Encoded Gray code.
+    """
+    def __init__(self, width):
+        self.width = width
+
+        self.i = Signal(width)
+        self.o = Signal(width)
+
+    def get_fragment(self, platform):
+        m = Module()
+        m.d.comb += self.o.eq(self.i ^ self.i[1:])
+        return m.lower(platform)
+
+
+class GrayDecoder:
+    """Decode Gray code to binary.
+
+    Parameters
+    ----------
+    width : int
+        Bit width.
+
+    Attributes
+    ----------
+    i : Signal(width), in
+        Input Gray code.
+    o : Signal(width), out
+        Decoded natural binary.
+    """
+    def __init__(self, width):
+        self.width = width
+
+        self.i = Signal(width)
+        self.o = Signal(width)
+
+    def get_fragment(self, platform):
+        m = Module()
+        m.d.comb += self.o[-1].eq(self.i[-1])
+        for i in reversed(range(self.width - 1)):
+            m.d.comb += self.o[i].eq(self.o[i + 1] ^ self.i[i])
+        return m.lower(platform)
index c535c79206cff4d40a2cb1d9c22550dd66c8f329..53f2b8b97e2bcbace3f479febfc80765208a37de 100644 (file)
@@ -1,5 +1,6 @@
 from .tools import *
 from ..hdl.ast import *
+from ..hdl.dsl import *
 from ..back.pysim import *
 from ..lib.coding import *
 
@@ -79,3 +80,47 @@ class DecoderTestCase(FHDLTestCase):
 
             sim.add_process(process)
             sim.run()
+
+
+class ReversibleSpec:
+    def __init__(self, encoder_cls, decoder_cls, args):
+        self.encoder_cls = encoder_cls
+        self.decoder_cls = decoder_cls
+        self.coder_args  = args
+
+    def get_fragment(self, platform):
+        m = Module()
+        enc, dec = self.encoder_cls(*self.coder_args), self.decoder_cls(*self.coder_args)
+        m.submodules += enc, dec
+        m.d.comb += [
+            dec.i.eq(enc.o),
+            Assert(enc.i == dec.o)
+        ]
+        return m.lower(platform)
+
+
+class HammingDistanceSpec:
+    def __init__(self, distance, encoder_cls, args):
+        self.distance    = distance
+        self.encoder_cls = encoder_cls
+        self.coder_args  = args
+
+    def get_fragment(self, platform):
+        m = Module()
+        enc1, enc2 = self.encoder_cls(*self.coder_args), self.encoder_cls(*self.coder_args)
+        m.submodules += enc1, enc2
+        m.d.comb += [
+            Assume(enc1.i + 1 == enc2.i),
+            Assert(sum(enc1.o ^ enc2.o) == self.distance)
+        ]
+        return m.lower(platform)
+
+
+class GrayCoderTestCase(FHDLTestCase):
+    def test_reversible(self):
+        spec = ReversibleSpec(encoder_cls=GrayEncoder, decoder_cls=GrayDecoder, args=(16,))
+        self.assertFormal(spec, mode="prove")
+
+    def test_distance(self):
+        spec = HammingDistanceSpec(distance=1, encoder_cls=GrayEncoder, args=(16,))
+        self.assertFormal(spec, mode="prove")