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:
parent
918a0d95ba
commit
e91ec2ed83
|
@ -2,7 +2,7 @@
|
|||
# This file is part of LiteX.
|
||||
#
|
||||
# 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
|
||||
|
||||
"""
|
||||
|
@ -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)
|
||||
]
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue