Functional Coverage Patterns – FIFO

Purpose

The goal of this document is to provide an overview of the main functional coverage items that must be defined for a FIFO. This document may serve as a starting point for any functional verification engineer who needs to verify a FIFO.

Table of Contents

What is a FIFO?

FIFO is an acronym for first-in-first-out, meaning that the data written into the buffer first also comes out of it first. The role played by a FIFO, whatever the chosen implementation, is to mediate between producer and consumer, as the diagram below shows:

Fig. 1 Producer-Consumer FIFO

Some applications of FIFOs include:

  • Crossing clock/reset domains
  • Buffering data for software applications
  • Storing data for later processing

FIFOs can be categorized according to the following criteria:

  • Data storage: data is stored by flip flops or by a RAM (single port or dual port memories)
  • Synchronicity: Synchronous FIFOs (i.e. both read and write ports are clocked by the same clock signal) and Asynchronous FIFOs (i.e. write and read operations are clocked by different clock signals)
  • Design implementation architecture: delay line (i.e. storage is achieved by a chain of flip flops connected serially) and “circular” FIFOs (e.g. uses a [S]RAM)

To understand better how a FIFO works, this post will present an example FIFO implementation. The implementation of a FIFO can vary depending on the application where it is used. The image below depicts a generic set of FIFO interfaces:

Fig. 2 Generic FIFO interfaces

FIFO interfaces can be grouped into 4 main categories:

  • Write interface
  • Read interface
  • Status interface
  • Clk and reset interface

The write interface provides the following signals:

  • wr_en – the producer asserts this control signal to indicate valid write data
  • wr_data – data signal validated by the wr_en signal; the data bus width is parameterizable within 1..N bits

The read interface provides the following signals:

  • rd_en – the consumer asserts this control signal to indicate valid read data
  • rd_data – data signal validated by the rd_en signal; the data bus width is parameterizable within 1..M bits

The FIFO has some status signals that indicate its fill level at any given moment. An example of how the status signals work is shown in the below image.

Fig. 3 FIFO status signals during different operations

The status interface provides the following signals:

  • empty – the status signal used to mark the FIFO does not contain any valid data
  • full – the status signal used to indicate that all FIFO positions are used and that there is no more space available for any more data
  • alm_empty (almost empty) – asserted when the fill level of the FIFO is between empty and alm_empty_thresh
  • alm_full (almost full) – asserted when the fill level of the FIFO is between FIFO_depth and (FIFO_depth – alm_full_thresh)
  • alm_empty_thresh (almost empty threshold) – used to configure how many spaces need to be filled in order for the alm_empty signal to be asserted
  • alm_full_thresh (almost full threshold) – used to configure how many spaces need to be empty in order for the alm_full signal to be asserted

Functional Verification of FIFOs as DUT

FIFOs can be verified using various different methods – formal, functional or SVAs – or a combination of methods. In this post we focus on functional verification.

What to verify

The main features of the FIFO to be verified are:

  • Capacity to store packets until they are read out by the consumer
  • Possibility of a reset for the FIFO during operation
  • Indication of fill level through flags depending on the implementation (i.e. full, empty, alm_full, alm_empty)
  • Configuration of fill level thresholds depending on the implementation (i.e. alm_full, alm_empty)
  • Capacity to ignore write operations when the FIFO is full
  • Capacity to ignore read operations when the FIFO is empty
  • Indication of overflow/underflow if the FIFO supports these features
  • Performance: ability not to block for maximum throughput writes and reads

The following mandatory checks should be performed:

  • Correct reset fill level of FIFO
  • FIFOs status signals (i.e. empty, alm_empty, alm_full, full) are asserted depending on the FIFO fill level
  • The data that are read must be exactly the same as the data that were written into the FIFO and in the same order
  • Check that when read and write operations are performed simultaneously, the throughput has the expected value
  • Check that all output signals from the FIFO have no X values when expected. This check is enabled only if non-X data is written to the input interface. In some instances, it is necessary to write something in order for that memory to output non-X values

Functional Coverage

The functional coverage items presented in this post were defined based on our example FIFO implementation. As already mentioned, a FIFO can be implemented in many different ways, meaning that some of the coverage items may not apply for all implementations or may need some minor adjustments.
A default list of functional coverage items that could be defined for our FIFO is given below. The coverage items are categorized according to each functionality of the FIFO.

Write interface

The write interface of the FIFO contains the following signals: wr_en and wr_data.

