fpga: lib: Add axis_pkt_throttle.sv
Original-commit: f8f93ced23cf3ce0a3ba4339e7fa4ab0c598654d
This commit is contained in:
@@ -34,4 +34,5 @@ axis_downsizer.v \
|
|||||||
axis_width_conv.v \
|
axis_width_conv.v \
|
||||||
axis_split.v \
|
axis_split.v \
|
||||||
axis_packetize.v \
|
axis_packetize.v \
|
||||||
|
axis_pkt_throttle.sv \
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: axis_pkt_throttle
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// This module takes in AXI-Stream and outputs the same stream, inserting
|
||||||
|
// gaps between packets in order to maintain a specific data rate. The amount
|
||||||
|
// of time between packets is controlled in such a way that the length of a
|
||||||
|
// packet divided by the time between the start of that packet and the next
|
||||||
|
// does not exceed some rate R.
|
||||||
|
//
|
||||||
|
// This module does NOT insert stalls within a packet, only between packets,
|
||||||
|
// so the peak data rate is not restricted and packet contiguity is not
|
||||||
|
// affected. Also, the average data rate could be slightly higher than the
|
||||||
|
// configured rate due to rounding error (within a single clock cycle per
|
||||||
|
// packet).
|
||||||
|
//
|
||||||
|
// The "throttle" input port controls the rate. In order to set the rate to
|
||||||
|
// R, where R is a fraction in the range (0,1], set throttle (T) using the
|
||||||
|
// formula T = (1/R)-1. In other words, R = 1/(T+1).
|
||||||
|
//
|
||||||
|
// The "throttle" input is represented as an unsigned fixed-point value with
|
||||||
|
// THROTTLE_W/2 whole bits and THROTTLE_W/2 fractional bits (UQn.n format).
|
||||||
|
// Throttle is therefore in the range [0,1).
|
||||||
|
//
|
||||||
|
// For example, if THROTTLE_W is 8 bits then throttle is in UQ4.4 format (4
|
||||||
|
// whole bits and 4 fractional bits). In this case, a throttle value of 0
|
||||||
|
// corresponds to a rate of 1/(0+1) = 1.0, which is 100% or full throttle. A
|
||||||
|
// throttle value of 15.9375 (i.e., 0xFF, or the max value) corresponds to a
|
||||||
|
// rate of 1/(15.9375+1) = 0.05904059, or 5.9% of the maximum rate.
|
||||||
|
//
|
||||||
|
// The throttle input is sampled between packets. Changing the throttle
|
||||||
|
// during a packet, or before its inserted stall time has elapsed, has no
|
||||||
|
// effect until the next packet.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
//
|
||||||
|
// THROTTLE_W : Width of the throttle input in bits.
|
||||||
|
// DATA_W : Width of data bus for AXI-Stream in bits.
|
||||||
|
// MTU : The maximum supported packet length is 2**MTU.
|
||||||
|
//
|
||||||
|
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
|
||||||
|
module axis_pkt_throttle #(
|
||||||
|
parameter int THROTTLE_W = 8,
|
||||||
|
parameter int DATA_W = 64,
|
||||||
|
parameter int MTU = 10
|
||||||
|
) (
|
||||||
|
input wire clk,
|
||||||
|
input wire rst,
|
||||||
|
|
||||||
|
input wire [THROTTLE_W-1:0] throttle,
|
||||||
|
|
||||||
|
// Input AXI-Stream
|
||||||
|
input wire [ DATA_W-1:0] i_tdata,
|
||||||
|
input wire i_tlast,
|
||||||
|
input wire i_tvalid,
|
||||||
|
output wire i_tready,
|
||||||
|
|
||||||
|
// Output AXI-Stream
|
||||||
|
output wire [ DATA_W-1:0] o_tdata,
|
||||||
|
output wire o_tlast,
|
||||||
|
output wire o_tvalid,
|
||||||
|
input wire o_tready
|
||||||
|
);
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Throttle Control Logic
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// This logic monitors the data flow and determines when we should pass data
|
||||||
|
// through and when we should stall in order to limit the data rate.
|
||||||
|
//
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Length of the fractional part of our fixed-point throttle and count.
|
||||||
|
localparam int FRAC_W = THROTTLE_W/2;
|
||||||
|
// Length of the whole-number part of our fixed-point throttle.
|
||||||
|
localparam int WHOLE_W = THROTTLE_W - FRAC_W;
|
||||||
|
// Width of an unsigned fixed-point value to track the amount of time to
|
||||||
|
// ensure that we have between the starts of packets. This must be large
|
||||||
|
// enough to store (2**MTU) * (2**THROTTLE_W-1).
|
||||||
|
localparam int TIME_W = MTU + THROTTLE_W;
|
||||||
|
// Width of an unsigned counter to track time between packets. Same as
|
||||||
|
// TIME_W, but the whole-number part.
|
||||||
|
localparam int COUNT_W = TIME_W - FRAC_W;
|
||||||
|
// Compute the minimum count value we can have and still guarantee that the
|
||||||
|
// next stall time adjustment won't cause underflow.
|
||||||
|
localparam longint MIN_COUNT = -(2**TIME_W) + (2**THROTTLE_W-1);
|
||||||
|
|
||||||
|
// Fixed-point accumulator that tracks amount of time to stall between
|
||||||
|
// packets. We add an extra bit for the sign since this value can be negative.
|
||||||
|
logic signed [TIME_W:0] stall_time;
|
||||||
|
// Counter to track the whole number of clock cycles to stall.
|
||||||
|
logic [COUNT_W-1:0] wait_count;
|
||||||
|
// Flag to indicate if underflow occurred and our count can't be trusted.
|
||||||
|
logic underflow;
|
||||||
|
// Register to control the flow of packets through this module. When 1, data
|
||||||
|
// flow is gated (stopped).
|
||||||
|
logic gate = 1'b0;
|
||||||
|
// Start of packet flag.
|
||||||
|
logic sop = 1'b1;
|
||||||
|
|
||||||
|
always_ff @(posedge clk) begin : throttle_control
|
||||||
|
if (gate) begin
|
||||||
|
wait_count <= wait_count-1;
|
||||||
|
gate <= (wait_count > 1);
|
||||||
|
// Update stall_time for next packet, in case throttle changes.
|
||||||
|
stall_time <= throttle;
|
||||||
|
end else begin
|
||||||
|
if (i_tvalid && o_tready) begin
|
||||||
|
if (i_tlast) begin
|
||||||
|
sop <= 1;
|
||||||
|
// End of the packet. Start stalling, if needed, and reset for the
|
||||||
|
// next packet.
|
||||||
|
if (!underflow && !stall_time[TIME_W]) begin
|
||||||
|
// No underflow and stall_time is non-negative, so start stalling
|
||||||
|
// the accumulated amount.
|
||||||
|
wait_count <= stall_time[FRAC_W+:COUNT_W];
|
||||||
|
gate <= (stall_time[FRAC_W+:COUNT_W] != 0);
|
||||||
|
end else begin
|
||||||
|
// We underflowed or stall_time was negative, so don't stall.
|
||||||
|
wait_count <= 0;
|
||||||
|
gate <= 0;
|
||||||
|
end
|
||||||
|
// Reset for next packet
|
||||||
|
underflow <= 0;
|
||||||
|
stall_time <= throttle;
|
||||||
|
end else begin
|
||||||
|
// A transfer is happening this cycle. Update stall time. Note that
|
||||||
|
// overflow is not possible as long as the MTU is honored.
|
||||||
|
stall_time <= stall_time + throttle;
|
||||||
|
sop <= 0;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
if (sop) begin
|
||||||
|
// We're in between packets. Update stall_time for next packet, in
|
||||||
|
// case throttle changes.
|
||||||
|
stall_time <= throttle;
|
||||||
|
end else begin
|
||||||
|
// An idle cycle (no transfer) is occurring this cycle so subtract
|
||||||
|
// 1.0 from our stall time. We must check for underflow since there
|
||||||
|
// is no limit to the number of idle cycles we might see.
|
||||||
|
stall_time <= stall_time - (1 << FRAC_W);
|
||||||
|
if (stall_time < MIN_COUNT) begin
|
||||||
|
underflow <= 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if (rst) begin
|
||||||
|
sop <= 1;
|
||||||
|
stall_time <= 0;
|
||||||
|
wait_count <= 'X; // Don't care
|
||||||
|
gate <= 0;
|
||||||
|
underflow <= 0;
|
||||||
|
end
|
||||||
|
end : throttle_control
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Data Pass-Through
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
assign o_tdata = i_tdata;
|
||||||
|
assign o_tlast = i_tlast;
|
||||||
|
assign o_tvalid = i_tvalid & ~gate;
|
||||||
|
assign i_tready = o_tready & ~gate;
|
||||||
|
|
||||||
|
endmodule : axis_pkt_throttle
|
||||||
|
|
||||||
|
|
||||||
|
`default_nettype wire
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#
|
||||||
|
# Copyright 2023 Ettus Research, a National Instruments Brand
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
#
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Top-of-Makefile
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Define BASE_DIR to point to the "top" dir
|
||||||
|
BASE_DIR = $(abspath ../../../top)
|
||||||
|
# Include viv_sim_preamble after defining BASE_DIR
|
||||||
|
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Design Specific
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Include makefiles and sources for the DUT and its dependencies
|
||||||
|
|
||||||
|
DESIGN_SRCS += $(abspath \
|
||||||
|
$(abspath ../../axi/axis_pkt_throttle.sv) \
|
||||||
|
)
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Testbench Specific
|
||||||
|
#-------------------------------------------------
|
||||||
|
SIM_TOP = axis_pkt_throttle_tb
|
||||||
|
|
||||||
|
SIM_SRCS = \
|
||||||
|
$(abspath axis_pkt_throttle_tb.sv) \
|
||||||
|
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Bottom-of-Makefile
|
||||||
|
#-------------------------------------------------
|
||||||
|
# Include all simulator specific makefiles here
|
||||||
|
# Each should define a unique target to simulate
|
||||||
|
# e.g. xsim, vsim, etc and a common "clean" target
|
||||||
|
include $(BASE_DIR)/../tools/make/viv_simulator.mak
|
||||||
@@ -0,0 +1,371 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2023 Ettus Research, a National Instruments Brand
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
//
|
||||||
|
// Module: align_samples_tb
|
||||||
|
//
|
||||||
|
// Description:
|
||||||
|
//
|
||||||
|
// Testbench for axis_pkt_throttle.
|
||||||
|
//
|
||||||
|
|
||||||
|
`default_nettype none
|
||||||
|
|
||||||
|
|
||||||
|
module axis_pkt_throttle_tb ();
|
||||||
|
|
||||||
|
// Include macros and time declarations for use with PkgTestExec
|
||||||
|
`include "test_exec.svh"
|
||||||
|
import PkgTestExec::*;
|
||||||
|
import PkgRandom::*;
|
||||||
|
|
||||||
|
localparam real CLK_PERIOD = 10.0;
|
||||||
|
localparam int THROTTLE_W = 8;
|
||||||
|
localparam int DATA_W = 32;
|
||||||
|
localparam int MTU = 4;
|
||||||
|
localparam int MAX_PKT_LEN = 2**MTU;
|
||||||
|
localparam int NUM_PACKETS = 100;
|
||||||
|
|
||||||
|
localparam int FRAC_W = THROTTLE_W/2;
|
||||||
|
localparam int WHOLE_W = THROTTLE_W - FRAC_W;
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Clocks and Resets
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
bit clk;
|
||||||
|
bit rst;
|
||||||
|
|
||||||
|
sim_clock_gen #(.PERIOD(CLK_PERIOD))
|
||||||
|
clk_gen (.clk(clk), .rst(rst));
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Device Under Test (DUT)
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
logic [THROTTLE_W-1:0] throttle;
|
||||||
|
|
||||||
|
logic [DATA_W-1:0] i_tdata;
|
||||||
|
logic i_tlast;
|
||||||
|
logic i_tvalid;
|
||||||
|
logic i_tready;
|
||||||
|
|
||||||
|
logic [DATA_W-1:0] o_tdata;
|
||||||
|
logic o_tlast;
|
||||||
|
logic o_tvalid;
|
||||||
|
logic o_tready;
|
||||||
|
|
||||||
|
axis_pkt_throttle #(
|
||||||
|
.THROTTLE_W(THROTTLE_W),
|
||||||
|
.DATA_W (DATA_W),
|
||||||
|
.MTU (MTU)
|
||||||
|
) axis_pkt_throttle_dut (
|
||||||
|
.clk (clk ),
|
||||||
|
.rst (rst ),
|
||||||
|
.throttle(throttle),
|
||||||
|
.i_tdata (i_tdata ),
|
||||||
|
.i_tlast (i_tlast ),
|
||||||
|
.i_tvalid(i_tvalid),
|
||||||
|
.i_tready(i_tready),
|
||||||
|
.o_tdata (o_tdata ),
|
||||||
|
.o_tlast (o_tlast ),
|
||||||
|
.o_tvalid(o_tvalid),
|
||||||
|
.o_tready(o_tready)
|
||||||
|
);
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Tests
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Run a test using the following parameters.
|
||||||
|
//
|
||||||
|
// num_pkts : Number of packets to generate, each with a random
|
||||||
|
// length.
|
||||||
|
// input_stall_prob : Probability of a stall on the input, a whole number
|
||||||
|
// from 0 to 99.
|
||||||
|
// output_stall_prob : Probability of a stall on the output, a whole number
|
||||||
|
// from 0 to 99.
|
||||||
|
// rate : Floating point value in the range (0, 1.0].
|
||||||
|
// min_pkt_length : Minimum packet length to generate.
|
||||||
|
//
|
||||||
|
task automatic run_test(
|
||||||
|
int num_pkts,
|
||||||
|
real rate = 1.0,
|
||||||
|
int input_stall_prob = 0,
|
||||||
|
int output_stall_prob = 0,
|
||||||
|
int min_pkt_length = 1
|
||||||
|
);
|
||||||
|
real actual_rate;
|
||||||
|
test.start_test(
|
||||||
|
$sformatf({ "num_pkts=%0d, rate=%0.3f, input_stall_prob=%0d, ",
|
||||||
|
"output_stall_prob=%0d, min_pkt_length=%0d"},
|
||||||
|
num_pkts, rate, input_stall_prob, output_stall_prob)
|
||||||
|
);
|
||||||
|
throttle = (1.0/rate-1.0) * 2.0**FRAC_W;
|
||||||
|
actual_rate = 1.0/(real'(throttle)/(2.0**FRAC_W) + 1.0);
|
||||||
|
$display("Setting throttle to 0x%X (rate = %0.3f)", throttle, actual_rate);
|
||||||
|
i_tdata <= 'X;
|
||||||
|
i_tlast <= 'X;
|
||||||
|
i_tvalid <= 0;
|
||||||
|
o_tready <= 0;
|
||||||
|
@(posedge clk);
|
||||||
|
|
||||||
|
fork
|
||||||
|
|
||||||
|
// The writer generates random packets to input to the DUT
|
||||||
|
begin : writer
|
||||||
|
logic [DATA_W-1:0] data = 0;
|
||||||
|
for (int pkt_count = 0; pkt_count < num_pkts; pkt_count++) begin
|
||||||
|
int pkt_length = $urandom_range(min_pkt_length, MAX_PKT_LEN);
|
||||||
|
for (int word_count = 0; word_count < pkt_length; word_count++) begin
|
||||||
|
// Write the next word
|
||||||
|
i_tdata <= data;
|
||||||
|
i_tlast <= (word_count == pkt_length-1);
|
||||||
|
i_tvalid <= 1;
|
||||||
|
do @(posedge clk); while (!(i_tvalid && i_tready));
|
||||||
|
data = data + 1;
|
||||||
|
// Randomly stall between words
|
||||||
|
if ($urandom_range(99) < input_stall_prob) begin
|
||||||
|
i_tdata <= 'X;
|
||||||
|
i_tlast <= 'X;
|
||||||
|
i_tvalid <= 0;
|
||||||
|
do @(posedge clk); while ($urandom_range(99) < input_stall_prob);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end : writer
|
||||||
|
|
||||||
|
// The data_checker verifies that the data output is correct and handles
|
||||||
|
// random stalling of the output stream.
|
||||||
|
begin : data_checker
|
||||||
|
logic [DATA_W-1:0] data = 0;
|
||||||
|
for (int pkt_count = 0; pkt_count < num_pkts; pkt_count++) begin
|
||||||
|
int word_count = 0;
|
||||||
|
forever begin
|
||||||
|
@(posedge clk);
|
||||||
|
if (o_tvalid && o_tready) begin
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
o_tdata == data,
|
||||||
|
$sformatf({
|
||||||
|
"Data didn't match expected on packet %0d word offset %0d. ",
|
||||||
|
"Expected %X, read %X"},
|
||||||
|
pkt_count, word_count, data, o_tdata
|
||||||
|
)
|
||||||
|
)
|
||||||
|
data++;
|
||||||
|
if (i_tlast) break;
|
||||||
|
word_count++;
|
||||||
|
end
|
||||||
|
// Randomly stall this cycle
|
||||||
|
o_tready <= ($urandom_range(99) >= output_stall_prob);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end : data_checker
|
||||||
|
|
||||||
|
// The throttle_checker measures the packet rate and confirms that it
|
||||||
|
// matches the configured rate.
|
||||||
|
begin : throttle_checker
|
||||||
|
bit sop = 0; // Start of packet indicator
|
||||||
|
realtime sop_time; // Time at which the packet started
|
||||||
|
int pkt_length; // Counter to measure packet length (number of transfers)
|
||||||
|
int pkt_duration; // Counter to measure packet duration (from start to tlast)
|
||||||
|
|
||||||
|
// Wait for the start of the first packet
|
||||||
|
forever begin
|
||||||
|
@(posedge clk);
|
||||||
|
if (o_tvalid && o_tready) begin
|
||||||
|
sop_time = $realtime;
|
||||||
|
sop = o_tlast;
|
||||||
|
break;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// Iterate through packets
|
||||||
|
pkt_length = 1;
|
||||||
|
pkt_duration = 1;
|
||||||
|
for (int pkt_count = 0; pkt_count < num_pkts; pkt_count++) begin : pkt_loop
|
||||||
|
int exp_pkt_cycles;
|
||||||
|
|
||||||
|
forever begin : cycle_loop
|
||||||
|
@(posedge clk);
|
||||||
|
if (!sop) pkt_duration++;
|
||||||
|
if (o_tvalid && o_tready) begin : transfer_cycle
|
||||||
|
// Calculate the minimum allowed time between packets assuming
|
||||||
|
// continuous data.
|
||||||
|
exp_pkt_cycles = int'(real'(pkt_length) / actual_rate);
|
||||||
|
|
||||||
|
// Check if this is the first transfer of a packet. If so, verify
|
||||||
|
// that the duration of the previous packet was not shorter than
|
||||||
|
// the configured rate would allow.
|
||||||
|
if (sop) begin : first_transfer
|
||||||
|
int pkt_cycles;
|
||||||
|
|
||||||
|
// Calculate the actual time between packets.
|
||||||
|
pkt_cycles = ($realtime - sop_time) / CLK_PERIOD;
|
||||||
|
|
||||||
|
if (input_stall_prob == 0 && output_stall_prob == 0) begin
|
||||||
|
// If there are no stalls, the actual time should exactly
|
||||||
|
// match the expected, or one less due to rounding.
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
pkt_cycles == exp_pkt_cycles || pkt_cycles == exp_pkt_cycles-1,
|
||||||
|
$sformatf({
|
||||||
|
"Time for packet %0d did not match expected range.\n",
|
||||||
|
" Actual Rate: %f\n",
|
||||||
|
" Packet Length: %0d\n",
|
||||||
|
" Packet Cycles: %0d\n",
|
||||||
|
" Expected Cycles: %0d"},
|
||||||
|
pkt_count, actual_rate, pkt_length, pkt_cycles, exp_pkt_cycles
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end else begin
|
||||||
|
// If there are stalls, the actual length should never be
|
||||||
|
// less than the min expected, but could be substantially
|
||||||
|
// more.
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
pkt_cycles >= exp_pkt_cycles-1,
|
||||||
|
$sformatf({
|
||||||
|
"Time for packet %0d was less than expected.\n",
|
||||||
|
" Actual Rate: %f\n",
|
||||||
|
" Packet Length: %0d\n",
|
||||||
|
" Packet Cycles: %0d\n",
|
||||||
|
" Expected Cycles: %0d"},
|
||||||
|
pkt_count, actual_rate, pkt_length, pkt_cycles, exp_pkt_cycles
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
// Setup measurement of the packet we just started
|
||||||
|
sop = o_tlast;
|
||||||
|
pkt_length = 1;
|
||||||
|
pkt_duration = 1;
|
||||||
|
sop_time = $realtime;
|
||||||
|
break;
|
||||||
|
end : first_transfer
|
||||||
|
else begin : subsequent_transfer
|
||||||
|
pkt_length++;
|
||||||
|
end : subsequent_transfer
|
||||||
|
|
||||||
|
sop = o_tlast;
|
||||||
|
end : transfer_cycle
|
||||||
|
else begin : idle_cycle
|
||||||
|
// Calculate the minimum allowed time between packets assuming
|
||||||
|
// continuous data.
|
||||||
|
int exp_pkt_cycles = int'(real'(pkt_length) / actual_rate);
|
||||||
|
|
||||||
|
// If the packet duration was longer than the expected packet
|
||||||
|
// time, then we should NOT be gating packet flow.
|
||||||
|
if (pkt_duration > exp_pkt_cycles) begin
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
axis_pkt_throttle_dut.gate == 0,
|
||||||
|
"The throttle gate engaged when it should not have."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// If we're on the last packet, then there won't be another start
|
||||||
|
// of packet to measure against, so there's nothing left to check.
|
||||||
|
if (sop && (pkt_count == num_pkts-1)) break;
|
||||||
|
end : cycle_loop
|
||||||
|
end : pkt_loop
|
||||||
|
end : throttle_checker
|
||||||
|
|
||||||
|
join
|
||||||
|
test.end_test();
|
||||||
|
endtask : run_test
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Underflow Tracker
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
int uflow_rise_count = 0;
|
||||||
|
int uflow_fall_count = 0;
|
||||||
|
logic uflow_prev = 0;
|
||||||
|
|
||||||
|
always @(posedge clk) begin
|
||||||
|
uflow_prev <= axis_pkt_throttle_dut.underflow;
|
||||||
|
|
||||||
|
if (axis_pkt_throttle_dut.underflow && !uflow_prev) begin
|
||||||
|
uflow_rise_count <= uflow_rise_count + 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if (!axis_pkt_throttle_dut.underflow && uflow_prev) begin
|
||||||
|
uflow_fall_count <= uflow_fall_count + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
// Main Test Process
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
initial begin : tb_main
|
||||||
|
string tb_name;
|
||||||
|
|
||||||
|
tb_name = $sformatf("axis_pkt_throttle");
|
||||||
|
test.start_tb(tb_name, 20ms);
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Reset
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
test.start_test("Reset", 1ms);
|
||||||
|
clk_gen.reset();
|
||||||
|
if (rst) @rst;
|
||||||
|
test.end_test();
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Test Sequences
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
begin
|
||||||
|
// List various rate settings and handshake stall behaviors to test
|
||||||
|
automatic real rates[] = '{
|
||||||
|
0.10,
|
||||||
|
0.25,
|
||||||
|
0.50,
|
||||||
|
0.75,
|
||||||
|
0.90,
|
||||||
|
1.00
|
||||||
|
};
|
||||||
|
automatic real stall_probs[] = '{
|
||||||
|
0,
|
||||||
|
25,
|
||||||
|
50,
|
||||||
|
75
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test each permutation of rates and stall probabilities
|
||||||
|
foreach (rates[i]) begin
|
||||||
|
foreach (stall_probs[j]) begin
|
||||||
|
foreach (stall_probs[k]) begin
|
||||||
|
run_test(NUM_PACKETS, rates[i], stall_probs[j], stall_probs[k]);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
// Test really slow packets to make sure the internal counters handle
|
||||||
|
// underflow correctly.
|
||||||
|
localparam int NUM_UFLOW_PACKETS = 4;
|
||||||
|
run_test(NUM_UFLOW_PACKETS, 0.1, 99, 99, MAX_PKT_LEN/2);
|
||||||
|
// Follow up with regular packets
|
||||||
|
run_test(2);
|
||||||
|
`ASSERT_ERROR(
|
||||||
|
uflow_rise_count == uflow_fall_count && uflow_rise_count == NUM_UFLOW_PACKETS,
|
||||||
|
"Underflow did not occur as expected."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
//--------------------------------
|
||||||
|
// Finish Up
|
||||||
|
//--------------------------------
|
||||||
|
|
||||||
|
test.end_tb();
|
||||||
|
|
||||||
|
end : tb_main
|
||||||
|
|
||||||
|
endmodule : axis_pkt_throttle_tb
|
||||||
|
|
||||||
|
|
||||||
|
`default_nettype wire
|
||||||
Reference in New Issue
Block a user