soc/cores/code_8b10b: add StreamEncoder/Decoder (to be used with LiteX's streams).

With improvements to handle backpressure on non-continous streams.
This commit is contained in:
Florent Kermarrec 2020-10-21 09:29:21 +02:00
parent 918a0d95ba
commit e91ec2ed83
2 changed files with 134 additions and 19 deletions

View File

@ -2,7 +2,7 @@
# This file is part of LiteX. # This file is part of LiteX.
# #
# Copyright (c) 2016-2017 Sebastien Bourdeauducq <sb@m-labs.hk> # Copyright (c) 2016-2017 Sebastien Bourdeauducq <sb@m-labs.hk>
# Copyright (c) 2019 Florent Kermarrec <florent@enjoy-digital.fr> # Copyright (c) 2019-2020 Florent Kermarrec <florent@enjoy-digital.fr>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
""" """
@ -24,6 +24,9 @@ from operator import add
from migen import * from migen import *
from litex.soc.interconnect import stream
# Helpers ------------------------------------------------------------------------------------------
def disparity(word, nbits): def disparity(word, nbits):
n0 = 0 n0 = 0
@ -68,7 +71,7 @@ def reverse_table(inputs, nbits):
return outputs return outputs
# 5b6b # 5b6b ---------------------------------------------------------------------------------------------
table_5b6b = [ table_5b6b = [
0b011000, 0b011000,
@ -109,11 +112,11 @@ table_5b6b_flip = list(table_5b6b_unbalanced)
table_5b6b_flip[7] = True table_5b6b_flip[7] = True
table_6b5b = reverse_table_flip(table_5b6b, table_5b6b_flip, 6) 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 = [ table_3b4b = [
0b0100, 0b0100,
@ -142,7 +145,9 @@ table_4b3b_kn[0b1000] = 0b111
table_4b3b_kp[0b1110] = 0b000 table_4b3b_kp[0b1110] = 0b000
table_4b3b_kp[0b0111] = 0b111 table_4b3b_kp[0b0111] = 0b111
# Single Encoder -----------------------------------------------------------------------------------
@CEInserter()
class SingleEncoder(Module): class SingleEncoder(Module):
def __init__(self, lsb_first=False): def __init__(self, lsb_first=False):
self.d = Signal(8) self.d = Signal(8)
@ -240,9 +245,11 @@ class SingleEncoder(Module):
else: else:
self.comb += self.output.eq(output_msb_first) self.comb += self.output.eq(output_msb_first)
# Encoder ------------------------------------------------------------------------------------------
class Encoder(Module): class Encoder(Module):
def __init__(self, nwords=1, lsb_first=False): def __init__(self, nwords=1, lsb_first=False):
self.ce = Signal(reset=1)
self.d = [Signal(8) for _ in range(nwords)] self.d = [Signal(8) for _ in range(nwords)]
self.k = [Signal() for _ in range(nwords)] self.k = [Signal() for _ in range(nwords)]
self.output = [Signal(10, reset_less=True) for _ in range(nwords)] self.output = [Signal(10, reset_less=True) for _ in range(nwords)]
@ -251,9 +258,10 @@ class Encoder(Module):
# # # # # #
encoders = [SingleEncoder(lsb_first) 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.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:]): for e1, e2 in zip(encoders, encoders[1:]):
self.comb += e2.disp_in.eq(e1.disp_out) self.comb += e2.disp_in.eq(e1.disp_out)
@ -264,14 +272,16 @@ class Encoder(Module):
encoder.k.eq(k) encoder.k.eq(k)
] ]
output.reset_less = True output.reset_less = True
self.sync += [ self.sync += If(self.ce,
output.eq(encoder.output), output.eq(encoder.output),
disparity.eq(encoder.disp_out) disparity.eq(encoder.disp_out)
] )
# Decoder ------------------------------------------------------------------------------------------
class Decoder(Module): class Decoder(Module):
def __init__(self, lsb_first=False): def __init__(self, lsb_first=False):
self.ce = Signal(reset=1)
self.input = Signal(10) self.input = Signal(10)
self.d = Signal(8) self.d = Signal(8)
self.k = Signal() self.k = Signal()
@ -292,11 +302,12 @@ class Decoder(Module):
code3b = Signal(3, reset_less=True) code3b = Signal(3, reset_less=True)
mem_6b5b = Memory(5, len(table_6b5b), init=table_6b5b) 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.specials += mem_6b5b, port_6b5b
self.comb += port_6b5b.adr.eq(code6b) 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), self.k.eq(0),
If(code6b == 0b001111, If(code6b == 0b001111,
self.k.eq(1), self.k.eq(1),
@ -315,12 +326,61 @@ class Decoder(Module):
), ),
code3b.eq(Array(table_4b3b)[code4b]) code3b.eq(Array(table_4b3b)[code4b])
), ),
] )
self.comb += code5b.eq(port_6b5b.dat_r) self.comb += code5b.eq(port_6b5b.dat_r)
self.comb += self.d.eq(Cat(code5b, code3b)) 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 # 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. # not report all invalid symbols but still allow detecting issues with the link.
ones = Signal(4, reset_less=True) 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)) 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)
]

View File

@ -337,3 +337,58 @@ class TestCode8B10B(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
self.assertEqual(self.input_sequence, self.assertEqual(self.input_sequence,
decode_sequence(self.output_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)