memory

Modules

ROM #(T, int DEPTH, T[DEPTH] DATA)

module ROM #(T, int DEPTH, T[DEPTH] DATA) {
    action read : int #(FROM: 0, TO: DEPTH) index'0 -> T output_data'1
}
ROM default clock clk read index output_data

RAM #(T, int DEPTH)

module RAM #(T, int DEPTH) {
   domain w_dom
    action write'0 : int#(FROM: 0, TO: DEPTH) write_addr'0, T write_data'0
   domain r_dom
    action read'0 : int#(FROM: 0, TO: DEPTH) read_addr'0 -> T read_data'1
}
RAM w_dom clock clk r_dom write write_addr write_data read read_addr read_data

RAM_Unbalanced #(ElemT, int DEPTH, int WRITE_SIZE, int READ_SIZE)

module RAM_Unbalanced #(ElemT, int DEPTH, int WRITE_SIZE, int READ_SIZE) {
   domain w_dom
    action write'0 : int#(FROM: 0, TO: DEPTH / WRITE_SIZE) write_addr'0, ElemT[WRITE_SIZE] write_data'0
   domain r_dom
    action read'0 : int#(FROM: 0, TO: DEPTH / READ_SIZE) read_addr'0 -> ElemT[READ_SIZE] read_data'1
}
RAM_Unbalanced w_dom clock clk r_dom write write_addr write_data read read_addr read_data

ShiftReg #(T, int LATENCY)

module ShiftReg #(T, int LATENCY) {
    interface ShiftReg : T din'0 -> T dout'LATENCY
}
ShiftReg default clock clk -LATENCY din dout

This shift register is implemented as a memory block with a rotating index.

It is meant to bridge a long latency difference.

We keep one latency register on the inflow, and one on the outflow, as they tend to slot into the primitives we synthesize to.

FIFO #(T, int DEPTH, int MAY_PUSH_LATENCY)

module FIFO #(T, int DEPTH, int MAY_PUSH_LATENCY) {
   domain push_dom
    output bool may_push'-MAY_PUSH_LATENCY
    action push'0 : T push_data'0
   domain pop_dom
    output bool may_pop'0
    action pop'0 : -> T pop_data'1
   domain reset
    action rst
}
FIFO push_dom clock clk pop_dom reset MAY_PUSH_LATENCY MAY_PUSH_LATENCY push push_data may_push pop may_pop pop_data rst

A First-In-First-Out memory element.

Push data in through the may_push, and push connections. If may_push is high, then you’re allowed to perform at most MAY_PUSH_LATENCY more pushes before you must stop. This is because MAY_PUSH_LATENCY translates to the ALMOST_FULL treshold on the FIFO’s internals. In the most common case, may_push has a 1-to-1 correspondence with push, hence

Pop data using the may_pop and pop connections. pop may only be called when may_pop is high. After pop is called, the data is returned, one or more cycles later. You should not depend on pop_data for your decision to pop. If you need to do that, opt for FWFT instead.

Example where we’re moving data from from_fifo, adding one to it with wrap around, and pushing to to_fifo:

module TwoFIFOs {
    FIFO#(T: type int#(FROM: 0, TO: 256), DEPTH: 128) from_fifo
    FIFO#(T: type int#(FROM: 0, TO: 256), DEPTH: 128) to_fifo
 
    when from_fifo.may_pop & to_fifo.may_push {
        int pop_data = from_fifo.pop()
        reg int data_incr = pop_data + 1 mod 256
        // to_fifo.MAY_PUSH_LATENCY is inferred to 2 here.
        // 1 for from_fifo.pop(), and 1 for the `data_incr` reg. 
        to_fifo.push(data_incr)
    }
 
    // not shown here is pushing data into from_fifo and popping it again from to_fifo
}

FWFT #(T, int DEPTH, int MAY_PUSH_LATENCY)

module FWFT #(T, int DEPTH, int MAY_PUSH_LATENCY) {
   domain push_dom
    output bool may_push'-MAY_PUSH_LATENCY
    action push'0 : T push_data'0
   domain pop_dom
    trigger pop_available'0 : T pop_data'0
    action pop'0
   domain reset
    action rst
}
FWFT push_dom clock clk pop_dom reset MAY_PUSH_LATENCY MAY_PUSH_LATENCY push push_data may_push pop pop_available pop_data rst

A First-Word-Fall-Through FIFO. Similar to the regular FIFO, but this one allows for inspection of the data that is ready to be popped before popping it.

Push data in through the may_push, and push connections. If may_push is high, then you’re allowed to perform at most MAY_PUSH_LATENCY more pushes before you must stop. This is because MAY_PUSH_LATENCY translates to the ALMOST_FULL treshold on the FIFO’s internals. In the most common case, may_push has a 1-to-1 correspondence with push, hence

Instead of FIFO’s may_pop interface, FWFT comes with a pop_available trigger, which presents you with the data that you can then pop().

The main usecase of FWFT is for situations where you need a 0-cycle response time to a pop signal.

Below is an of a scenario where we need such a 0-cycle pop latency. The goal is to produce an is_last signal, based on the sequence lengths stored in segment_sizes_fifo Since sequences can be back-to-back, num_left must be refresheable within a single cycle cycle, any number of cycles in a row. Let’s say it will not happen that data_stream is called when no segment is in the fifo for it.

module SplitIntoSegments {
    FWFT#(T: type int#(FROM: 1, TO: 256), DEPTH: 128) segment_sizes_fifo
 
    state int#(FROM: 0, TO: 256) num_left
    bool need_new_num_left = false
    when num_left == 0 {
        need_new_num_left = true
    }
 
    action data_stream : float _data -> bool is_last {
        when num_left == 1 {
            is_last = true
            need_new_num_left = true
        } else {
            is_last = false
        }
        num_left = num_left - 1 mod 256
    }
    when need_new_num_left {
        when segment_sizes_fifo.pop_available: int new_num_left {
            num_left = new_num_left
        }
    }
 
    // not shown here is pushing data into segment_sizes_fifo
}

RippleFIFO #(T, int DEPTH)

module RippleFIFO #(T, int DEPTH) {
   domain push_dom
    output bool may_push'0
    action push'0 : T push_data'0
   domain pop_dom
    output bool may_pop'0
    action pop'0 : -> T pop_data'0
   domain reset
    action rst
}
RippleFIFO push_dom clock clk pop_dom reset push push_data may_push pop may_pop pop_data rst

A FIFO implemented as a sequence of registers with a accompanying valid bit.

Bandwidth limited to 1 element every 2 cycles.

Use for very small FIFOs, in bandwidth-limited circumstances.