File: ../../Sources/byte_stuffer.vhd
0: ----------------------------------------------------------------------------------
1: -- Engineer: Vitor Mendes Camilo
2: --
3: -- Module Name: byte_stuffer - Behavioral
4: -- Description:
5: --
6: -- Per T.87: every 0xFF byte in the encoded bitstream must be followed by a
7: -- stuffed '0' bit so decoders can distinguish payload from markers (an FF
8: -- followed by a non-zero byte = marker).
9: --
10: -- Three internal stages:
11: --
12: -- Stage 1 — bit packer:
13: -- Accumulates input bits MSB-first into a 2*FIFO_BITS accumulator.
14: -- Drains a fixed FIFO_BITS-wide word into the FIFO whenever enough
15: -- bits are present. On flush the sub-byte residue is padded to a
16: -- byte boundary; the final FIFO write is always FIFO_BITS wide
17: -- (zero-padded if needed) and carries the final word's real valid-bit
18: -- count (the byte-boundary pad excluded) plus last_flag=1. Stage 3
19: -- counts only those real bits, so the pad is never emitted; the
20: -- genuine residue is padded post-stuffing at the terminal beat.
21: --
22: -- Stage 2 — BRAM-backed sync FIFO
23: --
24: -- Stage 3 — FF stuffer + output emit:
25: -- Refills a holding register from FIFO pops. Each cycle forms up to
26: -- OUT_BYTES_PER_CYCLE output bytes via:
27: -- (a) a parallel pre-compute of FF-equality flags over the 8 fixed
28: -- candidate byte windows that any of the 4 slots could ever
29: -- read from (offsets 0, 7, 8, 15, 16, 22, 23, 24);
30: -- (b) a 4-step chain over the slots that resolves each slot's input
31: -- prev_FF and selects the correct candidate flag/bits via a
32: -- small mux.
33: --
34: -- The end-of-image terminal beat (sub-byte residue, a pending stuff
35: -- bit with no follow-up data, or a byte-aligned clean end) is split
36: -- into its own cycle via sLastPending: the final byte (or 0-byte beat)
37: -- is assembled, latched, and emitted on the following beat. Adds at
38: -- most 1 cycle of latency per image boundary and keeps the pad-byte
39: -- assembly off the critical path.
40: --
41: -- Flush protocol (iFlush, single-cycle pulse from upstream on the cycle
42: -- the bit_packer presents the image's last word):
43: -- - Stage 1 pads its sub-byte residue and tags the final FIFO write
44: -- with last_flag=1.
45: -- - Stage 3 latches the last_flag when it pops that word. Once the
46: -- holding register drains, it inserts a final stuff '0' if the last
47: -- payload byte was 0xFF, zero-pads the output accumulator to a byte
48: -- boundary, emits the remaining whole bytes, and pulses oFlushDone
49: -- on the final output beat (oFlushDone is sampled together with
50: -- oWordValid='1', matching jls_framer's iEOI contract).
51: --
52: -- Generics:
53: -- IN_WIDTH : bit_packer worst-case word width (= LIMIT).
54: -- OUT_BYTES_PER_CYCLE : output bytes/cycle. Bounds the Stage 3 FF chain
55: -- depth (4 bytes/cycle -> 4 levels).
56: -- BURST_DEPTH : depth of the BRAM-backed FIFO (in wide words).
57: --
58: ----------------------------------------------------------------------------------
59:
60: library ieee;
61: use ieee.std_logic_1164.all;
62: use ieee.numeric_std.all;
63: use work.openjls_pkg.all;
64:
65: library work;
66: use work.olo_base_pkg_math.log2ceil;
67:
68: entity byte_stuffer is
69: generic (
70: IN_WIDTH : natural := CO_LIMIT_STD;
71: OUT_BYTES_PER_CYCLE : natural := CO_BYTE_STUFFER_OUT_BYTES_PER_CYCLE; -- Stuffs up to 4 bytes/cycle, not to be changed
72: OUT_WIDTH : natural := CO_BYTE_STUFFER_OUT_WIDTH;
73: BURST_DEPTH : natural := CO_BYTE_STUFFER_BURST_DEPTH
74: );
75: port (
76: iClk : in std_logic;
77: iRst : in std_logic;
78: iStall : in std_logic; -- Acts as oReady, always ready to receive data unless stalled
79: iWord : in std_logic_vector(IN_WIDTH - 1 downto 0);
80: iWordValid : in std_logic;
81: iWordValidLen : in unsigned(log2ceil(IN_WIDTH + 1) - 1 downto 0);
82: iFlush : in std_logic;
83: oWord : out std_logic_vector(OUT_WIDTH - 1 downto 0);
84: oWordValid : out std_logic;
85: oValidBytes : out unsigned(log2ceil(OUT_BYTES_PER_CYCLE + 1) - 1 downto 0);
86: iReady : in std_logic;
87: oAlmostFull : out std_logic;
88: oFlushDone : out std_logic
89: );
90: end entity byte_stuffer;
91:
92: architecture behavioral of byte_stuffer is
93:
94: -- Constants ----------------------------------------------------------------
95:
96: -- Stage 1 sizing
97: constant FIFO_BYTES : natural := math_ceil_div(IN_WIDTH, 8);
98: constant FIFO_BITS : natural := FIFO_BYTES * 8;
99: constant ACCUM_BITS : natural := 2 * FIFO_BITS;
100: -- Width of the final word's valid-bit count carried alongside the last FIFO
101: -- word (0..FIFO_BITS). Stage 1's byte-boundary pad (added so the FIFO word is
102: -- whole bytes) is excluded from this count, so stage 3 never emits the pad
103: -- bits; the genuine sub-byte residue is padded post-stuffing at the terminal.
104: constant LAST_BITS_WIDTH : natural := log2ceil(FIFO_BITS + 1);
105:
106: -- FIFO entry layout (LSB-first):
107: -- bit [0] : last_flag
108: -- bits [1 .. FIFO_BITS] : data
109: constant LAST_POS : natural := 0;
110: constant DATA_LSB : natural := 1;
111: constant FIFO_WIDTH : natural := FIFO_BITS + 1;
112:
113: -- Sideband byte-valid queue depth: bounds the number of in-flight
114: -- last-flag words allowed in the main FIFO simultaneously.
115: constant BYTE_VALID_QUEUE_DEPTH : natural := 3;
116:
117: -- AlmFull asserts STALL_CUSHION_ENTRIES below Full so the FIFO can absorb
118: -- in-flight tokens while the top-level stall signal propagates through its
119: -- pipeline (registered AlmFulls + registered sStallLogic = ~4 cycles).
120: constant STALL_CUSHION_ENTRIES : natural := 5;
121: constant ALM_FULL_LEVEL : natural := BURST_DEPTH - STALL_CUSHION_ENTRIES;
122:
123: -- Stage 3 holding register: one FIFO pop + 1 byte (deadlock floor). Bits
124: -- stored MSB-first (oldest emitted first).
125: constant HOLD_BYTES : natural := FIFO_BYTES + 1;
126: constant HOLD_BITS : natural := HOLD_BYTES * 8;
127:
128: -- Signals ---------------------------------------------------------------------
129: -- Input register
130: signal sWord : std_logic_vector(IN_WIDTH - 1 downto 0);
131: signal sWordValidLen : unsigned(log2ceil(IN_WIDTH + 1) - 1 downto 0);
132: signal sWordValid : std_logic;
133: signal sFlush : std_logic;
134:
135: -- Stage 1 accumulator
136: signal sAccumBuffer : std_logic_vector(ACCUM_BITS - 1 downto 0);
137: signal sAccumCountBits : unsigned(log2ceil(ACCUM_BITS + 1) - 1 downto 0);
138: signal sAccumCountBitsFlush : unsigned(log2ceil(ACCUM_BITS + 1) - 1 downto 0);
139: signal sFlushValidBits : unsigned(LAST_BITS_WIDTH - 1 downto 0);
140: signal sFlushPending : std_logic;
141:
142: -- FIFO interface
143: signal sFifoInData : std_logic_vector(FIFO_WIDTH - 1 downto 0);
144: signal sFifoInValid : std_logic;
145: signal sFifoInReady : std_logic;
146: signal sFifoOutData : std_logic_vector(FIFO_WIDTH - 1 downto 0);
147: signal sFifoOutValid : std_logic;
148: signal sFifoOutReady : std_logic;
149: signal sFifoAlmFull : std_logic;
150: signal sFifoFull : std_logic;
151:
152: -- Skid buffer between FIFO output and Stage 3 consume
153: -- Helps timing on FPGAs with poor interconnects
154: signal sSkidWord : std_logic_vector(FIFO_WIDTH - 1 downto 0);
155: signal sSkidData : std_logic_vector(FIFO_BITS - 1 downto 0);
156: signal sSkidValid : std_logic;
157: signal sSkidTaken : std_logic;
158: signal sSkidLast : std_logic;
159:
160: -- Final-word valid-bit-count queue (FIFO) signals
161: signal sBvQueueInValid : std_logic;
162: signal sBvQueueInData : std_logic_vector(LAST_BITS_WIDTH - 1 downto 0);
163: signal sBvQueueOutReady : std_logic;
164: signal sBvQueueOutData : std_logic_vector(LAST_BITS_WIDTH - 1 downto 0);
165:
166: -- Stage 3 (FF stuffer + emit) state.
167: signal sStuffBuffer : std_logic_vector(HOLD_BITS - 1 downto 0);
168: signal sStuffBufferBits : unsigned(log2ceil(HOLD_BITS + 1) - 1 downto 0);
169: signal sStuffBufferLast : std_logic;
170: signal sPrevFF : std_logic;
171: signal sOutWordReg : std_logic_vector(OUT_WIDTH - 1 downto 0);
172: signal sOutValidReg : std_logic;
173: signal sOutBytesValidReg : unsigned(log2ceil(OUT_BYTES_PER_CYCLE + 1) - 1 downto 0);
174: signal sFlushDone : std_logic;
175:
176: -- End-of-image terminal beat
177: signal sLastPending : std_logic;
178:
179: begin
180:
181: -- ASSERTIONS --------------------------------------------------------------------
182: assert OUT_BYTES_PER_CYCLE = 4
183: report "byte_stuffer: OUT_BYTES_PER_CYCLE must be 4 (stuffing arrays are hardcoded to 4 lanes)"
184: severity failure;
185:
186: assert BURST_DEPTH > STALL_CUSHION_ENTRIES
187: report "byte_stuffer: BURST_DEPTH must exceed STALL_CUSHION_ENTRIES"
188: severity failure;
189:
190: assert not (sFifoInValid = '1' and sFifoInReady = '0')
191: report "byte_stuffer: FIFO write dropped - AlmFull cushion undersized vs stall latency"
192: severity failure;
193:
194: -- Contract assertions in PSL (temporal, signal-level; active in NVC sims
195: -- via --psl, plain comments to synthesis) --------------------------------------
196: -- psl default clock is rising_edge(iClk);
197: -- psl assert always (iRst = '1' -> next (oWordValid = '0' and oFlushDone = '0')) report "byte_stuffer: reset must clear the output beat and oFlushDone";
198: -- psl assert never (oFlushDone = '1' and oWordValid = '0') report "byte_stuffer: oFlushDone only fires on a valid output beat (framer iEoi contract)";
199: -- psl assert always (oFlushDone = '1' -> next (oFlushDone = '0')) report "byte_stuffer: oFlushDone is a strict 1-cycle pulse";
200: -- psl assert always (oWordValid = '1' -> oValidBytes <= OUT_BYTES_PER_CYCLE) report "byte_stuffer: oValidBytes exceeds the per-cycle output cap";
201: ---------------------------------------------------------------------------------
202:
203: oWord <= sOutWordReg;
204: oWordValid <= sOutValidReg;
205: oValidBytes <= sOutBytesValidReg;
206: oFlushDone <= sFlushDone;
207: oAlmostFull <= sFifoAlmFull;
208:
209: -------------------------------------------------------------------------------------------------------------------------
210: -- INPUT REGISTER
211: -------------------------------------------------------------------------------------------------------------------------
212: -- Retimes bit_packer output. Latches only on iStall='0' (bit_packer holds its
213: -- output across a stall, so one latch == one consume). Not a skid: the
214: -- accumulator can't backpressure, so iStall is the only legal gate.
215:
216: input_reg_proc : process (iClk) is
217: begin
218:
219: if rising_edge(iClk) then
220: if (iRst = '1') then
221: sWord <= (others => '0');
222: sWordValidLen <= (others => '0');
223: sWordValid <= '0';
224: sFlush <= '0';
225: elsif (iStall = '0') then
226: sWord <= iWord;
227: sWordValidLen <= iWordValidLen;
228: sWordValid <= iWordValid;
229: sFlush <= iFlush;
230: end if;
231: end if;
232:
233: end process input_reg_proc;
234:
235: -------------------------------------------------------------------------------------------------------------------------
236: -- STAGE 1: Accumulator
237: -------------------------------------------------------------------------------------------------------------------------
238: -- Accumulates the variable length word from bit packer until its wide enough to fit
239: -- in the data FIFO, pack them as byte-valid + last_flag.
240: --
241: -- NOTE: Flush can take up to 2 cycles
242:
243: stage1_proc : process (iClk) is
244:
245: variable vAccumBuffer : std_logic_vector(ACCUM_BITS - 1 downto 0);
246: variable vAccumCountBits : natural range 0 to ACCUM_BITS;
247: variable vAccumCountBitsFlush : natural range 0 to ACCUM_BITS;
248: variable vFlushValidBits : natural range 0 to FIFO_BITS;
249: variable vFlushRawBits : natural range 0 to ACCUM_BITS;
250: variable vValidLenInt : natural;
251: variable vFlushPending : std_logic;
252: variable vPadBits : natural;
253: variable vLastFlag : std_logic;
254: variable vWide : std_logic_vector(ACCUM_BITS - 1 downto 0);
255: variable vMaskTop : std_logic_vector(ACCUM_BITS - 1 downto 0);
256: variable vShifted : std_logic_vector(ACCUM_BITS - 1 downto 0);
257: variable vMask : std_logic_vector(ACCUM_BITS - 1 downto 0);
258:
259: begin
260:
261: if rising_edge(iClk) then
262: if (iRst = '1') then
263: sAccumBuffer <= (others => '0');
264: sAccumCountBits <= (others => '0');
265: sAccumCountBitsFlush <= (others => '0');
266: sFlushValidBits <= (others => '0');
267: sFlushPending <= '0';
268: sFifoInValid <= '0';
269: sFifoInData <= (others => '0');
270: sBvQueueInValid <= '0';
271: sBvQueueInData <= (others => '0');
272: else
273: vAccumBuffer := sAccumBuffer;
274: vAccumCountBits := to_integer(sAccumCountBits);
275: vAccumCountBitsFlush := to_integer(sAccumCountBitsFlush);
276: vFlushValidBits := to_integer(sFlushValidBits);
277: vValidLenInt := to_integer(sWordValidLen);
278: vFlushPending := sFlushPending;
279:
280: sFifoInValid <= '0';
281: sBvQueueInValid <= '0';
282:
283: ---------------------------------------------------------------------------------
284: -- WRITE to Accumulator
285: ---------------------------------------------------------------------------------
286: -- Append input bits (MSB-first)
287:
288: if (sWordValid = '1' and iStall = '0') then
289: vWide := (others => '0');
290: vWide(ACCUM_BITS - 1 downto ACCUM_BITS - IN_WIDTH) := sWord;
291: vMaskTop := (others => '0');
292:
293: for i in 0 to IN_WIDTH - 1 loop
294:
295: if (i < vValidLenInt) then
296: vMaskTop(ACCUM_BITS - 1 - i) := '1';
297: end if;
298:
299: end loop;
300:
301: vShifted := std_logic_vector(shift_right(unsigned(vWide), vAccumCountBits));
302: vMask := std_logic_vector(shift_right(unsigned(vMaskTop), vAccumCountBits));
303: vAccumBuffer := (vAccumBuffer and not vMask) or (vShifted and vMask);
304: vAccumCountBits := vAccumCountBits + vValidLenInt;
305: end if;
306:
307: -- Flush entry: pad sub-byte residue to byte boundary, then pad up to
308: -- the next FIFO_BITS multiple so every drain becomes a constant
309: -- FIFO_BITS shift downstream.
310: if (sFlush = '1' and iStall = '0') then
311: assert vFlushPending = '0'
312: report "byte_stuffer: iFlush asserted while a flush is already pending"
313: severity failure;
314:
315: -- Raw valid-bit count at flush (before any padding). The valid bits
316: -- of the final FIFO word are derived from this once the FIFO_BITS pad
317: -- is known (below) — using the full count here is only correct for a
318: -- single-word flush and overflows on a multi-word flush.
319: vFlushRawBits := vAccumCountBits;
320:
321: -- byte-boundary pad
322: if ((vAccumCountBits mod 8) /= 0) then
323: vPadBits := 8 - (vAccumCountBits mod 8);
324:
325: for j in 0 to 7 loop
326:
327: if (j < vPadBits) then
328: vAccumBuffer(ACCUM_BITS - 1 - vAccumCountBits) := '0';
329: vAccumCountBits := vAccumCountBits + 1;
330: end if;
331:
332: end loop;
333:
334: end if;
335:
336: -- FIFO_BITS-multiple pseudo-pad (no bit is written)
337: if ((vAccumCountBits mod FIFO_BITS) /= 0) then
338: vPadBits := FIFO_BITS - (vAccumCountBits mod FIFO_BITS);
339: vAccumCountBits := vAccumCountBits + vPadBits;
340: end if;
341:
342: vAccumCountBitsFlush := vAccumCountBits;
343:
344: -- Real bits carried by the final FIFO word: the raw bits falling in
345: -- the last FIFO_BITS slice. Single-word flush -> equals vFlushRawBits;
346: -- multi-word flush -> the remainder, always in (0, FIFO_BITS].
347: vFlushValidBits := vFlushRawBits + FIFO_BITS - vAccumCountBitsFlush;
348:
349: vFlushPending := '1';
350: end if;
351:
352: ---------------------------------------------------------------------------------
353: -- READ from Accumulator to FIFO
354: ---------------------------------------------------------------------------------
355: -- Single constant-shift drain
356:
357: assert not (sFifoFull = '1' and (vFlushPending = '1' or vAccumCountBits >= FIFO_BITS))
358: report "byte_stuffer: FIFO full but accumulator didn't stall"
359: severity failure;
360:
361: if (sFifoFull = '0') then
362: if (vFlushPending = '1') then
363: if (vAccumCountBitsFlush = FIFO_BITS) then
364: vLastFlag := '1';
365: sBvQueueInValid <= '1';
366: sBvQueueInData <= std_logic_vector(to_unsigned(vFlushValidBits, LAST_BITS_WIDTH));
367: vFlushPending := '0';
368: else
369: vLastFlag := '0';
370: end if;
371:
372: sFifoInData <= vAccumBuffer(ACCUM_BITS - 1 downto ACCUM_BITS - FIFO_BITS) & vLastFlag;
373: sFifoInValid <= '1';
374:
375: vAccumBuffer := std_logic_vector(shift_left(unsigned(vAccumBuffer), FIFO_BITS));
376: vAccumCountBits := vAccumCountBits - FIFO_BITS;
377: vAccumCountBitsFlush := vAccumCountBitsFlush - FIFO_BITS;
378: elsif (vAccumCountBits >= FIFO_BITS) then
379: sFifoInData <= vAccumBuffer(ACCUM_BITS - 1 downto ACCUM_BITS - FIFO_BITS) & '0';
380: sFifoInValid <= '1';
381:
382: vAccumBuffer := std_logic_vector(shift_left(unsigned(vAccumBuffer), FIFO_BITS));
383: vAccumCountBits := vAccumCountBits - FIFO_BITS;
384: end if;
385: end if;
386:
387: sAccumBuffer <= vAccumBuffer;
388: sAccumCountBits <= to_unsigned(vAccumCountBits, sAccumCountBits'length);
389: sAccumCountBitsFlush <= to_unsigned(vAccumCountBitsFlush, sAccumCountBitsFlush'length);
390: sFlushValidBits <= to_unsigned(vFlushValidBits, sFlushValidBits'length);
391: sFlushPending <= vFlushPending;
392:
393: assert vAccumCountBits <= ACCUM_BITS
394: report "byte_stuffer: stage 1 accumulator overflow"
395: severity failure;
396: end if;
397: end if;
398:
399: end process stage1_proc;
400:
401: -------------------------------------------------------------------------------------------------------------------------
402: -- STAGE 2: FIFOs (Data and byte valid)
403: -------------------------------------------------------------------------------------------------------------------------
404: fifo_inst : entity work.olo_base_fifo_sync(rtl)
405: generic map (
406: WIDTH_G => FIFO_WIDTH,
407: DEPTH_G => BURST_DEPTH,
408: ALMFULLON_G => true,
409: ALMFULLLEVEL_G => ALM_FULL_LEVEL,
410: RAMSTYLE_G => "auto",
411: RAMBEHAVIOR_G => "RBW"
412: )
413: port map (
414: Clk => iClk,
415: Rst => iRst,
416: In_Data => sFifoInData,
417: In_Valid => sFifoInValid,
418: In_Ready => sFifoInReady,
419: Out_Data => sFifoOutData,
420: Out_Valid => sFifoOutValid,
421: Out_Ready => sFifoOutReady,
422: Full => sFifoFull,
423: AlmFull => sFifoAlmFull,
424: Empty => open,
425: AlmEmpty => open
426: );
427:
428: -- Read last-word valid-bit-count FIFO on Last word
429: sBvQueueOutReady <= sSkidTaken and sSkidLast;
430:
431: byte_valid_fifo_inst : entity work.olo_base_fifo_sync(rtl)
432: generic map (
433: WIDTH_G => LAST_BITS_WIDTH,
434: DEPTH_G => BYTE_VALID_QUEUE_DEPTH,
435: RAMSTYLE_G => "auto",
436: RAMBEHAVIOR_G => "RBW"
437: )
438: port map (
439: Clk => iClk,
440: Rst => iRst,
441: In_Data => sBvQueueInData,
442: In_Valid => sBvQueueInValid,
443: In_Ready => open,
444: Out_Data => sBvQueueOutData,
445: Out_Valid => open,
446: Out_Ready => sBvQueueOutReady,
447: Full => open,
448: Empty => open
449: );
450:
451: -------------------------------------------------------------------------------------------------------------------------
452: -- STAGE 3: FF stuffer + output emit
453: -------------------------------------------------------------------------------------------------------------------------
454: -- Stuffs a '0' bit after a 0xFF byte in data, this is required by the
455: -- standard T.87 since a byte 0xFF followed by a bit '1' denotes a
456: -- marker and markers aren't allowed on the payload
457: --
458: -- NOTE: Flush can take up to 2 cycles
459:
460: -- Stage 3 drains the skid buffer when it has data and the hold has room.
461: sSkidTaken <= '1' when sSkidValid = '1'
462: and sStuffBufferBits <= to_unsigned(HOLD_BITS - FIFO_BITS, sStuffBufferBits'length)
463: and iReady = '1'
464: and sLastPending = '0' else
465: '0';
466: -- Pop FIFO when the skid buffer is empty or being drained this cycle.
467: sFifoOutReady <= '1' when sSkidValid = '0' or sSkidTaken = '1' else
468: '0';
469:
470: sSkidData <= sSkidWord(FIFO_WIDTH - 1 downto DATA_LSB);
471: sSkidLast <= sSkidWord(LAST_POS);
472:
473: skid_proc : process (iClk) is
474: begin
475:
476: if rising_edge(iClk) then
477: if (iRst = '1') then
478: sSkidValid <= '0';
479: sSkidWord <= (others => '0');
480: else
481: if (sSkidTaken = '1') then
482: sSkidValid <= '0';
483: end if;
484: if (sFifoOutValid = '1' and sFifoOutReady = '1') then
485: sSkidWord <= sFifoOutData;
486: sSkidValid <= '1';
487: end if;
488: end if;
489: end if;
490:
491: end process skid_proc;
492:
493: stage3_proc : process (iClk) is
494:
495: variable vStuffBuffer : std_logic_vector(HOLD_BITS - 1 downto 0);
496: variable vStuffBufferBits : natural range 0 to HOLD_BITS;
497: variable vStuffBufferLast : std_logic;
498: variable vPrevFF : std_logic;
499:
500: variable vValidBytesInt : natural range 0 to FIFO_BYTES;
501: variable vValidBitsInt : natural range 0 to FIFO_BITS;
502:
503: -- Parallel-precomputed FF-equality flags for the 8 fixed candidate
504: -- byte windows the chain can ever pick from.
505: variable ff0 : std_logic; -- offset 0
506: variable ff1a, ff1b : std_logic; -- offsets 7, 8
507: variable ff2a, ff2b : std_logic; -- offsets 15, 16
508: variable ff3a : std_logic;
509: variable ff3b : std_logic;
510: variable ff3c : std_logic; -- offsets 22, 23, 24
511:
512: type byte_array is array (natural range <>) of std_logic_vector(7 downto 0);
513:
514: type cumulative_array is array (natural range <>) of natural range 0 to 32;
515:
516: variable vByte : byte_array(0 to 3);
517: variable vCumu : cumulative_array(0 to 3);
518: variable vStuffed : std_logic_vector(3 downto 0);
519:
520: variable vEmitData : std_logic_vector(OUT_WIDTH - 1 downto 0);
521: variable vEmitBytes : natural range 0 to OUT_BYTES_PER_CYCLE;
522: variable vConsumed : natural range 0 to 32;
523: variable vEmitLastFF : std_logic;
524: variable vPadByte : std_logic_vector(7 downto 0);
525:
526: begin
527:
528: if rising_edge(iClk) then
529: if (iRst = '1') then
530: sStuffBuffer <= (others => '0');
531: sStuffBufferBits <= (others => '0');
532: sStuffBufferLast <= '0';
533: sPrevFF <= '0';
534: sOutWordReg <= (others => '0');
535: sOutValidReg <= '0';
536: sOutBytesValidReg <= (others => '0');
537: sFlushDone <= '0';
538: sLastPending <= '0';
539: elsif (sLastPending = '1') then
540: -- EOI terminal beat, assembled outside the main chain (1 extra cycle,
541: -- absorbed by the stage 2 FIFO). Sub-byte residue or dangling 0xFF
542: -- emits one padded byte; a byte-aligned clean end emits a 0-byte beat.
543:
544: if (iReady = '1') then
545: vStuffBufferBits := to_integer(sStuffBufferBits);
546: vPadByte := (others => '0');
547:
548: if (sPrevFF = '1') then
549: -- Stuff '0' at MSB, up to 7 real bits below it, zero pad.
550: if (vStuffBufferBits > 0) then
551: vPadByte(6 downto 7 - vStuffBufferBits) := sStuffBuffer(HOLD_BITS - 1 downto HOLD_BITS - vStuffBufferBits);
552: end if;
553: elsif (vStuffBufferBits > 0) then
554: vPadByte(7 downto 8 - vStuffBufferBits) := sStuffBuffer(HOLD_BITS - 1 downto HOLD_BITS - vStuffBufferBits);
555: end if;
556:
557: if (vStuffBufferBits = 0 and sPrevFF = '0') then
558: sOutWordReg <= (others => '0');
559: sOutBytesValidReg <= (others => '0');
560: else
561: sOutWordReg(OUT_WIDTH - 1 downto OUT_WIDTH - 8) <= vPadByte;
562: sOutWordReg(OUT_WIDTH - 9 downto 0) <= (others => '0');
563: sOutBytesValidReg <= to_unsigned(1, sOutBytesValidReg'length);
564: end if;
565:
566: sOutValidReg <= '1';
567: sFlushDone <= '1';
568: sLastPending <= '0';
569: sStuffBufferLast <= '0';
570: sPrevFF <= '0';
571: sStuffBuffer <= (others => '0');
572: sStuffBufferBits <= (others => '0');
573: else
574: sOutValidReg <= '0';
575: sFlushDone <= '0';
576: end if;
577: else
578: vStuffBuffer := sStuffBuffer;
579: vStuffBufferBits := to_integer(sStuffBufferBits);
580: vStuffBufferLast := sStuffBufferLast;
581: vPrevFF := sPrevFF;
582: vEmitBytes := 0;
583: vEmitData := (others => '0');
584: vConsumed := 0;
585: sFlushDone <= '0';
586:
587: ----------------------------------------------------------------------
588: -- (1) Refill: drain the skid buffer into the holding buffer.
589: -- Only the final word may be partial.
590: ----------------------------------------------------------------------
591: if (sSkidTaken = '1') then
592: if (sSkidLast = '0') then
593: vStuffBuffer(HOLD_BITS - 1 - vStuffBufferBits downto HOLD_BITS - vStuffBufferBits - FIFO_BITS) := sSkidData;
594: vStuffBufferBits := vStuffBufferBits + FIFO_BITS;
595: else
596: -- Last data beat, may be partial. The sideband carries the real
597: -- bit count; stage 1's byte-boundary pad lives in the top byte(s)
598: -- but is excluded here so the stuffer never emits it.
599: vValidBitsInt := to_integer(unsigned(sBvQueueOutData));
600: vValidBytesInt := (vValidBitsInt + 7) / 8; -- bytes physically present
601:
602: for k in 0 to FIFO_BYTES - 1 loop
603:
604: -- Write partial word to buffer
605: if (k < vValidBytesInt) then
606: vStuffBuffer(HOLD_BITS - 1 - vStuffBufferBits - k * 8 downto HOLD_BITS - vStuffBufferBits - (k + 1) * 8)
607: := sSkidData(FIFO_BITS - 1 - k * 8 downto FIFO_BITS - (k + 1) * 8);
608: end if;
609:
610: end loop;
611:
612: vStuffBufferBits := vStuffBufferBits + vValidBitsInt;
613: vStuffBufferLast := '1';
614: end if;
615: end if;
616:
617: ----------------------------------------------------------------------
618: -- (2) Parallel-precompute FF flags for the 8 fixed candidate byte
619: -- windows.
620: ----------------------------------------------------------------------
621: ff0 := bool2bit(vStuffBuffer(HOLD_BITS - 1 downto HOLD_BITS - 8) = x"FF");
622: ff1a := bool2bit(vStuffBuffer(HOLD_BITS - 8 downto HOLD_BITS - 15) = x"FF");
623: ff1b := bool2bit(vStuffBuffer(HOLD_BITS - 9 downto HOLD_BITS - 16) = x"FF");
624: ff2a := bool2bit(vStuffBuffer(HOLD_BITS - 16 downto HOLD_BITS - 23) = x"FF");
625: ff2b := bool2bit(vStuffBuffer(HOLD_BITS - 17 downto HOLD_BITS - 24) = x"FF");
626: ff3a := bool2bit(vStuffBuffer(HOLD_BITS - 23 downto HOLD_BITS - 30) = x"FF");
627: ff3b := bool2bit(vStuffBuffer(HOLD_BITS - 24 downto HOLD_BITS - 31) = x"FF");
628: ff3c := bool2bit(vStuffBuffer(HOLD_BITS - 25 downto HOLD_BITS - 32) = x"FF");
629:
630: ----------------------------------------------------------------------
631: -- (3) Resolve the 4-slot stuffer chain from (vPrevFF, ff flags, vStuffBuffer).
632: ----------------------------------------------------------------------
633: case vPrevFF is
634:
635: when '1' =>
636:
637: -- byte0 stuffs: '0' + 7 real bits at offset 0.
638: vByte(0) := '0' & vStuffBuffer(HOLD_BITS - 1 downto HOLD_BITS - 7);
639: vStuffed(0) := '0';
640: vCumu(0) := 7;
641: -- byte1 reads 8 bits at offset 7.
642: vByte(1) := vStuffBuffer(HOLD_BITS - 8 downto HOLD_BITS - 15);
643: vStuffed(1) := ff1a;
644: vCumu(1) := 15;
645:
646: case vStuffed(1) is
647:
648: when '1' =>
649:
650: -- byte1 = FF → byte2 stuffs at offset 15 (7 bits).
651: vByte(2) := '0' & vStuffBuffer(HOLD_BITS - 16 downto HOLD_BITS - 22);
652: vStuffed(2) := '0';
653: vCumu(2) := 22;
654: vByte(3) := vStuffBuffer(HOLD_BITS - 23 downto HOLD_BITS - 30);
655: vStuffed(3) := ff3a;
656: vCumu(3) := 30;
657:
658: when others =>
659:
660: -- byte1 ≠ FF → byte2 reads 8 bits at offset 15.
661: vByte(2) := vStuffBuffer(HOLD_BITS - 16 downto HOLD_BITS - 23);
662: vStuffed(2) := ff2a;
663: vCumu(2) := 23;
664:
665: case vStuffed(2) is
666:
667: when '1' =>
668:
669: vByte(3) := '0' & vStuffBuffer(HOLD_BITS - 24 downto HOLD_BITS - 30);
670: vStuffed(3) := '0';
671: vCumu(3) := 30;
672:
673: when others =>
674:
675: vByte(3) := vStuffBuffer(HOLD_BITS - 24 downto HOLD_BITS - 31);
676: vStuffed(3) := ff3b;
677: vCumu(3) := 31;
678:
679: end case;
680:
681: end case;
682:
683: when others =>
684:
685: -- byte0 reads 8 bits at offset 0.
686: vByte(0) := vStuffBuffer(HOLD_BITS - 1 downto HOLD_BITS - 8);
687: vStuffed(0) := ff0;
688: vCumu(0) := 8;
689:
690: case ff0 is
691:
692: when '1' =>
693:
694: -- byte0 = FF → byte1 stuffs at offset 8 (7 bits).
695: vByte(1) := '0' & vStuffBuffer(HOLD_BITS - 9 downto HOLD_BITS - 15);
696: vStuffed(1) := '0';
697: vCumu(1) := 15;
698: vByte(2) := vStuffBuffer(HOLD_BITS - 16 downto HOLD_BITS - 23);
699: vStuffed(2) := ff2a;
700: vCumu(2) := 23;
701:
702: case vStuffed(2) is
703:
704: when '1' =>
705:
706: vByte(3) := '0' & vStuffBuffer(HOLD_BITS - 24 downto HOLD_BITS - 30);
707: vStuffed(3) := '0';
708: vCumu(3) := 30;
709:
710: when others =>
711:
712: vByte(3) := vStuffBuffer(HOLD_BITS - 24 downto HOLD_BITS - 31);
713: vStuffed(3) := ff3b;
714: vCumu(3) := 31;
715:
716: end case;
717:
718: when others =>
719:
720: -- byte0 ≠ FF → byte1 reads 8 bits at offset 8.
721: vByte(1) := vStuffBuffer(HOLD_BITS - 9 downto HOLD_BITS - 16);
722: vStuffed(1) := ff1b;
723: vCumu(1) := 16;
724:
725: case vStuffed(1) is
726:
727: when '1' =>
728:
729: -- byte1 = FF → byte2 stuffs at offset 16.
730: vByte(2) := '0' & vStuffBuffer(HOLD_BITS - 17 downto HOLD_BITS - 23);
731: vStuffed(2) := '0';
732: vCumu(2) := 23;
733: vByte(3) := vStuffBuffer(HOLD_BITS - 24 downto HOLD_BITS - 31);
734: vStuffed(3) := ff3b;
735: vCumu(3) := 31;
736:
737: when others =>
738:
739: -- byte1 ≠ FF → byte2 reads 8 bits at offset 16.
740: vByte(2) := vStuffBuffer(HOLD_BITS - 17 downto HOLD_BITS - 24);
741: vStuffed(2) := ff2b;
742: vCumu(2) := 24;
743:
744: case vStuffed(2) is
745:
746: when '1' =>
747:
748: vByte(3) := '0' & vStuffBuffer(HOLD_BITS - 25 downto HOLD_BITS - 31);
749: vStuffed(3) := '0';
750: vCumu(3) := 31;
751:
752: when others =>
753:
754: vByte(3) := vStuffBuffer(HOLD_BITS - 25 downto HOLD_BITS - 32);
755: vStuffed(3) := ff3c;
756: vCumu(3) := 32;
757:
758: end case;
759:
760: end case;
761:
762: end case;
763:
764: end case;
765:
766: ----------------------------------------------------------------------
767: -- (4) Pick emit count from how much of the chain's consumption is
768: -- covered by vStuffBufferBits. This is the *only* place sStuffBufferBits gates
769: -- output, so partial fills naturally degrade to 1..3 byte beats.
770: ----------------------------------------------------------------------
771: vEmitBytes := 0;
772: vConsumed := 0;
773: vEmitLastFF := vPrevFF;
774:
775: if (iReady = '1') then
776:
777: for i in 0 to OUT_BYTES_PER_CYCLE - 1 loop
778:
779: if (vStuffBufferBits >= vCumu(i)) then
780: vEmitBytes := i + 1;
781: vConsumed := vCumu(i);
782: vEmitLastFF := vStuffed(i);
783: end if;
784:
785: end loop;
786:
787: end if;
788:
789: ----------------------------------------------------------------------
790: -- (5) Pack output and shift buffer by the total bits consumed.
791: ----------------------------------------------------------------------
792: for i in 0 to OUT_BYTES_PER_CYCLE - 1 loop
793:
794: vEmitData(OUT_WIDTH - 1 - (i * 8) downto OUT_WIDTH - ((i + 1) * 8)) := vByte(i);
795:
796: end loop;
797:
798: if (vEmitBytes > 0) then
799: vStuffBuffer := std_logic_vector(shift_left(unsigned(vStuffBuffer), vConsumed));
800: vStuffBufferBits := vStuffBufferBits - vConsumed;
801: vPrevFF := vEmitLastFF;
802: end if;
803:
804: ----------------------------------------------------------------------
805: -- (6) Output register and flush-done / drain entry.
806: ----------------------------------------------------------------------
807: if (vEmitBytes > 0) then
808: sOutWordReg <= vEmitData;
809: sOutBytesValidReg <= to_unsigned(vEmitBytes, sOutBytesValidReg'length);
810: sOutValidReg <= '1';
811: else
812: sOutValidReg <= '0';
813: end if;
814:
815: -- Once the last word is consumed and only a sub-byte residue remains
816: -- (bits < 8, including the bits=0 clean end), hand off to the
817: -- sLastPending branch which assembles the final beat off this path.
818: if (iReady = '1'
819: and vStuffBufferLast = '1'
820: and vStuffBufferBits < 8) then
821: sLastPending <= '1';
822: end if;
823:
824: sStuffBuffer <= vStuffBuffer;
825: sStuffBufferBits <= to_unsigned(vStuffBufferBits, sStuffBufferBits'length);
826: sStuffBufferLast <= vStuffBufferLast;
827: sPrevFF <= vPrevFF;
828: end if;
829: end if;
830:
831: end process stage3_proc;
832:
833: end architecture behavioral;