Covergroup: Write data

  • The wr_data bus is parameterized to N bits width. For a data bus, we want to see that we have used the width of the bus as much as possible. To achieve this we can use one of the following bitwise coverage methods that were already described in a previous article.

    Sampling point: When a write operation is performed (i.e. wr_en == 1)

For the write interface we are looking to see different behaviors for the wr_en signal, such as: the distance between two consecutive writes to the FIFO, or the number of back-to-back writes to the FIFO. We can use the following coverage definitions to verify these behaviors. To define the bins for the below covergroup, we will use our own typedef:

typedef enum {
_0 = 0, _1_TO_10 = 1, _11_TO_50 = 2, _51_TO_MAX = 3
} distance_intervals_t;

Covergroup: Write enable distance

  • covergroup wr_en_distance_cg with function sample (distance_intervals_t distance_in_clk_cycles);
        distance_between_2_consecutive_writes: coverpoint distance_in_clk_cycles {
            bins \0 = {_0};
            bins \1..10 = {_1_TO_10};
            bins \11..50 = {_11_TO_50};
            bins \51_$ = {_51_TO_MAX};
        }
        distance_between_2_consecutive_writes_transition: coverpoint distance_in_clk_cycles {
            bins trans[] = (0, _1_TO_10, _11_TO_50, _51_TO_MAX => 0, _1_TO_10, _11_TO_50, _51_TO_MAX);
        }
    endgroup

    Sampling point: @(posedge wr_clk) and wr_en == 1, starting with the second wr_en == 1 during the simulation

Covergroup: Write enable back2back

  • covergroup wr_en_back2back_cg (int fifo_depth) with function sample (int back2back_writes);
        nof_back_to_back_writes_cp: coverpoint back2back_writes {
            bins values[] = {[2 : fifo_depth]};
        }
    endgroup

    Sampling point: @(negedge wr_en)

Read interface

The read interface of our FIFO is similar to the write interface, so the coverage items defined for it will be the same apart from a few minor changes, as follows:

  • rd_data_cg will have different bins for its coverpoint depending on the M width of the rd_data bus. The sampling point will be updated to: when rd_en is active (i.e. rd_en == 1)
  • rd_en_distance_cg will have a different sampling point: @(posedge clk) and rd_en == 1, starting with the second wr_en == 1 during the simulation
  • rd_en_back2back_cg will have a different sampling point: @(negedge rd_en)

Status interface

The status interface contains the following signals: empty, alm_empty, alm_full, full, alm_empty_thresh, alm_full_thresh.
The thresholds of the alm_empty and alm_full can be computed/generated in different ways:

  • Pre-set levels: levels 1, 2, and 3;
    • level 1: full=alm_full, empty=alm_emtpy;
    • level 2: alm_full=full – X entries, alm_empty = empty + X entries;
    • level 3: alm_full = alm_empty
  • User selected: number of free/used entries until full/empty
  • Relative to the FIFO depth: almost full/empty thresholds computed relative to Z% of the FIFO depth

The alm_empty_thresh and alm_full_thresh are parameterized to P bits width. For these thresholds we want to see the different values we are using in our verification tests. The coverage items for these thresholds will look like this:

Covergroup: Almost empty threshold

  • covergroup alm_empty_threshold_cg with function sample (bit[P-1 : 0 ] threshold);
        threshold_val: coverpoint threshold {
            bins min = {0};
            bins middle[P] = {[ 1 : 2**(P) - 2]};
            bins max = {2**(P) - 1};
        }
    endgroup

    Sampling point: @(posedge alm_empty) if the threshold value has changed

Covergroup: Almost full threshold

  • For the alm_full_thresh signal, the functional coverage to be defined is similar to the previous one defined for alm_empty_thresh. The only things that differs are:

    • Bin values if the width of the thresholds are different
    • Sampling point must be changed to: At first posedge of alm_full signal

    For the status interface of our FIFO, we also need to check that we had all the status signals active during our verification. The following coverage items can be used to verify that these scenarios occurred during our simulations.

Covergroup: Empty values

  • covergroup empty_cg with function sample (bit empty, bit alm_empty);
        empty_val: coverpoint empty {
            bins \0 = {0};
            bins \1 = {1};
        }
        alm_empty_val: coverpoint alm_empty {
            bins \0 = {0};
            bins \1 = {1};
        }
    endgroup

    Sampling point: @(posedge clk)

Covergroup: Full values

  • The covergroup for the two other status signals relating to the full status (i.e. full and alm_full) is similar to the one described above for the empty and alm_empty signal.

    Sampling point: @(posedge clk)

FIFO block

