From e91ec2ed833b1efe42d0c92495afc84723b46f02 Mon Sep 17 00:00:00 2001 From: Florent Kermarrec Date: Wed, 21 Oct 2020 09:29:21 +0200 Subject: [PATCH] soc/cores/code_8b10b: add StreamEncoder/Decoder (to be used with LiteX's streams). With improvements to handle backpressure on non-continous streams. --- litex/soc/cores/code_8b10b.py | 98 ++++++++++++++++++++++++++++------- test/test_code_8b10b.py | 55 ++++++++++++++++++++ 2 files changed, 134 insertions(+), 19 deletions(-) diff --git a/litex/soc/cores/code_8b10b.py b/litex/soc/cores/code_8b10b.py index f6bb0cf4f..74e4e3f83 100644 --- a/litex/soc/cores/code_8b10b.py +++ b/litex/soc/cores/code_8b10b.py @@ -2,7 +2,7 @@ # This file is part of LiteX. # # Copyright (c) 2016-2017 Sebastien Bourdeauducq -# Copyright (c) 2019 Florent Kermarrec +# Copyright (c) 2019-2020 Florent Kermarrec # SPDX-License-Identifier: BSD-2-Clause """ @@ -24,6 +24,9 @@ from operator import add from migen import * +from litex.soc.interconnect import stream + +# Helpers ------------------------------------------------------------------------------------------ def disparity(word, nbits): n0 = 0 @@ -68,7 +71,7 @@ def reverse_table(inputs, nbits): return outputs -# 5b6b +# 5b6b --------------------------------------------------------------------------------------------- table_5b6b = [ 0b011000, @@ -109,11 +112,11 @@ table_5b6b_flip = list(table_5b6b_unbalanced) table_5b6b_flip[7] = True table_6b5b = reverse_table_flip(table_5b6b, table_5b6b_flip, 6) -# K.28 -table_6b5b[0b001111] = 0b11100 -table_6b5b[0b110000] = 0b11100 -# 3b4b +table_6b5b[0b001111] = 0b11100 # K.28 +table_6b5b[0b110000] = 0b11100 # K.28 + +# 3b4b --------------------------------------------------------------------------------------------- table_3b4b = [ 0b0100, @@ -142,7 +145,9 @@ table_4b3b_kn[0b1000] = 0b111 table_4b3b_kp[0b1110] = 0b000 table_4b3b_kp[0b0111] = 0b111 +# Single Encoder ----------------------------------------------------------------------------------- +@CEInserter() class SingleEncoder(Module): def __init__(self, lsb_first=False): self.d = Signal(8) @@ -240,20 +245,23 @@ class SingleEncoder(Module): else: self.comb += self.output.eq(output_msb_first) +# Encoder ------------------------------------------------------------------------------------------ class Encoder(Module): def __init__(self, nwords=1, lsb_first=False): - self.d = [Signal(8) for _ in range(nwords)] - self.k = [Signal() for _ in range(nwords)] - self.output = [Signal(10, reset_less=True) for _ in range(nwords)] + self.ce = Signal(reset=1) + self.d = [Signal(8) for _ in range(nwords)] + self.k = [Signal() for _ in range(nwords)] + self.output = [Signal(10, reset_less=True) for _ in range(nwords)] self.disparity = [Signal() for _ in range(nwords)] # # # encoders = [SingleEncoder(lsb_first) for _ in range(nwords)] + self.comb += [encoder.ce.eq(self.ce) for encoder in encoders] self.submodules += encoders - self.sync += encoders[0].disp_in.eq(encoders[-1].disp_out) + self.sync += If(self.ce, encoders[0].disp_in.eq(encoders[-1].disp_out)) for e1, e2 in zip(encoders, encoders[1:]): self.comb += e2.disp_in.eq(e1.disp_out) @@ -264,17 +272,19 @@ class Encoder(Module): encoder.k.eq(k) ] output.reset_less = True - self.sync += [ + self.sync += If(self.ce, output.eq(encoder.output), disparity.eq(encoder.disp_out) - ] + ) +# Decoder ------------------------------------------------------------------------------------------ class Decoder(Module): def __init__(self, lsb_first=False): - self.input = Signal(10) - self.d = Signal(8) - self.k = Signal() + self.ce = Signal(reset=1) + self.input = Signal(10) + self.d = Signal(8) + self.k = Signal() self.invalid = Signal() # # # @@ -292,11 +302,12 @@ class Decoder(Module): code3b = Signal(3, reset_less=True) mem_6b5b = Memory(5, len(table_6b5b), init=table_6b5b) - port_6b5b = mem_6b5b.get_port() + port_6b5b = mem_6b5b.get_port(has_re=True) self.specials += mem_6b5b, port_6b5b self.comb += port_6b5b.adr.eq(code6b) + self.comb += port_6b5b.re.eq(self.ce) - self.sync += [ + self.sync += If(self.ce, self.k.eq(0), If(code6b == 0b001111, self.k.eq(1), @@ -315,12 +326,61 @@ class Decoder(Module): ), code3b.eq(Array(table_4b3b)[code4b]) ), - ] + ) self.comb += code5b.eq(port_6b5b.dat_r) self.comb += self.d.eq(Cat(code5b, code3b)) # Basic invalid symbols detection: check that we have 4,5 or 6 ones in the symbol. This does # not report all invalid symbols but still allow detecting issues with the link. ones = Signal(4, reset_less=True) - self.sync += ones.eq(reduce(add, [self.input[i] for i in range(10)])) + self.sync += If(self.ce, ones.eq(reduce(add, [self.input[i] for i in range(10)]))) self.comb += self.invalid.eq((ones != 4) & (ones != 5) & (ones != 6)) + + +# Stream Encoder ----------------------------------------------------------------------------------- + +class StreamEncoder(stream.PipelinedActor): + def __init__(self, nwords=1): + self.sink = sink = stream.Endpoint([("d", nwords*8), ("k", nwords)]) + self.source = source = stream.Endpoint([("data", nwords*10)]) + stream.PipelinedActor.__init__(self, latency=2) + + # # # + + encoder = Encoder(nwords, True) + self.submodules += encoder + + # Control + self.comb += encoder.ce.eq(self.pipe_ce) + + # Datapath + for i in range(nwords): + self.comb += [ + encoder.k[i].eq(sink.k[i]), + encoder.d[i].eq(sink.d[8*i:8*(i+1)]), + source.data[10*i:10*(i+1)].eq(encoder.output[i]) + ] + +# Stream Encoder ----------------------------------------------------------------------------------- + +class StreamDecoder(stream.PipelinedActor): + def __init__(self, nwords=1): + self.sink = sink = stream.Endpoint([("data", nwords*10)]) + self.source = source = stream.Endpoint([("d", nwords*8), ("k", nwords)]) + stream.PipelinedActor.__init__(self, latency=1) + + # # # + + decoders = [Decoder(True) for _ in range(nwords)] + self.submodules += decoders + + # Control + self.comb += [decoders[i].ce.eq(self.pipe_ce) for i in range(nwords)] + + # Datapath + for i in range(nwords): + self.comb += [ + decoders[i].input.eq(sink.data[10*i:10*(i+1)]), + source.k[i].eq(decoders[i].k), + source.d[8*i:8*(i+1)].eq(decoders[i].d) + ] \ No newline at end of file diff --git a/test/test_code_8b10b.py b/test/test_code_8b10b.py index a4c2fe121..3c217ea6b 100644 --- a/test/test_code_8b10b.py +++ b/test/test_code_8b10b.py @@ -337,3 +337,58 @@ class TestCode8B10B(unittest.TestCase): def test_roundtrip(self): self.assertEqual(self.input_sequence, decode_sequence(self.output_sequence)) + + def test_stream(self): + def data_generator(dut, endpoint, datas, commas, rand=True): + prng = random.Random(42) + for i, (data, comma) in enumerate(zip(datas, commas)): + if rand: + while prng.randrange(4): + yield + yield endpoint.valid.eq(1) + yield endpoint.d.eq(data) + yield endpoint.k.eq(comma) + yield + while (yield endpoint.ready) == 0: + yield + yield endpoint.valid.eq(0) + + def data_checker(dut, endpoint, datas, commas, rand=True): + prng = random.Random(42) + dut.errors = 0 + for i, (data_ref, comma_ref) in enumerate(zip(datas, commas)): + yield endpoint.ready.eq(1) + yield + while (yield endpoint.valid) == 0: + yield + data = (yield endpoint.d) + comma = (yield endpoint.k) + #print("data: 0x{:08x} vs 0x{:08x}".format(data, data_ref)) + #print("comma: 0b{:04b} vs 0b{:04b}".format(comma, comma_ref)) + if data != data_ref: + dut.errors += 1 + if comma != comma_ref: + dut.errors += 1 + yield endpoint.ready.eq(0) + if rand: + while prng.randrange(4): + yield + + + class DUT(Module): + def __init__(self): + self.submodules.encoder = code_8b10b.StreamEncoder(nwords=4) + self.submodules.decoder = code_8b10b.StreamDecoder(nwords=4) + self.comb += self.encoder.source.connect(self.decoder.sink) + + + prng = random.Random(42) + dut = DUT() + datas = [prng.randrange(2**32) for i in range(128)] + commas = [0 for i in range(128)] + generators = [ + data_generator(dut, dut.encoder.sink, datas, commas), + data_checker(dut, dut.decoder.source, datas, commas) + ] + run_simulation(dut, generators) + self.assertEqual(dut.errors, 0)