Add (de)scrambling support
This adds support for (de)scrambling as described in X3.263. The
scrambler is fairly straightforward. Because we only have to recognize
idles, and because the timing constraints are more relaxed (than e.g.
the PCS), we can make several simplifications not found in other
designs (e.g. X3.263 Annex G or DP83222).
First, we can reuse the same register for the lfsr as for the input
ciphertext. This is because we only need to record the scrambled data
when we are unlocked, and we can easily recover the unscrambled data
just by an inversion (as opposed to needing to align with /H/ etc).
Second, it is not critical what the exact thresholds are for locking an
unlocking, as long as certain minimums are met. This allows us to ignore
edge cases, such as if we have data=10 and valid=2. Without these
relaxed constraints, we would need to special-case this input to ensure
we didn't miss the last necessary consecutive idle. But instead we just
set the threshold such that one missed bit does not matter.
To support easier testing, a test input may be used to cause the
descramble to become unlocked after only 5us, instead of the mandated
361. This makes simulation go much faster.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-08-24 11:35:24 -05:00
|
|
|
# SPDX-License-Identifier: AGPL-3.0-Only
|
|
|
|
# Copyright (C) 2022 Sean Anderson <seanga2@gmail.com>
|
|
|
|
|
|
|
|
import itertools
|
|
|
|
import random
|
|
|
|
|
|
|
|
import cocotb
|
|
|
|
from cocotb.clock import Clock
|
|
|
|
from cocotb.regression import TestFactory
|
|
|
|
from cocotb.result import SimTimeoutError
|
|
|
|
from cocotb.triggers import ClockCycles, FallingEdge, RisingEdge, Timer, with_timeout
|
|
|
|
|
|
|
|
from .util import compare_lists, send_recovered_bits, timeout, with_valids
|
|
|
|
|
|
|
|
def scramble(bits):
|
|
|
|
lfsr = random.randrange(1, 0x7ff)
|
|
|
|
for bit in bits:
|
|
|
|
ldd = (lfsr >> 10) ^ ((lfsr >> 8) & 1)
|
|
|
|
yield bit ^ ldd
|
|
|
|
lfsr <<= 1
|
|
|
|
lfsr &= 0x7ff
|
|
|
|
lfsr |= ldd
|
|
|
|
|
|
|
|
async def send_scrambled(descrambler, data, valids):
|
|
|
|
descrambler.signal_status.value = 1
|
|
|
|
await send_recovered_bits(descrambler.clk, descrambler.scrambled,
|
|
|
|
descrambler.scrambled_valid, scramble(data), valids)
|
|
|
|
descrambler.signal_status.value = 0
|
|
|
|
|
|
|
|
@timeout(10, 'us')
|
|
|
|
async def test_unlock(descrambler, valids):
|
|
|
|
descrambler.signal_status.value = 0
|
|
|
|
descrambler.scrambled_valid.value = 0
|
|
|
|
descrambler.test_mode.value = 1
|
|
|
|
await Timer(1)
|
|
|
|
await cocotb.start(Clock(descrambler.clk, 8, units='ns').start())
|
|
|
|
|
|
|
|
await cocotb.start(send_scrambled(descrambler,
|
|
|
|
itertools.chain(itertools.repeat(1, 60),
|
|
|
|
itertools.repeat(0, 625//2),
|
|
|
|
itertools.repeat(1, 29),
|
|
|
|
itertools.repeat(0)),
|
|
|
|
valids))
|
|
|
|
|
|
|
|
await ClockCycles(descrambler.clk, 60)
|
|
|
|
assert descrambler.locked.value
|
|
|
|
try:
|
|
|
|
await with_timeout(FallingEdge(descrambler.locked), 6, 'us')
|
|
|
|
except SimTimeoutError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
assert False
|
|
|
|
await FallingEdge(descrambler.locked)
|
|
|
|
|
|
|
|
with_valids(globals(), test_unlock)
|
|
|
|
|
|
|
|
@timeout(10, 'us')
|
|
|
|
async def test_descramble(descrambler, valids):
|
|
|
|
descrambler.signal_status.value = 0
|
|
|
|
descrambler.scrambled_valid.value = 0
|
|
|
|
descrambler.test_mode.value = 0
|
|
|
|
await Timer(1)
|
|
|
|
await cocotb.start(Clock(descrambler.clk, 8, units='ns').start())
|
|
|
|
|
|
|
|
ins = [1] * 60 + [0] + [random.randrange(2) for _ in range(1000)]
|
|
|
|
await cocotb.start(send_scrambled(descrambler, ins, valids))
|
|
|
|
|
|
|
|
outs = []
|
|
|
|
await RisingEdge(descrambler.locked)
|
|
|
|
while descrambler.locked.value:
|
|
|
|
await RisingEdge(descrambler.clk)
|
|
|
|
valid = descrambler.unscrambled_valid.value
|
|
|
|
if valid == 0:
|
|
|
|
pass
|
|
|
|
elif valid == 1:
|
|
|
|
outs.append(descrambler.unscrambled[1].value)
|
|
|
|
else:
|
|
|
|
outs.append(descrambler.unscrambled[1].value)
|
|
|
|
outs.append(descrambler.unscrambled[0].value)
|
|
|
|
|
|
|
|
best_corr = -1
|
|
|
|
best_off = None
|
2022-08-28 11:41:43 -05:00
|
|
|
for off in range(28, 42):
|
Add (de)scrambling support
This adds support for (de)scrambling as described in X3.263. The
scrambler is fairly straightforward. Because we only have to recognize
idles, and because the timing constraints are more relaxed (than e.g.
the PCS), we can make several simplifications not found in other
designs (e.g. X3.263 Annex G or DP83222).
First, we can reuse the same register for the lfsr as for the input
ciphertext. This is because we only need to record the scrambled data
when we are unlocked, and we can easily recover the unscrambled data
just by an inversion (as opposed to needing to align with /H/ etc).
Second, it is not critical what the exact thresholds are for locking an
unlocking, as long as certain minimums are met. This allows us to ignore
edge cases, such as if we have data=10 and valid=2. Without these
relaxed constraints, we would need to special-case this input to ensure
we didn't miss the last necessary consecutive idle. But instead we just
set the threshold such that one missed bit does not matter.
To support easier testing, a test input may be used to cause the
descramble to become unlocked after only 5us, instead of the mandated
361. This makes simulation go much faster.
Signed-off-by: Sean Anderson <seanga2@gmail.com>
2022-08-24 11:35:24 -05:00
|
|
|
corr = sum(i == o for i, o in zip(ins[off:], outs))
|
|
|
|
if corr > best_corr:
|
|
|
|
best_corr = corr
|
|
|
|
best_off = off
|
|
|
|
|
|
|
|
print(f"best offset is {best_off} correlation {best_corr/(len(ins) - best_off)}")
|
|
|
|
compare_lists(ins[best_off:], outs)
|
|
|
|
|
|
|
|
with_valids(globals(), test_descramble)
|