DRAM FIFO

ENTITY DRAM_FIFO IS

FIFO logic built around an external DDR memory using the Avalon Memory-Mapped (AVMM) interface. Data is written to memory one beat at a time. The first beat of each N packets is the packet length metadata beat, followed by the packet payload beats. Write addresses increment manually after each accepted beat.

Generics

Generic

Type

Default

Description

DDR_ADDR_WIDTH

natural

27

Width of AVMM_ADDRESS. Set by the card’s memory controller.

DDR_BURST_WIDTH

natural

7

Width of AVMM_BURSTCOUNT. Transactions are single-beat, so the output is tied to 1.

DDR_DATA_WIDTH

natural

512

Width of the AVMM data path, and therefore of one ring item.

AXI_DATA_WIDTH

natural

512

Width of both AXI-Stream ports.

FIFO_ITEMS

natural

2 ** 13

Depth in DDR words. Must not exceed the memory behind the AVMM port.

PKT_MTU

natural

2 ** 12

Largest packet in bytes (4 KB). Sets LEN_WIDTH = log2(PKT_MTU+1) and the width of TX_PKT_LEN. A frame larger than this has its stored length wrapped, which corrupts every later packet in the metaword.

DEVICE

string

“AGILEX”

Target device string, passed to the FIFO primitives.

FIFO_BUFF_IN_ITEMS

natural

2**16

Depth of the on-chip write buffer. An assert checks that at least one MTU packet fits.

FIFO_BUFF_OUT_ITEMS

natural

2**12

Depth of the read prefetch buffer. An assert checks that at least one MTU packet fits.

Ports

Port

Type

Mode

Description

=====

FIFO interface

=====

=====

CLK

std_logic

in

RESET

std_logic

in

=====

Write interface

=====

=====

RX_AXI_DATA

std_logic_vector(AXI_DATA_WIDTH - 1 downto 0)

in

RX_AXI_KEEP

std_logic_vector(AXI_DATA_WIDTH/8 - 1 downto 0)

in

RX_AXI_LAST

std_logic

in

RX_AXI_VALID

std_logic

in

RX_AXI_READY

std_logic

out

=====

Read interface

=====

=====

TX_AXI_DATA

std_logic_vector(AXI_DATA_WIDTH - 1 downto 0)

out

TX_AXI_KEEP

std_logic_vector(AXI_DATA_WIDTH/8 - 1 downto 0)

out

TX_AXI_LAST

std_logic

out

TX_AXI_VALID

std_logic

out

TX_AXI_READY

std_logic

in

TX_PKT_LEN

std_logic_vector(log2(PKT_MTU+1)-1 downto 0)

out

Packet length output, valid with first payload beat (TX_AXI_VALID=1 and SOF)

=====

DRAM interface

=====

=====

AVMM_ADDRESS

std_logic_vector(DDR_ADDR_WIDTH - 1 downto 0)

out

Request interface

AVMM_BURSTCOUNT

std_logic_vector(DDR_BURST_WIDTH - 1 downto 0)

out

AVMM_WRITE

std_logic

out

AVMM_WRITEDATA

std_logic_vector(DDR_DATA_WIDTH - 1 downto 0)

out

AVMM_READ

std_logic

out

AVMM_READY

std_logic

in

AVMM_READDATA

std_logic_vector(DDR_DATA_WIDTH - 1 downto 0)

in

Response interface

AVMM_READDATAVALID

std_logic

in

DDR_FULL

std_logic

out

Low when another word of FIFO_ITEMS b can be written.

DDR_READ_EN

std_logic

in

Set by user - when high reading via the AVMM_* interface is enabled

See the DRAM Packet Capture application for how this component is used and sized.

Structure

The write side is three stages:

  • AXIS_PACKET_LEN measures each incoming packet and emits its length.

  • fifo_buffer_in (AXIS_FIFO, FIFO_BUFF_IN_ITEMS deep) holds the payload beats while the length is being packed.

  • a shift register packs up to META_LEN_COUNT lengths into one metaword, which is pushed into meta_fifo (FIFOX, 1024 entries).

The read side is two stages: fifo_buffer_out (FIFOX, FIFO_BUFF_OUT_ITEMS deep) prefetches DDR words, and a two-state formatting FSM turns them back into AXI-Stream packets.

Metaword format

The structure of a metaword depends entirely on PKT_MTU. The following example uses PKT_MTU = 4096 B

