NVC code coverage report

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;