DC balance
AC coupling makes the baseline wander with the data; one inversion flag per frame balances all four outputs at once.
What this stage does
The receivers are AC-coupled: a slow loop in each TIA cancels its DC offset. That is useful, because it removes output 1’s carrier pedestal and shrinks the TIA’s required linear range from 7.555 steps to 4 steps. But the loop cannot tell the pedestal from the data’s own running mean. When the data is biased, or holds one word for a long time, the decision baseline drifts with it, and a drift of half a step closes the eye.
Frame-inversion coding fixes this with one bit per frame. Sending a frame as 1 − x instead of x flips the sign of every output’s deviation at once, because the DFT of the all-ones word is . The encoder keeps a running sum of the four deviations as a vector and inverts each frame whose sum points the same way; the FPGA undoes it after decoding with .
How it’s modelled
The simulator models the offset-cancel loop as a single first-order high-pass at the TIA input with time constant , which at the default corner is 22,282 symbols. The encoder balances the running digital sum (not the filter state); the scalar variant balances output 1 alone for comparison. Workloads of 1,048,576 symbols are random bits, biased bits with P(1) = 0.8 illustrative, and runs of one word held for 32,768 symbols illustrative. Flag errors are not modelled. The animation above runs the same model in your browser on its own random stream.
Before any of this the simulator also asks how often a word clips at all (outputs 1 and 3 have five levels, PAM4 four): random bits clip 0.1249 of words; 16-bit two’s-complement small integers clip 0.1037, but sent operand by operand they bunch into runs of up to 30 words.
Data table
| workload | none | plane inversion | word inversion |
|---|---|---|---|
| random bits | 0.1249 | 0.1235 | 0 |
| residues mod 65537 | 0.1168 | 0.1175 | 0 |
| signed ints σ=3.2, 16-bit | 0.1037 | 0.1037 | 0 |
| signed ints σ=3.2, 32-bit | 0.09743 | 0.09769 | 0 |
Data table
| workload | plane-major | operand-major |
|---|---|---|
| random bits | 5 | 5 |
| residues mod 65537 | 6 | 5 |
| signed ints σ=3.2, 16-bit | 5 | 30 |
| signed ints σ=3.2, 32-bit | 5 | 62 |
What we found
Uncoded, random data is harmless (0.0184 steps of wander), but biased data drifts by 1.213 steps and run-heavy data by 1.937 steps, far past the ½-step margin. Vector frame inversion holds biased data to 0.008412 steps with 64-symbol frames and run-heavy data to 0.02622 steps with 256-symbol frames and 0.1029 steps with 1024-symbol frames. Balancing output 1 alone is not enough: on runs the other outputs still wander by 1.541 steps.
Data table
| code frame (symbols) | random, none | random, vector | biased 0.8 ones, none | biased 0.8 ones, vector | runs of 32768 symbols, none | runs of 32768 symbols, vector | runs of 32768 symbols, scalar |
|---|---|---|---|---|---|---|---|
| 64 | 0.0184 | 0.001893 | 1.213 | 0.008412 | 1.937 | 0.006586 | 1.541 |
| 128 | 0.0184 | 0.002834 | 1.213 | 0.0105 | 1.937 | 0.01315 | 1.541 |
| 256 | 0.0184 | 0.003471 | 1.213 | 0.01806 | 1.937 | 0.02622 | 1.541 |
| 512 | 0.0184 | 0.004771 | 1.213 | 0.03626 | 1.937 | 0.0521 | 1.542 |
| 1,024 | 0.0184 | 0.006224 | 1.213 | 0.07426 | 1.937 | 0.1029 | 1.544 |
| 2,048 | 0.0184 | 0.008122 | 1.213 | 0.1493 | 1.937 | 0.2005 | 1.547 |
| 4,096 | 0.0184 | 0.01219 | 1.213 | 0.289 | 1.937 | 0.3811 | 1.553 |
Data table
| high-pass corner (Hz) | random, uncoded | random, vector | biased 0.8 ones, uncoded | biased 0.8 ones, vector | runs of 32768 symbols, uncoded | runs of 32768 symbols, vector |
|---|---|---|---|---|---|---|
| 10,000 | 0.003555 | 0.0003477 | 1.19 | 0.001776 | 0.7653 | 0.002423 |
| 30,000 | 0.008303 | 0.001049 | 1.206 | 0.005098 | 1.351 | 0.007313 |
| 100,000 | 0.0184 | 0.003471 | 1.213 | 0.01806 | 1.937 | 0.02622 |
| 300,000 | 0.03145 | 0.009951 | 1.224 | 0.05715 | 2 | 0.08401 |
| 1 × 10⁶ | 0.05995 | 0.03088 | 1.247 | 0.1924 | 2 | 0.2682 |
| 3 × 10⁶ | 0.1147 | 0.08069 | 1.281 | 0.5157 | 2 | 0.704 |
| 1 × 10⁷ | 0.2127 | 0.196 | 1.346 | 1.088 | 2 | 1.53 |
The fix is not free. Each frame needs a flag (0.3906 % of the symbols at 256-symbol frames) and the encoder must see a whole frame before deciding, adding 18.29 ns of latency (73.14 ns with 1024-symbol frames). The largest swept frame that keeps the worst case under a tenth of a step is 512 symbols. A dark calibration pause offsets output 1 by 2 steps and needs 5.871 µs to settle unless the idle fill is itself balanced; and calibrating with one pattern held still errs by 0.3358 steps, against 0.005736 steps when each pattern alternates with its inverse.
What would need measured data
The high-pass corner and the single-pole loop are illustrative; a real TIA’s offset-cancel loop may have more poles or a different corner, and its response to a dark pause is what would set the calibration procedure. One would also need the flag channel’s error rate (one flag error corrupts a whole frame) and the clipping statistics of the operands a real workload sends.