One important scenario that can be covered with our FIFO involves checking to see if it is possible to have both read and write operations at the same time. To do this we will define two cover groups: one used to see that we have a write with a simultaneous read, and another to see that we had a read with a simultaneous write. The two cover groups are identical, the only difference will be the sampling point.

Covergroup: Write with reads

  • covergroup wr_with_rd_cg with function sample (bit wr_en, bit rd_en);
        wr_en_val: coverpoint wr_en {
           bins \0 = {0};
           bins \1 = {1};
        }
        rd_en_val: coverpoint rd_en {
           bins \0 = {0};
           bins \1 = {1};
        }
        wr_en_x_rd_en: cross wr_en_val, rd_en_val;
    endgroup

    Sampling point: When a write is performed (i.e. wr_en == 1)

Covergroup: Read with writes

  • The other cover group, rd_with_wr_cg, will be similar to the one above with the corresponding adjustments to the enable signal used for read.

    Sampling point: When a read is performed (i.e. rd_en == 1)

In addition to the previous covergroups, we are also looking for different combinations between consecutive writes and the number of reads during this time period. We can use the below covergroups to check for these combinations.

Covergroup: Write enable back2back with simultaneous reads

  • covergroup wr_en_back2back_with_simultaneous_reads_cg (int wr_fifo_depth, int rd_fifo_depth) with function  sample (int back2back_writes, int nof_reads);
        nof_back_to_back_writes: coverpoint back2back_writes {
            bins values[] = {[2 : wr_fifo_depth]};
        }
        nof_reads_val: coverpoint nof_reads{
            bins \0..1 = {[0:1]};
            bins values[] = {[2 : rd_fifo_depth]};
        }
        nof_back_to_back_writes_x_nof_reads_val: cross nof_back_to_back_writes, nof_reads_val;
    endgroup

    Sampling point: @(negedge wr_en)

Covergroup: Read enable back2back with simultaneous writes

  • A similar covergroup can be created for the relationship between consecutive reads and the number of writes during these read operations.

    Sampling point: @(negedge rd_en)

Covergroup: Almost empty and almost full thresholds cross

  • If the status interface of the FIFO is equipped with both alm_full and alm_empty signals and the conditions for both signals to be active individually are parameterized within a threshold, then we would expect to see different combinations between the two thresholds with the following coverage:

    covergroup alm_empty_x_alm_full_thresholds_cg with function sample (bit[P-1 : 0] alm_empty_threshold, bit[P-1 : 0] alm_full_threshold);
        alm_empty_threshold_val: coverpoint alm_empty_threshold {
            bins min = {0};
            bins middle[P] = {[ 1 : 2**(P) - 2]};
            bins max = {2**(P) - 1};
        }
        alm_full_threshold_val: coverpoint alm_full_threshold {
            bins min = {0};
            bins middle[P] = {[ 1 : 2**(P) - 2]};
            bins max = {2**(P) - 1};
        }
        empty_thresh_x_full_thresh: cross alm_empty_threshold_val, alm_full_threshold_val
    endgroup

    Sampling point: @(posedge alm_full or posedge alm_empty) and one threshold value has changed

Covergroup: Overflow

  • The overflow/underflow feature, if supported by the FIFO, needs to be tested using directed scenarios. We can add a coverage item to make sure that we have tested this feature. We consider that we have an overflow when the full signal is asserted and we have one more write towards the FIFO. For the underflow scenario, we consider this to have occurred when the empty signal is asserted and we try to read one more packet from the FIFO. The coverage items are presented below:

    covergroup overflow_cg with function sample (bit overflow);
        overflow_val: coverpoint overflow {
            bins \0 = {0};
            bins \1 = {1};
        }
    endgroup

    Sampling point: @(posedge wr_en)

Covergroup: Underflow

  • covergroup underflow_cg with function sample (bit underflow);
        underflow_val: coverpoint underflow {
            bins \0 = {0};
            bins \1 = {1};
        }
    endgroup

    Sampling point: @(posedge rd_en)

With a FIFO, it is important to cover the relationship between the signals used to validate the write/read operations and the signals used to indicate the fill level. To cover this relationship, we can use the below covergroups, one for each operation.

