File: ../../Sources/jls_framer.vhd
0: ----------------------------------------------------------------------------------
1: -- Engineer: Vitor Mendes Camilo
2: --
3: -- Module Name: jls_framer - Behavioral
4: -- Description:
5: --
6: -- Wraps the byte-stuffed compressed payload with JPEG-LS framing markers:
7: -- SOI + SOF55 + SOS (header, 25 bytes, before data)
8: -- EOI = FF D9 (footer, 2 bytes, after data)
9: --
10: -- Architecture:
11: -- - One byte FIFO holds payload bytes. The FF D9 footer is pushed into
12: -- the FIFO right after byte_stuffer's last word on iEoi, so the output
13: -- side sees footer bytes as ordinary data and oLast falls out naturally.
14: -- - Header bytes come from a combinational ROM (get_header_byte). The
15: -- FIFO never stores header bytes since there are too many of them and
16: -- we need the FIFO to store new data as the HEADER is being sent.
17: -- - Output FSM (IDLE / HEADER / DATA) selects header ROM vs FIFO per beat.
18: -- Splicing handles the partial last header beat (filled from the FIFO)
19: -- and the partial last data beat (cut at sEndOfImage with oLast).
20: -- - byte_stuffer is never back-pressured. The FIFO is sized so that
21: -- pushes always fit under correct upstream behavior.
22: --
23: -- iEoi (1-cycle pulse): asserts on byte_stuffer's last word for the image.
24: -- That cycle the framer pushes (iBsValidBytes byte_stuffer bytes) followed
25: -- by (FF D9), and latches sEndOfImage to the offset (from FIFO head) of the
26: -- trailing D9. The output FSM watches this offset; when the bytes popped
27: -- this cycle reach it, oLast asserts on that beat.
28: --
29: -- In-flight EoI offsets are held in a small queue (EOI_FIFO_DEPTH) so that
30: -- tiny back-to-back images can have multiple D9 markers pending in the FIFO
31: -- at once. Depth 3 covers the smallest line_buffer-supported image (3x1)
32: -- pipelined back-to-back; overflow is caught by an assert at push time.
33: --
34: -- Image dimensions are fixed at reset by the top level and read directly
35: -- from the ports without registering.
36: --
37: -- IN_WIDTH : word width from byte_stuffer (CO_BYTE_STUFFER_OUT_WIDTH)
38: -- OUT_WIDTH : final output word width (CO_OUT_WIDTH_STD)
39: -- BUFFER_BYTES : payload FIFO depth in bytes (auto-derived; see below)
40: --
41: ---------------------------------------------------------------------------
42: -- BUFFER_BYTES sizing (worst-case occupancy)
43: ---------------------------------------------------------------------------
44: -- Two regimes contribute to peak FIFO occupancy:
45: --
46: -- 1) iEoi cycle (single-image worst case):
47: -- DATA pop fires only when vCount >= BYTES_OUT, so vCount_pre at iEoi
48: -- can sit at BYTES_OUT - 1 without triggering a pop. The iEoi cycle
49: -- then pushes (iBsValidBytes <= BYTES_IN) plus the 2 footer bytes:
50: -- vCount_iEoi_max = BYTES_OUT - 1 + BYTES_IN + 2
51: --
52: -- 2) Back-to-back images (image N+1 starts pushing while N drains):
53: -- From iEoi cycle T, the FF D9 marker takes
54: -- D = ceil(vCount_iEoi_max / BYTES_OUT)
55: -- cycles to reach the FIFO head and emit (oLast fires on the D9 beat).
56: -- After that, the framer transits to HEADER and emits the 25-byte
57: -- marker block over
58: -- N_h = ceil(HEADER_LEN / BYTES_OUT)
59: -- beats. The first (N_h - 1) HEADER beats pull bytes from the ROM and
60: -- do not pop the FIFO; the final mixed beat splices ROM + FIFO bytes
61: -- and pops (BYTES_OUT - HEADER_LEN mod BYTES_OUT) data bytes.
62: --
63: -- Worst case: byte_stuffer begins pushing image N+1 the cycle after
64: -- iEoi and continues at BYTES_IN/cycle throughout. Since BYTES_OUT is
65: -- normally >= BYTES_IN, image N drains faster than N+1 accumulates,
66: -- so by the time the mixed HEADER beat fires N's bytes are gone and
67: -- only N+1's bytes occupy the FIFO. Peak N+1 bytes (after the mixed
68: -- beat's net push):
69: -- PEAK_N1 = (D + N_h) * BYTES_IN
70: -- - (BYTES_OUT - HEADER_LEN mod BYTES_OUT)
71: --
72: -- General formula (peak FIFO occupancy across all phases):
73: --
74: -- V_iEoi = BYTES_OUT + BYTES_IN + 1
75: -- D = ceil(V_iEoi / BYTES_OUT)
76: -- N_h = ceil(HEADER_LEN / BYTES_OUT)
77: --
78: -- BUFFER_BYTES = max(
79: -- V_iEoi, (a) iEoi cycle
80: -- D * BYTES_IN, (b) end of N drain
81: -- (D + N_h - 1) * BYTES_IN, (c) last full hdr beat
82: -- (D + N_h) * BYTES_IN - (N_h*BYTES_OUT - HEADER_LEN) (d) after mixed beat
83: -- )
84: --
85: -- For typical configs with BYTES_IN <= BYTES_OUT, term (d) dominates.
86: --
87: -- Standard config (BYTES_IN=8, BYTES_OUT=9, HEADER_LEN=25):
88: -- V_iEoi = 18, D = 2, N_h = 3
89: -- (a)=18, (b)=16, (c)=32, (d)=(5*8) - (3*9 - 25) = 40 - 2 = 38
90: -- BUFFER_BYTES = 38
91: --
92: -- This also implies that outputting the last bytes after EOI can take several
93: -- cycles, the image last byte needs to be tracked to assert oLast, padding and
94: -- oKeep on the correct beat.
95: --
96: -- Caveat: the BUFFER_BYTES formula above assumes iReady = '1' steady-state.
97: -- With sustained downstream backpressure the FIFO has no bound (no upstream
98: -- stall path). A runtime assert catches overflow in simulation.
99: ----------------------------------------------------------------------------------
100:
101: library ieee;
102: use ieee.std_logic_1164.all;
103: use ieee.numeric_std.all;
104: use work.openjls_pkg.all;
105:
106: library work;
107: use work.olo_base_pkg_math.log2ceil;
108:
109: entity jls_framer is
110: generic (
111: BITNESS : natural := CO_BITNESS_STD;
112: IN_WIDTH : natural := CO_BYTE_STUFFER_OUT_WIDTH;
113: OUT_WIDTH : natural := CO_OUT_WIDTH_STD;
114: MAX_IMAGE_WIDTH : natural := 4096;
115: MAX_IMAGE_HEIGHT : natural := 4096;
116: -- Cushion between oReady deassertion and BUFFER_BYTES. Covers two
117: -- in-flight cycles of byte_stuffer emission (decision + transfer pipeline)
118: -- plus the +2 EOI footer push. Worst case: 2*BYTES_IN + 2.
119: STALL_MARGIN_BYTES : natural := 2 * math_ceil_div(CO_BYTE_STUFFER_OUT_WIDTH, 8) + 2
120: );
121: port (
122: iClk : in std_logic;
123: iRst : in std_logic;
124: -- Image control
125: iStart : in std_logic;
126: iImageWidth : in unsigned(log2ceil(MAX_IMAGE_WIDTH + 1) - 1 downto 0);
127: iImageHeight : in unsigned(log2ceil(MAX_IMAGE_HEIGHT + 1) - 1 downto 0);
128: iEoi : in std_logic;
129: -- Byte stuffer interface
130: iWord : in std_logic_vector(IN_WIDTH - 1 downto 0);
131: iValid : in std_logic;
132: iByteEnable : in unsigned(log2ceil(IN_WIDTH / 8 + 1) - 1 downto 0);
133: oReady : out std_logic;
134: -- Output AXI stream interface
135: oWord : out std_logic_vector(OUT_WIDTH - 1 downto 0);
136: oValid : out std_logic;
137: oByteEnable : out unsigned(log2ceil(OUT_WIDTH / 8 + 1) - 1 downto 0);
138: oLast : out std_logic;
139: iReady : in std_logic
140: );
141: end entity jls_framer;
142:
143: architecture behavioral of jls_framer is
144:
145: constant NEAR : natural := 0;
146: constant BYTES_IN : natural := IN_WIDTH / 8;
147: constant BYTES_OUT : natural := OUT_WIDTH / 8;
148: constant HEADER_LEN : natural := 25;
149:
150: -- Auto-derived FIFO depth (worst-case occupancy; see file header).
151: -- BUFFER_BYTES_NOMINAL handles steady-state (iReady='1') traffic.
152: -- STALL_MARGIN_BYTES extends the FIFO so oAlmostFull can assert at the
153: -- nominal threshold and still absorb in-flight bytes from the upstream
154: -- pipeline (Reg5b → bit_packer → byte_stuffer) before the stall reaches.
155: constant V_IEOI : natural := BYTES_OUT + BYTES_IN + 1;
156: constant D_DRAIN : natural := math_ceil_div(V_IEOI, BYTES_OUT);
157: constant N_H : natural := math_ceil_div(HEADER_LEN, BYTES_OUT);
158: -- Header-tail carry: bytes of the final header beat not covered by the
159: -- header itself. Expressed via math_min to stay in natural for wide
160: -- OUT_WIDTHs, where N_H * BYTES_OUT - HEADER_LEN exceeds the fill.
161: constant H_SLACK : natural := N_H * BYTES_OUT - HEADER_LEN;
162: constant BUFFER_BYTES_NOMINAL : natural := math_max(
163: math_max(V_IEOI, D_DRAIN * BYTES_IN),
164: (D_DRAIN + N_H) * BYTES_IN - math_min(BYTES_IN, H_SLACK));
165: constant BUFFER_BYTES : natural := BUFFER_BYTES_NOMINAL + STALL_MARGIN_BYTES;
166: constant BUFFER_WIDTH : natural := BUFFER_BYTES * 8;
167:
168: type fsm_t is (idle, header, data);
169:
170: signal sFsmState : fsm_t;
171:
172: -- EoI tracker: queue of footer (D9) byte offsets for in-flight images.
173: -- Needed for really small images, like the 4x4 image on T.87 H.3 example
174: constant EOI_FIFO_DEPTH : natural := 2;
175:
176: type eoi_offsets_t is array(natural range <>) of natural range 0 to BUFFER_BYTES;
177:
178: signal sBuffer : std_logic_vector(BUFFER_WIDTH - 1 downto 0);
179: signal sFifoByteCount : natural range 0 to BUFFER_BYTES;
180: signal sEoiFifo : eoi_offsets_t(0 to EOI_FIFO_DEPTH - 1);
181: signal sEoiCount : natural range 0 to EOI_FIFO_DEPTH;
182:
183: signal sOutWord : std_logic_vector(OUT_WIDTH - 1 downto 0);
184: signal sOutValid : std_logic;
185: signal sOutLast : std_logic;
186: signal sValidBytes : unsigned(log2ceil(OUT_WIDTH / 8 + 1) - 1 downto 0);
187:
188: signal sHeaderByteIdx : natural range 0 to HEADER_LEN;
189: -- Pending-start counter. Holds iStart pulses that arrive while an earlier
190: -- image is still being framed. Each EOI dequeues one. Sized to absorb the
191: -- worst-case lead of upstream over the framer when byte_stuffer's FIFO is
192: -- buffering several images at once.
193: constant START_QUEUE_DEPTH : natural := 4;
194: signal sNextPending : natural range 0 to START_QUEUE_DEPTH;
195:
196: signal sAxiHandshake : boolean;
197:
198: -- Returns the i-th byte of the 25-byte JPEG-LS frame header.
199: -- Header layout:
200: -- [0-1] FF D8 SOI
201: -- [2-5] FF F7 00 0B SOF55 marker + Lf=11
202: -- [6] P precision (BITNESS)
203: -- [7-8] Y[15:8] Y[7:0] image height (runtime)
204: -- [9-10] X[15:8] X[7:0] image width (runtime)
205: -- [11] 01 Nf=1
206: -- [12-14] 01 11 00 C1=1, H1V1=0x11, Tq1=0
207: -- [15-16] FF DA SOS marker
208: -- [17-18] 00 08 Ls=8
209: -- [19] 01 Ns=1
210: -- [20-21] 01 00 Cs1=1, Tm1=0
211: -- [22] NEAR
212: -- [23-24] 00 00 ILV=0, Al/Ah=0
213:
214: function get_header_byte (
215: idx : natural;
216: width : unsigned(15 downto 0);
217: height : unsigned(15 downto 0)
218: ) return std_logic_vector is
219: begin
220:
221: case idx is
222:
223: when 0 =>
224:
225: return x"FF";
226:
227: when 1 =>
228:
229: return x"D8";
230:
231: when 2 =>
232:
233: return x"FF";
234:
235: when 3 =>
236:
237: return x"F7";
238:
239: when 4 =>
240:
241: return x"00";
242:
243: when 5 =>
244:
245: return x"0B";
246:
247: when 6 =>
248:
249: return std_logic_vector(to_unsigned(BITNESS, 8));
250:
251: when 7 =>
252:
253: return std_logic_vector(height(15 downto 8));
254:
255: when 8 =>
256:
257: return std_logic_vector(height(7 downto 0));
258:
259: when 9 =>
260:
261: return std_logic_vector(width(15 downto 8));
262:
263: when 10 =>
264:
265: return std_logic_vector(width(7 downto 0));
266:
267: when 11 =>
268:
269: return x"01";
270:
271: when 12 =>
272:
273: return x"01";
274:
275: when 13 =>
276:
277: return x"11";
278:
279: when 14 =>
280:
281: return x"00";
282:
283: when 15 =>
284:
285: return x"FF";
286:
287: when 16 =>
288:
289: return x"DA";
290:
291: when 17 =>
292:
293: return x"00";
294:
295: when 18 =>
296:
297: return x"08";
298:
299: when 19 =>
300:
301: return x"01";
302:
303: when 20 =>
304:
305: return x"01";
306:
307: when 21 =>
308:
309: return x"00";
310:
311: when 22 =>
312:
313: return std_logic_vector(to_unsigned(NEAR, 8));
314:
315: when 23 =>
316:
317: return x"00";
318:
319: when 24 =>
320:
321: return x"00";
322:
323: when others =>
324:
325: return x"00";
326:
327: end case;
328:
329: end function get_header_byte;
330:
331: -- Pop head EoI: shift remaining entries down, decrement their offsets by
332: -- bytes popped this beat, decrement count.
333:
334: procedure pop_eoi (
335: variable fifo : inout eoi_offsets_t;
336: variable cnt : inout natural;
337: constant emit : in natural
338: ) is
339: begin
340:
341: for k in 0 to EOI_FIFO_DEPTH - 2 loop
342:
343: if (k + 1 < cnt) then
344: fifo(k) := fifo(k + 1) - emit;
345: else
346: fifo(k) := 0;
347: end if;
348:
349: end loop;
350:
351: fifo(EOI_FIFO_DEPTH - 1) := 0;
352: cnt := cnt - 1;
353:
354: end procedure pop_eoi;
355:
356: -- Decrement all live EoI offsets by bytes popped this beat (no head pop).
357:
358: procedure dec_eoi (
359: variable fifo : inout eoi_offsets_t;
360: constant cnt : in natural;
361: constant emit : in natural
362: ) is
363: begin
364:
365: for k in 0 to EOI_FIFO_DEPTH - 1 loop
366:
367: if (k < cnt) then
368: fifo(k) := fifo(k) - emit;
369: end if;
370:
371: end loop;
372:
373: end procedure dec_eoi;
374:
375: begin
376:
377: assert IN_WIDTH mod 8 = 0
378: report "jls_framer: IN_WIDTH must be a multiple of 8"
379: severity failure;
380:
381: assert OUT_WIDTH mod 8 = 0
382: report "jls_framer: OUT_WIDTH must be a multiple of 8"
383: severity failure;
384:
385: assert BYTES_OUT >= BYTES_IN + 1
386: report "jls_framer: OUT_WIDTH must be at least one byte wider than IN_WIDTH for FIFO stability under absolute worst-case scenario (sustained max-rate output across back-to-back images)"
387: severity failure;
388:
389: sAxiHandshake <= (iReady and sOutValid) = '1';
390:
391: -- Contract assertions in PSL: the output side is an AXI-Stream master —
392: -- these hold for any downstream behaviour (active in NVC sims via --psl,
393: -- plain comments to synthesis).
394: -- psl default clock is rising_edge(iClk);
395: -- psl assert always (iRst = '1' -> next (oValid = '0' and oLast = '0')) report "jls_framer: reset must clear the output stream";
396: -- psl assert always ((iRst = '0' and oValid = '1' and iReady = '0') -> next (oValid = '1')) report "jls_framer: oValid must hold until the beat is accepted (AXIS)";
397: -- psl assert never (oLast = '1' and oValid = '0') report "jls_framer: oLast is only meaningful on a valid beat";
398:
399: oWord <= sOutWord;
400: oValid <= sOutValid;
401: oByteEnable <= sValidBytes;
402: oLast <= sOutLast;
403:
404: -- Ready/valid handshake with byte_stuffer: deasserts at the nominal
405: -- sizing threshold, leaving STALL_MARGIN_BYTES headroom for the
406: -- 1-cycle handshake latency (byte_stuffer's registered output).
407: oReady <= '1' when sFifoByteCount < BUFFER_BYTES_NOMINAL else
408: '0';
409:
410: -------------------------------------------------------------------------------------------------------------
411: -- SYNCHRONOUS PROCESS
412: -------------------------------------------------------------------------------------------------------------
413: sync_proc : process (iClk) is
414:
415: variable vBuffer : std_logic_vector(BUFFER_WIDTH - 1 downto 0);
416: variable vFifoByteCount : natural range 0 to BUFFER_BYTES;
417: variable vEoiIdxFifo : eoi_offsets_t(0 to EOI_FIFO_DEPTH - 1);
418: variable vEoiCount : natural range 0 to EOI_FIFO_DEPTH;
419: variable vWidth : unsigned(15 downto 0);
420: variable vHeight : unsigned(15 downto 0);
421: variable vHeaderBytesRemain : natural range 0 to HEADER_LEN;
422: variable vDataNeededBytes : natural range 0 to BYTES_OUT;
423: variable vEmitDataBytes : natural range 0 to BYTES_OUT;
424: variable vCanEmit : boolean;
425: variable vEoiInBeat : boolean;
426: variable vOffsetI : natural range 0 to BYTES_OUT;
427: variable vNextPending : natural range 0 to START_QUEUE_DEPTH;
428:
429: begin
430:
431: if rising_edge(iClk) then
432: if (iRst = '1') then
433: sFsmState <= idle;
434: sBuffer <= (others => '0');
435: sFifoByteCount <= 0;
436: sEoiFifo <= (others => 0);
437: sEoiCount <= 0;
438: sOutWord <= (others => '0');
439: sOutValid <= '0';
440: sOutLast <= '0';
441: sValidBytes <= (others => '0');
442: sHeaderByteIdx <= 0;
443: sNextPending <= 0;
444: else
445: vBuffer := sBuffer;
446: vFifoByteCount := sFifoByteCount;
447: vEoiIdxFifo := sEoiFifo;
448: vEoiCount := sEoiCount;
449: vWidth := resize(iImageWidth, 16);
450: vHeight := resize(iImageHeight, 16);
451: vCanEmit := sAxiHandshake or sOutValid = '0';
452: vNextPending := sNextPending;
453:
454: -- iStart latching: queue starts so back-to-back images can be buffered
455: -- in byte_stuffer while framer drains earlier images.
456: if (iStart = '1') then
457: assert vNextPending < START_QUEUE_DEPTH
458: report "jls_framer: iStart dropped (start queue full)"
459: severity failure;
460: vNextPending := vNextPending + 1;
461: end if;
462:
463: -----------------------------------------------------------------------------------------------------
464: -- READ
465: -----------------------------------------------------------------------------------------------------
466: -- Beat was consumed
467: if (sAxiHandshake) then
468: sOutValid <= '0';
469: sOutLast <= '0';
470: end if;
471:
472: case sFsmState is
473:
474: when idle =>
475:
476: if (vNextPending > 0) then
477: sFsmState <= header;
478: sHeaderByteIdx <= 0;
479: vNextPending := vNextPending - 1;
480: end if;
481:
482: when header =>
483:
484: if (vCanEmit) then
485: vHeaderBytesRemain := HEADER_LEN - sHeaderByteIdx;
486:
487: if (vHeaderBytesRemain >= BYTES_OUT) then
488: -- Full header beat from HEADER ROM
489:
490: for i in 0 to BYTES_OUT - 1 loop
491:
492: sOutWord(OUT_WIDTH - 1 - i * 8 downto OUT_WIDTH - (i + 1) * 8) <= get_header_byte(sHeaderByteIdx + i, vWidth, vHeight);
493:
494: end loop;
495:
496: sValidBytes <= to_unsigned(BYTES_OUT, sValidBytes'length);
497: sOutValid <= '1';
498: sOutLast <= '0';
499:
500: if (sHeaderByteIdx + BYTES_OUT = HEADER_LEN) then
501: sFsmState <= data;
502: else
503: sHeaderByteIdx <= sHeaderByteIdx + BYTES_OUT;
504: end if;
505: else
506: -- Last partial beat, fill remaining bytes from the DATA FIFO
507:
508: vDataNeededBytes := BYTES_OUT - vHeaderBytesRemain;
509:
510: -- Check for EOI in the FIFO bytes
511: vEoiInBeat := vEoiCount > 0 and vEoiIdxFifo(0) < vDataNeededBytes;
512: if (vEoiInBeat) then
513: vEmitDataBytes := vEoiIdxFifo(0) + 1;
514: else
515: vEmitDataBytes := vDataNeededBytes;
516: end if;
517:
518: if (vFifoByteCount >= vEmitDataBytes) then
519: -- FIFO has enough bytes
520: -- Otherwise it'll stall here until it does
521:
522: for i in 0 to BYTES_OUT - 1 loop
523:
524: if (i < vHeaderBytesRemain) then
525: sOutWord(OUT_WIDTH - 1 - i * 8 downto OUT_WIDTH - (i + 1) * 8) <= get_header_byte(sHeaderByteIdx + i, vWidth, vHeight);
526: elsif (i < vHeaderBytesRemain + vEmitDataBytes) then
527: vOffsetI := i - vHeaderBytesRemain;
528: sOutWord(OUT_WIDTH - 1 - i * 8 downto OUT_WIDTH - (i + 1) * 8) <= vBuffer(BUFFER_WIDTH - 1 - vOffsetI * 8 downto BUFFER_WIDTH - (vOffsetI + 1) * 8);
529: else
530: sOutWord(OUT_WIDTH - 1 - i * 8 downto OUT_WIDTH - (i + 1) * 8) <= (others => '0');
531: end if;
532:
533: end loop;
534:
535: sValidBytes <= to_unsigned(vHeaderBytesRemain + vEmitDataBytes, sValidBytes'length);
536: sOutValid <= '1';
537: vBuffer := std_logic_vector(shift_left(unsigned(vBuffer), vEmitDataBytes * 8));
538: vFifoByteCount := vFifoByteCount - vEmitDataBytes;
539:
540: if (vEoiInBeat) then
541: sOutLast <= '1';
542: pop_eoi(vEoiIdxFifo, vEoiCount, vEmitDataBytes);
543:
544: if (vNextPending > 0) then
545: sFsmState <= header;
546: sHeaderByteIdx <= 0;
547: vNextPending := vNextPending - 1;
548: else
549: sFsmState <= idle;
550: end if;
551: else
552: sOutLast <= '0';
553: dec_eoi(vEoiIdxFifo, vEoiCount, vEmitDataBytes);
554: sFsmState <= data;
555: end if;
556: end if;
557: end if;
558: end if;
559:
560: when data =>
561:
562: if (vCanEmit) then
563: vEoiInBeat := vEoiCount > 0 and vEoiIdxFifo(0) < BYTES_OUT;
564: if (vEoiInBeat) then
565: vEmitDataBytes := vEoiIdxFifo(0) + 1;
566: else
567: vEmitDataBytes := BYTES_OUT;
568: end if;
569:
570: if (vFifoByteCount >= vEmitDataBytes) then
571:
572: for i in 0 to BYTES_OUT - 1 loop
573:
574: if (i < vEmitDataBytes) then
575: sOutWord(OUT_WIDTH - 1 - i * 8 downto OUT_WIDTH - (i + 1) * 8) <= vBuffer(BUFFER_WIDTH - 1 - i * 8 downto BUFFER_WIDTH - (i + 1) * 8);
576: else
577: sOutWord(OUT_WIDTH - 1 - i * 8 downto OUT_WIDTH - (i + 1) * 8) <= (others => '0');
578: end if;
579:
580: end loop;
581:
582: sValidBytes <= to_unsigned(vEmitDataBytes, sValidBytes'length);
583: sOutValid <= '1';
584: vBuffer := std_logic_vector(shift_left(unsigned(vBuffer), vEmitDataBytes * 8));
585: vFifoByteCount := vFifoByteCount - vEmitDataBytes;
586:
587: if (vEoiInBeat) then
588: sOutLast <= '1';
589: pop_eoi(vEoiIdxFifo, vEoiCount, vEmitDataBytes);
590:
591: if (vNextPending > 0) then
592: sFsmState <= header;
593: sHeaderByteIdx <= 0;
594: vNextPending := vNextPending - 1;
595: else
596: sFsmState <= idle;
597: end if;
598: else
599: sOutLast <= '0';
600: dec_eoi(vEoiIdxFifo, vEoiCount, vEmitDataBytes);
601: end if;
602: end if;
603: end if;
604:
605: end case;
606:
607: -----------------------------------------------------------------------------------------------------
608: -- WRITE
609: -----------------------------------------------------------------------------------------------------
610: if (iValid = '1') then
611:
612: for i in 0 to BYTES_IN - 1 loop
613:
614: if (i < to_integer(iByteEnable)) then
615: vBuffer(BUFFER_WIDTH - 1 - (vFifoByteCount + i) * 8 downto BUFFER_WIDTH - (vFifoByteCount + i + 1) * 8) := iWord(IN_WIDTH - 1 - i * 8 downto IN_WIDTH - (i + 1) * 8);
616: end if;
617:
618: end loop;
619:
620: vFifoByteCount := vFifoByteCount + to_integer(iByteEnable);
621:
622: if (iEoi = '1') then
623: -- Push FOOTER into the FIFO right after the last data byte
624: vBuffer(BUFFER_WIDTH - 1 - vFifoByteCount * 8 downto BUFFER_WIDTH - (vFifoByteCount + 1) * 8) := x"FF";
625: vBuffer(BUFFER_WIDTH - 1 - (vFifoByteCount + 1) * 8 downto BUFFER_WIDTH - (vFifoByteCount + 2) * 8) := x"D9";
626: vFifoByteCount := vFifoByteCount + 2;
627:
628: assert vEoiCount < EOI_FIFO_DEPTH
629: report "jls_framer: EoI FIFO overflow; back-to-back images closer than depth allows"
630: severity failure;
631:
632: vEoiIdxFifo(vEoiCount) := vFifoByteCount - 1;
633: vEoiCount := vEoiCount + 1;
634: end if;
635: end if;
636:
637: assert vFifoByteCount <= BUFFER_BYTES
638: report "jls_framer: payload FIFO overflow (vCount exceeds BUFFER_BYTES; check sizing assumptions or sustained backpressure)"
639: severity failure;
640:
641: sBuffer <= vBuffer;
642: sFifoByteCount <= vFifoByteCount;
643: sEoiFifo <= vEoiIdxFifo;
644: sEoiCount <= vEoiCount;
645: sNextPending <= vNextPending;
646: end if;
647: end if;
648:
649: end process sync_proc;
650:
651: end architecture behavioral;