A Stream Data Generator which can take data from both a file or just a counter. This is needed for me as a testbench component for interfaces which works on one hand as an AXIS slave and on the other hand as Ethernet.
A struct module
`timescale 1ns / 1ps
module axis_data_generator #
(
parameter integer AXIS_DATA_WIDTH = 32,
parameter [(AXIS_DATA_WIDTH / 8) - 1:0] AXIS_TKEEP = 4'hf,
parameter INIT_FILE = "",
parameter integer BURST_SIZE = 1024
)
(
input wire clk_i,
input wire s_rst_n_i,
input wire enable_i,
output wire [AXIS_DATA_WIDTH - 1 : 0] m_axis_tdata_o,
output wire [(AXIS_DATA_WIDTH / 8) - 1:0] m_axis_tkeep_o,
output wire m_axis_tvalid_o,
output wire m_axis_tlast_o,
input wire m_axis_tready_i
);
wire counter_terminate;
wire counter_enable;
wire [AXIS_DATA_WIDTH - 1 : 0] data;
wire [AXIS_DATA_WIDTH - 1 : 0] counter_value;
COUNTER_TC_MACRO #
(
.COUNT_BY (48'h1 ),
.DEVICE ("7SERIES" ),
.DIRECTION ("UP" ),
.RESET_UPON_TC ("TRUE" ),
.TC_VALUE (BURST_SIZE ),
.WIDTH_DATA (AXIS_DATA_WIDTH )
)
tc_counter_inst_0
(
.Q (counter_value ),
.TC (counter_terminate),
.CLK (clk_i ),
.CE (counter_enable ),
.RST (!s_rst_n_i )
);
generate
if ("" != INIT_FILE)
begin : init_from_file
BRAM_SINGLE_MACRO #
(
.BRAM_SIZE ("18Kb" ),
.DEVICE ("7SERIES" ),
.DO_REG (0 ),
.INIT ({AXIS_DATA_WIDTH{1'h0}}),
.INIT_FILE ("NONE" ),
.WRITE_WIDTH (AXIS_DATA_WIDTH ),
.READ_WIDTH (AXIS_DATA_WIDTH ),
.SRVAL ({AXIS_DATA_WIDTH{1'h0}}),
.WRITE_MODE ("NO_CHANGE" )
)
single_bram_inst_0
(
.DO (data ),
.ADDR (counter_value ),
.CLK (clk_i ),
.DI ({AXIS_DATA_WIDTH{1'h0}}),
.EN (enable_i ),
.REGCE (1'h0 ),
.RST (!s_rst_n_i ),
.WE (1'h0 )
);
end
else
begin : data_from_counter_buf
IBUF #
(
.IBUF_LOW_PWR ("TRUE" ),
.IOSTANDARD ("DEFAULT")
)
ibuf_inst_0
(
.O (data ),
.I (counter_value)
);
end
endgenerate
axis_data_generator_cntr #
(
.AXIS_DATA_WIDTH (AXIS_DATA_WIDTH),
.AXIS_TKEEP (AXIS_TKEEP )
)
(
.clk_i (clk_i ),
.s_rst_n_i (s_rst_n_i ),
.enable_i (enable_i ),
.m_axis_tdata_o (m_axis_tdata_o ),
.m_axis_tkeep_o (m_axis_tkeep_o ),
.m_axis_tvalid_o (m_axis_tvalid_o ),
.m_axis_tlast_o (m_axis_tlast_o ),
.m_axis_tready_i (m_axis_tready_i ),
.data_i (data ),
.counter_terminal_i (counter_terminate),
.counter_enable_o (counter_enable )
);
endmodule
axi stream controller module
`timescale 1ns / 1ps
module axis_data_generator_cntr #
(
parameter integer AXIS_DATA_WIDTH = 32,
parameter [(AXIS_DATA_WIDTH / 8) - 1:0] AXIS_TKEEP = 'hf
)
(
input wire clk_i,
input wire s_rst_n_i,
input wire enable_i,
output wire [AXIS_DATA_WIDTH - 1 : 0] m_axis_tdata_o,
output wire [(AXIS_DATA_WIDTH / 8) - 1:0] m_axis_tkeep_o,
output wire m_axis_tvalid_o,
output wire m_axis_tlast_o,
input wire m_axis_tready_i,
input wire [AXIS_DATA_WIDTH - 1 : 0] data_i,
input wire counter_terminal_i,
output wire counter_enable_o
);
localparam integer STATE_NUM = 3;
localparam integer STATE_WIDTH = $clog2(STATE_NUM);
localparam [STATE_WIDTH - 1 : 0] IDLE_STATE = 0;
localparam [STATE_WIDTH - 1 : 0] SENDING_STATE = 1;
localparam [STATE_WIDTH - 1 : 0] STOP_STATE = 2;
reg [STATE_WIDTH - 1 : 0] fsm_state;
reg [STATE_WIDTH - 1 : 0] next_fsm_state;
assign m_axis_tvalid_o = enable_i;
assign m_axis_tkeep_o = AXIS_TKEEP;
assign m_axis_tlast_o = counter_terminal_i;
assign counter_enable_o = ((SENDING_STATE == fsm_state) && (1'h1 == m_axis_tready_i));
assign m_axis_tdata_o = data_i;
always @( posedge clk_i )
begin
if(1'h0 == s_rst_n_i)
begin
fsm_state <= IDLE_STATE;
next_fsm_state <= IDLE_STATE;
end
else
begin
fsm_state <= next_fsm_state;
end
end
always @ (*)
begin
next_fsm_state = fsm_state;
if (1'h1 == enable_i)
begin
case (fsm_state)
IDLE_STATE:
begin
if (1'h1 == m_axis_tready_i)
begin
next_fsm_state = SENDING_STATE;
end
end
SENDING_STATE:
begin
if (1'h1 == counter_terminal_i)
begin
next_fsm_state = STOP_STATE;
end
end
STOP_STATE:
begin
next_fsm_state = IDLE_STATE;
end
endcase
end
end
endmodule
testbench
`timescale 1ns / 1ps
module axis_data_generator_cntr_tb;
localparam integer DATA_WIDTH = 32;
localparam integer CLOCK_PERIOD = 100;
localparam integer PACK_SIZE = 1024;
localparam integer PACK_NUMBER = 1024;
localparam integer ITERATION_NUMBER = PACK_SIZE * PACK_NUMBER;
localparam [DATA_WIDTH / 8 - 1 : 0] KEEP = 'hf;
wire counter_enable;
wire terminate;
wire axis_tvalid;
wire axis_tlast;
wire [DATA_WIDTH - 1 : 0] axis_tdata;
wire [DATA_WIDTH / 8 - 1 : 0] axis_tkeep;
wire [DATA_WIDTH - 1 : 0] data;
reg axis_tready;
reg clk;
reg rst_n;
reg enable;
COUNTER_TC_MACRO #
(
.COUNT_BY (48'h1 ),
.DEVICE ("7SERIES" ),
.DIRECTION ("UP" ),
.RESET_UPON_TC ("TRUE" ),
.TC_VALUE (PACK_SIZE ),
.WIDTH_DATA (DATA_WIDTH)
)
COUNTER_TC_MACRO_inst_0
(
.Q (data ),
.TC (terminate ),
.CLK (clk ),
.CE (counter_enable),
.RST (!rst_n )
);
axis_data_generator_cntr #
(
.AXIS_DATA_WIDTH (DATA_WIDTH),
.AXIS_TKEEP (KEEP )
)
axis_data_generator_cntr_dut_0
(
.clk_i (clk ),
.s_rst_n_i (rst_n ),
.enable_i (enable ),
.m_axis_tdata_o (axis_tdata ),
.m_axis_tkeep_o (axis_tkeep ),
.m_axis_tvalid_o (axis_tvalid ),
.m_axis_tlast_o (axis_tlast ),
.m_axis_tready_i (axis_tready ),
.data_i (data ),
.counter_terminal_i (terminate ),
.counter_enable_o (counter_enable)
);
task check_data;
begin
enable <= 1'h1;
rst_n <= 1'h1;
@(posedge clk);
wait (axis_tvalid);
repeat(100)
begin
if ({DATA_WIDTH{1'h0}} != axis_tdata)
begin
$display("The ready signal error.");
$stop();
end
@(posedge clk);
end
repeat(ITERATION_NUMBER)
begin
axis_tready <= $urandom % 2;
@(posedge clk);
end
enable <= 1'h0;
end
endtask
initial
begin
clk = 1'h0;
forever
begin
#(CLOCK_PERIOD / 2) clk = !clk;
end
end
initial
begin
rst_n = 1'h0;
enable = 1'h0;
@(posedge clk);
check_data;
$display("The test has finished.");
$stop();
end
endmodule
1 Answer 1
The layout of code the follows good practices, and you have a clean separation between design and testbench.
There is a syntax error which your compiler should have detected. An instance name is required when you instantiate a module, but your axis_data_generator_cntr
instance is missing a name. You should add something like axis_data_generator_cntr_0
below:
axis_data_generator_cntr #
(
.AXIS_DATA_WIDTH (AXIS_DATA_WIDTH),
.AXIS_TKEEP (AXIS_TKEEP )
)
axis_data_generator_cntr_0
(
.clk_i (clk_i ),
You should not assign to signals from multiple always
blocks. The assignment to next_fsm_state
should be removed from the sequential logic block:
next_fsm_state <= IDLE_STATE;
While this might not cause problems during simulation, you will likely get warnings from synthesis.
In your testbench, it is a good practice to use case equality operators for comparisons when operands are 4-state values (and can have x
or z
). For example, change !=
to !==
in the following line:
if ({DATA_WIDTH{1'h0}} != axis_tdata)
It is also good practice to display the simulation time for your $display
messages. For example:
$display($time, " The ready signal error.");
It doesn't matter much in the code you posted since you immediately stop the simulation after each $display
, but if you start displaying messages at different times, it will be helpful for debug.
$urandom
is a system task which was introduced as part of the SystemVerilog extensions to the language (IEEE Std 1800). This means that your simulator supports SV to some degree. You could also consider exploring whether your tool chain supports other SV features.
You could also review my Answer to your previous Question regarding some more minor coding style and layout preferences.
-
1\$\begingroup\$ I gratful you for your notices, it will be very useful for me. I use a vivado compiler and yes it missed the error as well as me. All which you indicated was corrected and now it should be better. Unfortunately, I am not able to refuse "if (1'h1 == some_net)". It is my curse... \$\endgroup\$Drakonof– Drakonof2021年02月17日 12:52:20 +00:00Commented Feb 17, 2021 at 12:52