+---------+--------+---------+-----+--------+
| PADDING | COUNT  |  P[37]  | ... |  P[0]  |
|   12b   |   6b   |   13b   |     |  13b   |
+---------+--------+---------+-----+--------+
 MSB                                     LSB

Note

The field widths are derived, not fixed.

Constant

Derivation

Default

LEN_WIDTH

log2(PKT_MTU + 1)

13

META_COUNT_BITS

log2(DDR_DATA_WIDTH / LEN_WIDTH + 1)

6

META_LEN_COUNT

(DDR_DATA_WIDTH - META_COUNT_BITS) / LEN_WIDTH

38

META_UNUSED_BITS

remainder

12

COUNT says how many of the length slots are valid, so a partial metaword is legal — which is what makes the flush below safe.

Write path

A packet is written to DDR only when both halves are ready: fifo_buffer_in holds at least one complete packet and meta_fifo holds a metaword describing it.

Each burst to memory is the metaword beat first, then that group’s payload beats. metaword_sent selects between meta_fifo_do and fifo_tdata on AVMM_WRITEDATA, and the payload FIFO is popped only during payload beats.

Metaword flush

A metaword is normally emitted once all 38 slots are filled. Waiting for a 38th packet that may never arrive would strand the previous 37 in on-chip memory, so flush_timer counts cycles since the last new length and forces out a partial word after FLUSH_TIMEOUT (1024) idle cycles.

Read path

Prefetching is credit-based. out_fifo_credits starts at FIFO_BUFF_OUT_ITEMS, decrements when a read is issued and increments when a beat is popped.

A read is issued only when all three hold:

  • diff > outstanding_read_cnt — there is unread data in DDR not already requested.

  • out_fifo_credits > 1 — there is somewhere to put the result.

  • DDR_READ_EN = '1'.

The formatting FSM has two states:

FMT_META

Pops a metaword, extracts COUNT and the length array, loads the first length, and moves to FMT_PAYLOAD.

FMT_PAYLOAD

Emits payload beats. While more than one beat remains, TX_AXI_KEEP is all ones. On the final beat TX_AXI_LAST asserts and ones_mask() builds a partial TX_AXI_KEEP from the bytes left. State advances only on an AXI handshake. When the last packet of the group completes it returns to FMT_META.

Ring accounting and flow control

write_ptr and read_ptr are one bit wider than the address space. The extra bit is a wrap flag, so diff = write_ptr - read_ptr distinguishes full from empty:

can_write <= '1' when diff < (FIFO_ITEMS - 1) else '0';
can_read  <= '1' when diff > 0 else '0';
DDR_FULL  <= not can_write;

Pointers advance on accepted AVMM transactions (AVMM_WRITE and AVMM_READY), not on request assertion. Both pointers are cleared only by RESET; DDR_READ_EN and DDR_FULL never move them, so data left in the ring is read out before anything written later.

AVMM arbitration

One AVMM port serves both directions, so writes and reads are arbitrated:

grant_write <= lock_is_write     when req_lock = '1' else write_wants;
grant_read  <= not lock_is_write when req_lock = '1' else read_wants and not write_wants;

Writes win. A read is granted only when no write is pending, which maximizes RX throughput.

req_lock holds a grant until the memory accepts it. Without it, a request could switch direction mid-transaction while AVMM_READY was still low (AVMM protocol violation), and the address would change under an in-flight request.

Sizing constraints

Two elaboration-time assertions enforce that both buffers hold at least one full MTU packet:

assert FIFO_BUFF_IN_ITEMS  >= PKT_MTU/(AXI_DATA_WIDTH/8)
assert FIFO_BUFF_OUT_ITEMS >= PKT_MTU/(AXI_DATA_WIDTH/8)

Note

FIFO_ITEMS must not exceed the memory actually behind the AVMM port. The address is truncated, so an oversized ring aliases onto itself and overwrites unread data. In this application the depth is derived from MEM_TARGET; see Memory interface.

Simulation

The component has its own cocotb testbench in cocotb/, which drives the AXI ports directly and answers the AVMM port with a memory model, so no card or application core is involved.

cd apps/dram_pkt_capture/comp/dram_fifo/cocotb
make

run_test sends 10000 random frames of 100–4096 B with DDR_READ_EN tied high, and checks that what comes out of the read port matches what went in. Generics are left at their entity defaults, so PKT_MTU is 4096 — exactly the largest frame the test generates.

For the whole capture path end to end, see Top-level simulation.