diff --git a/README.md b/README.md index a4de85a25..6ac5e504f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,11 @@ Configurable word-based or frame-based asynchronous FIFO with parametrizable data width, depth, type, and bad frame detection. Supports power of two depths only. +### axis_broadcast module + +AXI stream broadcaster. Duplicates one input stream across multiple output +streams. + ### axis_cobs_decode Consistent Overhead Byte Stuffing (COBS) decoder. Fixed 8 bit width. @@ -186,6 +191,7 @@ Parametrizable priority encoder. axis_adapter.v : Parametrizable bus width adapter axis_arb_mux.v : Parametrizable arbitrated multiplexer axis_async_fifo.v : Parametrizable asynchronous FIFO + axis_broadcast.v : AXI stream broadcaster axis_cobs_decode.v : COBS decoder axis_cobs_encode.v : COBS encoder axis_crosspoint.v : Parametrizable crosspoint switch diff --git a/rtl/axis_broadcast.v b/rtl/axis_broadcast.v new file mode 100644 index 000000000..c285ff3e3 --- /dev/null +++ b/rtl/axis_broadcast.v @@ -0,0 +1,180 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * AXI4-Stream broadcaster + */ +module axis_broadcast # +( + parameter M_COUNT = 4, + parameter DATA_WIDTH = 8, + parameter KEEP_ENABLE = (DATA_WIDTH>8), + parameter KEEP_WIDTH = (DATA_WIDTH/8), + parameter LAST_ENABLE = 1, + parameter ID_ENABLE = 0, + parameter ID_WIDTH = 8, + parameter DEST_ENABLE = 0, + parameter DEST_WIDTH = 8, + parameter USER_ENABLE = 1, + parameter USER_WIDTH = 1 +) +( + input wire clk, + input wire rst, + + /* + * AXI input + */ + input wire [DATA_WIDTH-1:0] s_axis_tdata, + input wire [KEEP_WIDTH-1:0] s_axis_tkeep, + input wire s_axis_tvalid, + output wire s_axis_tready, + input wire s_axis_tlast, + input wire [ID_WIDTH-1:0] s_axis_tid, + input wire [DEST_WIDTH-1:0] s_axis_tdest, + input wire [USER_WIDTH-1:0] s_axis_tuser, + + /* + * AXI outputs + */ + output wire [M_COUNT*DATA_WIDTH-1:0] m_axis_tdata, + output wire [M_COUNT*KEEP_WIDTH-1:0] m_axis_tkeep, + output wire [M_COUNT-1:0] m_axis_tvalid, + input wire [M_COUNT-1:0] m_axis_tready, + output wire [M_COUNT-1:0] m_axis_tlast, + output wire [M_COUNT*ID_WIDTH-1:0] m_axis_tid, + output wire [M_COUNT*DEST_WIDTH-1:0] m_axis_tdest, + output wire [M_COUNT*USER_WIDTH-1:0] m_axis_tuser +); + +parameter CL_M_COUNT = $clog2(M_COUNT); + +// datapath registers +reg s_axis_tready_reg = 1'b0, s_axis_tready_next; + +reg [DATA_WIDTH-1:0] m_axis_tdata_reg = {DATA_WIDTH{1'b0}}; +reg [KEEP_WIDTH-1:0] m_axis_tkeep_reg = {KEEP_WIDTH{1'b0}}; +reg [M_COUNT-1:0] m_axis_tvalid_reg = {M_COUNT{1'b0}}, m_axis_tvalid_next; +reg m_axis_tlast_reg = 1'b0; +reg [ID_WIDTH-1:0] m_axis_tid_reg = {ID_WIDTH{1'b0}}; +reg [DEST_WIDTH-1:0] m_axis_tdest_reg = {DEST_WIDTH{1'b0}}; +reg [USER_WIDTH-1:0] m_axis_tuser_reg = {USER_WIDTH{1'b0}}; + +reg [DATA_WIDTH-1:0] temp_m_axis_tdata_reg = {DATA_WIDTH{1'b0}}; +reg [KEEP_WIDTH-1:0] temp_m_axis_tkeep_reg = {KEEP_WIDTH{1'b0}}; +reg temp_m_axis_tvalid_reg = 1'b0, temp_m_axis_tvalid_next; +reg temp_m_axis_tlast_reg = 1'b0; +reg [ID_WIDTH-1:0] temp_m_axis_tid_reg = {ID_WIDTH{1'b0}}; +reg [DEST_WIDTH-1:0] temp_m_axis_tdest_reg = {DEST_WIDTH{1'b0}}; +reg [USER_WIDTH-1:0] temp_m_axis_tuser_reg = {USER_WIDTH{1'b0}}; + +// // datapath control +reg store_axis_input_to_output; +reg store_axis_input_to_temp; +reg store_axis_temp_to_output; + +assign s_axis_tready = s_axis_tready_reg; + +assign m_axis_tdata = {M_COUNT{m_axis_tdata_reg}}; +assign m_axis_tkeep = KEEP_ENABLE ? {M_COUNT{m_axis_tkeep_reg}} : {M_COUNT*KEEP_WIDTH{1'b1}}; +assign m_axis_tvalid = m_axis_tvalid_reg; +assign m_axis_tlast = LAST_ENABLE ? {M_COUNT{m_axis_tlast_reg}} : {M_COUNT{1'b1}}; +assign m_axis_tid = ID_ENABLE ? {M_COUNT{m_axis_tid_reg}} : {M_COUNT*ID_WIDTH{1'b0}}; +assign m_axis_tdest = DEST_ENABLE ? {M_COUNT{m_axis_tdest_reg}} : {M_COUNT*DEST_WIDTH{1'b0}}; +assign m_axis_tuser = USER_ENABLE ? {M_COUNT{m_axis_tuser_reg}} : {M_COUNT*USER_WIDTH{1'b0}}; + +// enable ready input next cycle if output is ready or the temp reg will not be filled on the next cycle (output reg empty or no input) +wire s_axis_tready_early = ((m_axis_tready & m_axis_tvalid) == m_axis_tvalid) || (!temp_m_axis_tvalid_reg && (!m_axis_tvalid || !s_axis_tvalid)); + +always @* begin + // transfer sink ready state to source + m_axis_tvalid_next = m_axis_tvalid_reg & ~m_axis_tready; + temp_m_axis_tvalid_next = temp_m_axis_tvalid_reg; + + store_axis_input_to_output = 1'b0; + store_axis_input_to_temp = 1'b0; + store_axis_temp_to_output = 1'b0; + + if (s_axis_tready_reg) begin + // input is ready + if (((m_axis_tready & m_axis_tvalid) == m_axis_tvalid) || !m_axis_tvalid) begin + // output is ready or currently not valid, transfer data to output + m_axis_tvalid_next = {M_COUNT{s_axis_tvalid}}; + store_axis_input_to_output = 1'b1; + end else begin + // output is not ready, store input in temp + temp_m_axis_tvalid_next = s_axis_tvalid; + store_axis_input_to_temp = 1'b1; + end + end else if ((m_axis_tready & m_axis_tvalid) == m_axis_tvalid) begin + // input is not ready, but output is ready + m_axis_tvalid_next = {M_COUNT{temp_m_axis_tvalid_reg}}; + temp_m_axis_tvalid_next = 1'b0; + store_axis_temp_to_output = 1'b1; + end +end + +always @(posedge clk) begin + if (rst) begin + s_axis_tready_reg <= 1'b0; + m_axis_tvalid_reg <= {M_COUNT{1'b0}}; + temp_m_axis_tvalid_reg <= {M_COUNT{1'b0}}; + end else begin + s_axis_tready_reg <= s_axis_tready_early; + m_axis_tvalid_reg <= m_axis_tvalid_next; + temp_m_axis_tvalid_reg <= temp_m_axis_tvalid_next; + end + + // datapath + if (store_axis_input_to_output) begin + m_axis_tdata_reg <= s_axis_tdata; + m_axis_tkeep_reg <= s_axis_tkeep; + m_axis_tlast_reg <= s_axis_tlast; + m_axis_tid_reg <= s_axis_tid; + m_axis_tdest_reg <= s_axis_tdest; + m_axis_tuser_reg <= s_axis_tuser; + end else if (store_axis_temp_to_output) begin + m_axis_tdata_reg <= temp_m_axis_tdata_reg; + m_axis_tkeep_reg <= temp_m_axis_tkeep_reg; + m_axis_tlast_reg <= temp_m_axis_tlast_reg; + m_axis_tid_reg <= temp_m_axis_tid_reg; + m_axis_tdest_reg <= temp_m_axis_tdest_reg; + m_axis_tuser_reg <= temp_m_axis_tuser_reg; + end + + if (store_axis_input_to_temp) begin + temp_m_axis_tdata_reg <= s_axis_tdata; + temp_m_axis_tkeep_reg <= s_axis_tkeep; + temp_m_axis_tlast_reg <= s_axis_tlast; + temp_m_axis_tid_reg <= s_axis_tid; + temp_m_axis_tdest_reg <= s_axis_tdest; + temp_m_axis_tuser_reg <= s_axis_tuser; + end +end + +endmodule diff --git a/tb/test_axis_broadcast_4.py b/tb/test_axis_broadcast_4.py new file mode 100755 index 000000000..0cb828847 --- /dev/null +++ b/tb/test_axis_broadcast_4.py @@ -0,0 +1,548 @@ +#!/usr/bin/env python +""" + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +""" + +from myhdl import * +import os + +import axis_ep +import math + +module = 'axis_broadcast' +testbench = 'test_%s_4' % module + +srcs = [] + +srcs.append("../rtl/%s.v" % module) +srcs.append("%s.v" % testbench) + +src = ' '.join(srcs) + +build_cmd = "iverilog -o %s.vvp %s" % (testbench, src) + +def bench(): + + # Parameters + M_COUNT = 4 + DATA_WIDTH = 8 + KEEP_ENABLE = (DATA_WIDTH>8) + KEEP_WIDTH = (DATA_WIDTH/8) + LAST_ENABLE = 1 + ID_ENABLE = 1 + ID_WIDTH = 8 + DEST_ENABLE = 1 + DEST_WIDTH = 8 + USER_ENABLE = 1 + USER_WIDTH = 1 + + # Inputs + clk = Signal(bool(0)) + rst = Signal(bool(0)) + current_test = Signal(intbv(0)[8:]) + + s_axis_tdata = Signal(intbv(0)[DATA_WIDTH:]) + s_axis_tkeep = Signal(intbv(1)[KEEP_WIDTH:]) + s_axis_tvalid = Signal(bool(0)) + s_axis_tlast = Signal(bool(0)) + s_axis_tid = Signal(intbv(0)[ID_WIDTH:]) + s_axis_tdest = Signal(intbv(0)[DEST_WIDTH:]) + s_axis_tuser = Signal(intbv(0)[USER_WIDTH:]) + + m_axis_tready_list = [Signal(bool(0)) for i in range(M_COUNT)] + + m_axis_tready = ConcatSignal(*reversed(m_axis_tready_list)) + + # Outputs + s_axis_tready = Signal(bool(0)) + + m_axis_tdata = Signal(intbv(0)[M_COUNT*DATA_WIDTH:]) + m_axis_tkeep = Signal(intbv(0xf)[M_COUNT*KEEP_WIDTH:]) + m_axis_tvalid = Signal(intbv(0)[M_COUNT:]) + m_axis_tlast = Signal(intbv(0)[M_COUNT:]) + m_axis_tid = Signal(intbv(0)[M_COUNT*ID_WIDTH:]) + m_axis_tdest = Signal(intbv(0)[M_COUNT*DEST_WIDTH:]) + m_axis_tuser = Signal(intbv(0)[M_COUNT*USER_WIDTH:]) + + m_axis_tdata_list = [m_axis_tdata((i+1)*DATA_WIDTH, i*DATA_WIDTH) for i in range(M_COUNT)] + m_axis_tkeep_list = [m_axis_tkeep((i+1)*KEEP_WIDTH, i*KEEP_WIDTH) for i in range(M_COUNT)] + m_axis_tvalid_list = [m_axis_tvalid(i) for i in range(M_COUNT)] + m_axis_tlast_list = [m_axis_tlast(i) for i in range(M_COUNT)] + m_axis_tid_list = [m_axis_tid((i+1)*ID_WIDTH, i*ID_WIDTH) for i in range(M_COUNT)] + m_axis_tdest_list = [m_axis_tdest((i+1)*DEST_WIDTH, i*DEST_WIDTH) for i in range(M_COUNT)] + m_axis_tuser_list = [m_axis_tuser((i+1)*USER_WIDTH, i*USER_WIDTH) for i in range(M_COUNT)] + + # sources and sinks + source_pause = Signal(bool(0)) + sink_pause_list = [] + sink_list = [] + sink_logic_list = [] + + source = axis_ep.AXIStreamSource() + + source_logic = source.create_logic( + clk, + rst, + tdata=s_axis_tdata, + tkeep=s_axis_tkeep, + tvalid=s_axis_tvalid, + tready=s_axis_tready, + tlast=s_axis_tlast, + tid=s_axis_tid, + tdest=s_axis_tdest, + tuser=s_axis_tuser, + pause=source_pause, + name='source' + ) + + for k in range(M_COUNT): + s = axis_ep.AXIStreamSink() + p = Signal(bool(0)) + + sink_list.append(s) + sink_pause_list.append(p) + + sink_logic_list.append(s.create_logic( + clk, + rst, + tdata=m_axis_tdata_list[k], + tkeep=m_axis_tkeep_list[k], + tvalid=m_axis_tvalid_list[k], + tready=m_axis_tready_list[k], + tlast=m_axis_tlast_list[k], + tid=m_axis_tid_list[k], + tdest=m_axis_tdest_list[k], + tuser=m_axis_tuser_list[k], + pause=p, + name='sink_%d' % k + )) + + # DUT + if os.system(build_cmd): + raise Exception("Error running build command") + + dut = Cosimulation( + "vvp -m myhdl %s.vvp -lxt2" % testbench, + clk=clk, + rst=rst, + current_test=current_test, + + s_axis_tdata=s_axis_tdata, + s_axis_tkeep=s_axis_tkeep, + s_axis_tvalid=s_axis_tvalid, + s_axis_tready=s_axis_tready, + s_axis_tlast=s_axis_tlast, + s_axis_tid=s_axis_tid, + s_axis_tdest=s_axis_tdest, + s_axis_tuser=s_axis_tuser, + + m_axis_tdata=m_axis_tdata, + m_axis_tkeep=m_axis_tkeep, + m_axis_tvalid=m_axis_tvalid, + m_axis_tready=m_axis_tready, + m_axis_tlast=m_axis_tlast, + m_axis_tid=m_axis_tid, + m_axis_tdest=m_axis_tdest, + m_axis_tuser=m_axis_tuser + ) + + @always(delay(4)) + def clkgen(): + clk.next = not clk + + @instance + def check(): + yield delay(100) + yield clk.posedge + rst.next = 1 + yield clk.posedge + rst.next = 0 + yield clk.posedge + yield delay(100) + yield clk.posedge + + yield clk.posedge + + yield clk.posedge + print("test 1: test packet") + current_test.next = 1 + + test_frame = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=1 + ) + + source.send(test_frame) + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame + + yield delay(100) + + yield clk.posedge + print("test 2: longer packet") + current_test.next = 2 + + test_frame = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + bytearray(range(256)), + id=1 + ) + + source.send(test_frame) + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame + + yield delay(100) + + yield clk.posedge + print("test 3: test packet with pauses") + current_test.next = 3 + + test_frame = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=3, + dest=1 + ) + + source.send(test_frame) + yield clk.posedge + + yield delay(64) + yield clk.posedge + source_pause.next = True + yield delay(32) + yield clk.posedge + source_pause.next = False + + yield delay(64) + yield clk.posedge + sink_pause_list[0].next = True + sink_pause_list[1].next = True + sink_pause_list[2].next = True + sink_pause_list[3].next = True + yield delay(32) + yield clk.posedge + sink_pause_list[0].next = False + sink_pause_list[1].next = False + sink_pause_list[2].next = False + sink_pause_list[3].next = False + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame + + yield delay(100) + + yield clk.posedge + print("test 4: back-to-back packets") + current_test.next = 4 + + test_frame1 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=4, + dest=1 + ) + test_frame2 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x02\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=4, + dest=2 + ) + + source.send(test_frame1) + source.send(test_frame2) + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame1 + + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame2 + + yield delay(100) + + yield clk.posedge + print("test 5: alternate pause source") + current_test.next = 5 + + test_frame1 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=5, + dest=1 + ) + test_frame2 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x02\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=5, + dest=2 + ) + + source.send(test_frame1) + source.send(test_frame2) + yield clk.posedge + + while s_axis_tvalid or m_axis_tvalid: + yield clk.posedge + yield clk.posedge + source_pause.next = False + yield clk.posedge + source_pause.next = True + yield clk.posedge + + source_pause.next = False + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame1 + + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame2 + + yield delay(100) + + yield clk.posedge + print("test 6: alternate pause sink") + current_test.next = 6 + + test_frame1 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=6, + dest=1 + ) + test_frame2 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x02\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=6, + dest=2 + ) + + source.send(test_frame1) + source.send(test_frame2) + yield clk.posedge + + while s_axis_tvalid or m_axis_tvalid: + sink_pause_list[0].next = True + sink_pause_list[1].next = True + sink_pause_list[2].next = True + sink_pause_list[3].next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + sink_pause_list[0].next = False + sink_pause_list[1].next = False + sink_pause_list[2].next = False + sink_pause_list[3].next = False + yield clk.posedge + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame1 + + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame2 + + yield delay(100) + + yield clk.posedge + print("test 7: alternate pause individual sinks") + current_test.next = 7 + + test_frame1 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=7, + dest=1 + ) + test_frame2 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x02\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=7, + dest=2 + ) + + source.send(test_frame1) + source.send(test_frame2) + yield clk.posedge + + while s_axis_tvalid or m_axis_tvalid: + for pause in sink_pause_list: + pause.next = True + yield clk.posedge + yield clk.posedge + yield clk.posedge + pause.next = False + yield clk.posedge + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame1 + + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame2 + + yield delay(100) + + yield clk.posedge + print("test 8: alternate un-pause individual sinks") + current_test.next = 8 + + test_frame1 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x01\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=8, + dest=1 + ) + test_frame2 = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x02\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=8, + dest=2 + ) + + source.send(test_frame1) + source.send(test_frame2) + yield clk.posedge + + for pause in sink_pause_list: + pause.next = True + + while s_axis_tvalid or m_axis_tvalid: + for pause in sink_pause_list: + yield clk.posedge + yield clk.posedge + yield clk.posedge + pause.next = False + yield clk.posedge + pause.next = True + + for pause in sink_pause_list: + pause.next = False + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame1 + + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame2 + + yield delay(100) + + yield clk.posedge + print("test 9: tuser assert") + current_test.next = 9 + + test_frame = axis_ep.AXIStreamFrame( + b'\xDA\xD1\xD2\xD3\xD4\xD5' + + b'\x5A\x51\x52\x53\x54\x55' + + b'\x80\x00' + + b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10', + id=9, + dest=1, + last_cycle_user=1 + ) + + source.send(test_frame) + + for sink in sink_list: + yield sink.wait() + rx_frame = sink.recv() + + assert rx_frame == test_frame + assert rx_frame.last_cycle_user + + yield delay(100) + + raise StopSimulation + + return instances() + +def test_bench(): + os.chdir(os.path.dirname(os.path.abspath(__file__))) + sim = Simulation(bench()) + sim.run() + +if __name__ == '__main__': + print("Running test...") + test_bench() + diff --git a/tb/test_axis_broadcast_4.v b/tb/test_axis_broadcast_4.v new file mode 100644 index 000000000..e4f707697 --- /dev/null +++ b/tb/test_axis_broadcast_4.v @@ -0,0 +1,140 @@ +/* + +Copyright (c) 2019 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Testbench for axis_broadcast + */ +module test_axis_broadcast_4; + +// Parameters +parameter M_COUNT = 4; +parameter DATA_WIDTH = 8; +parameter KEEP_ENABLE = (DATA_WIDTH>8); +parameter KEEP_WIDTH = (DATA_WIDTH/8); +parameter LAST_ENABLE = 1; +parameter ID_ENABLE = 1; +parameter ID_WIDTH = 8; +parameter DEST_ENABLE = 1; +parameter DEST_WIDTH = 8; +parameter USER_ENABLE = 1; +parameter USER_WIDTH = 1; + +// Inputs +reg clk = 0; +reg rst = 0; +reg [7:0] current_test = 0; + +reg [DATA_WIDTH-1:0] s_axis_tdata = 0; +reg [KEEP_WIDTH-1:0] s_axis_tkeep = 0; +reg s_axis_tvalid = 0; +reg s_axis_tlast = 0; +reg [ID_WIDTH-1:0] s_axis_tid = 0; +reg [DEST_WIDTH-1:0] s_axis_tdest = 0; +reg [USER_WIDTH-1:0] s_axis_tuser = 0; + +reg [M_COUNT-1:0] m_axis_tready = 0; + +// Outputs +wire s_axis_tready; + +wire [M_COUNT*DATA_WIDTH-1:0] m_axis_tdata; +wire [M_COUNT*KEEP_WIDTH-1:0] m_axis_tkeep; +wire [M_COUNT-1:0] m_axis_tvalid; +wire [M_COUNT-1:0] m_axis_tlast; +wire [M_COUNT*ID_WIDTH-1:0] m_axis_tid; +wire [M_COUNT*DEST_WIDTH-1:0] m_axis_tdest; +wire [M_COUNT*USER_WIDTH-1:0] m_axis_tuser; + +initial begin + // myhdl integration + $from_myhdl( + clk, + rst, + current_test, + s_axis_tdata, + s_axis_tkeep, + s_axis_tvalid, + s_axis_tlast, + s_axis_tid, + s_axis_tdest, + s_axis_tuser, + m_axis_tready + ); + $to_myhdl( + s_axis_tready, + m_axis_tdata, + m_axis_tkeep, + m_axis_tvalid, + m_axis_tlast, + m_axis_tid, + m_axis_tdest, + m_axis_tuser + ); + + // dump file + $dumpfile("test_axis_broadcast_4.lxt"); + $dumpvars(0, test_axis_broadcast_4); +end + +axis_broadcast #( + .M_COUNT(M_COUNT), + .DATA_WIDTH(DATA_WIDTH), + .KEEP_ENABLE(KEEP_ENABLE), + .KEEP_WIDTH(KEEP_WIDTH), + .LAST_ENABLE(LAST_ENABLE), + .ID_ENABLE(ID_ENABLE), + .ID_WIDTH(ID_WIDTH), + .DEST_ENABLE(DEST_ENABLE), + .DEST_WIDTH(DEST_WIDTH), + .USER_ENABLE(USER_ENABLE), + .USER_WIDTH(USER_WIDTH) +) +UUT ( + .clk(clk), + .rst(rst), + // AXI input + .s_axis_tdata(s_axis_tdata), + .s_axis_tkeep(s_axis_tkeep), + .s_axis_tvalid(s_axis_tvalid), + .s_axis_tready(s_axis_tready), + .s_axis_tlast(s_axis_tlast), + .s_axis_tid(s_axis_tid), + .s_axis_tdest(s_axis_tdest), + .s_axis_tuser(s_axis_tuser), + // AXI outputs + .m_axis_tdata(m_axis_tdata), + .m_axis_tkeep(m_axis_tkeep), + .m_axis_tvalid(m_axis_tvalid), + .m_axis_tready(m_axis_tready), + .m_axis_tlast(m_axis_tlast), + .m_axis_tid(m_axis_tid), + .m_axis_tdest(m_axis_tdest), + .m_axis_tuser(m_axis_tuser) +); + +endmodule