Covergroup: FIFO fill level status on write

  • covergroup fill_level_status_on_write_cg with function sample (bit empty, bit alm_empty, bit alm_full, bit full);
        empty_val: coverpoint empty {
            bins \0 = {0};
            bins \1 = {1};
        }
        alm_empty_val: coverpoint alm_empty {
            bins \0 = {0};
            bins \1 = {1};
        }
        alm_full_val: coverpoint alm_full {
            bins \0 = {0};
            bins \1 = {1};
        }
        full_val: coverpoint full{
            bins \0 = {0};
            bins \1 = {1};
        }
        empty_val_x_alm_empty_val_x_alm_full_val_x_full_val: cross empty_val, alm_empty_val, alm_full_val, full_val {
            ignore_bins full_and_empty = empty_val_x_alm_empty_val_x_alm_full_val_x_full_val with (empty_val == 1 && full_val == 1);
            ignore_bins empty_without_alm_empty = empty_val_x_alm_empty_val_x_alm_full_val_x_full_val with (empty_val == 1 && alm_empty_val == 0);
            ignore_bins full_without_alm_full = empty_val_x_alm_empty_val_x_alm_full_val_x_full_val with (full_val == 1 && alm_full_val == 0);
        }
    endgroup 

    Sampling point: When a write is performed (i.e. wr_en == 1)

Covergroup: FIFO fill level status on read

  • The covergroup used for the read operation (i.e fill_level_status_on_read_cg) will look similar to the one above. The signal used to validate the write operations is replaced with the one used for the read operations.

    Sampling point: When a read is performed (i.e. rd_en == 1)

SystemVerilog Assertions

SystemVerilog Assertions (SVA) are a good way to check behavior and can be adapted for functional verification, formal verification, directed testing verification, etc. Below I give a few examples of SVA properties that can be used in functional verification as checks or in formal verification as properties to be proofed.

Like the functional coverage items defined above, the SVAs presented here were developed based on our FIFO implementation. They should be used/adapted according to the specific FIFO implementation chosen by the verifier.

  • A property used to check the X/Z values of a signal:
    property AMIQ_FIFO_CHECK_FOR_X_Z_VALUES_PROPERTY(logic signal);
        @(posedge clk) disable iff (!rst_n || !has_checks)
            !$isunknown(signal);
    endproperty
  • A property used to check the X/Z values of a signal when only a control signal is active:
    property AMIQ_FIFO_CHECK_FOR_DATA_BUS_X_Z_VALUES_PROPERTY(logic control_signal, logic signal);
        @(posedge clk) disable iff (!rst_n || !has_checks|| disable_unknown_data_check || !check_x_on_output)
            control_signal -> !$isunknown(signal);
    endproperty
  • A property used to check that empty and full signals are not asserted at the same time:
    property AMIQ_FIFO_NOT_FULL_AND_EMPTY_PROPERTY
        @(posedge clk) disable iff(!rst_n || !has_checks|| N > MEM_SIZE)
            !(full && empty);
    endproperty
  • A property used to check the values of the status signals when reset is asserted:
    property AMIQ_FIFO_VALUE_AFTER_RESET_PROPERTY(logic reset_signal);
            @(posedge clk) disable iff (!has_checks) $fell(reset_signal) |->
                empty === 1 && alm_empty === 1 && alm_full === 0 && full === 0;
        endproperty
  • Properties used to check the correctness of the empty signal behavior:
    property AMIQ_FIFO_EMPTY_ASSERTED_1_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            empty === 1 |-> fill < M;
    endproperty
    property AMIQ_FIFO_EMPTY_ASSERTED_2_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            fill < M |-> empty === 1;
    endproperty
  • Properties used to check the correctness of the alm_empty signal behavior:
    property AMIQ_FIFO_ALM_EMPTY_ASSERTED_1_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            alm_empty === 1 |-> fill <= M * $past(alm_empty_thresh);
    endproperty
    property AMIQ_FIFO_ALM_EMPTY_ASSERTED_2_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            fill <= M * $past(alm_empty_thresh) |-> alm_empty === 1;
    endproperty
  • Properties used to check the correctness of the alm_full signal behavior:
    property AMIQ_FIFO_ALM_FULL_ASSERTED_1_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            alm_full === 1 |-> fill >= N * (FIFO_SIZE - $past(alm_full_thresh));
    endproperty
    property AMIQ_FIFO_ALM_FULL_ASSERTED_2_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            fill >= N * (FIFO_SIZE - $past(alm_full_thresh)) |-> alm_full === 1;
    endproperty
  • Properties used to check the correctness of the full signal behavior:
    property AMIQ_FIFO_FULL_ASSERTED_1_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            full === 1 |-> fill + N > MEM_SIZE;
    endproperty
    property AMIQ_FIFO_FULL_ASSERTED_2_PROPERTY();
        @(posedge clk) disable iff (!rst_n || !has_checks)
            fill + N > MEM_SIZE |-> full === 1;
    endproperty

You can download the module containing all of these properties from our GitHub repo.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Subscribe to our newsletter

Do you want to be up to date with our latest articles?