Has anyone successfully created a 2-input, 1-output RFNoC block that they could share, or at least help me figure out what I'm doing incorrectly?
I had set out to create a 2-input, 1-output RFNoC block: Complex Multiply. There's already a 'cmul' module in the uhd-fpga source, all it needed was a proper RFNoC wrapper. I reviewed the source of the addsub block, as it has two inputs. However, its a little odd in its implementation as it doesn't follow the design pattern using an AXI wrapper between the the RFNoC shell and the module IP. It uses its own chdr deframer (which is what axi_wrapper seems like it should do instead). I had also found this list post from 2017: http://lists.ettus.com/pipermail/usrp-users_lists.ettus.com/2017-June/053156.html There didn't appear to be any published resolution on that thread. It turns out, finding information on 2-input, 1-output blocks is a bit of a challenge, some other relevant posts: http://lists.ettus.com/pipermail/usrp-users_lists.ettus.com/2018-January/055504.html I had also found this post that featured some example code and some discussion about a "combiner" block. It's very similar to what I needed to do, but different arithmetic required: http://lists.ettus.com/pipermail/usrp-users_lists.ettus.com/2017-October/054654.html Another list post about 2-in 1-out, but no clear resolution (other than physical limitations): http://lists.ettus.com/pipermail/usrp-users_lists.ettus.com/2017-September/054460.html So, armed with some information I followed https://kb.ettus.com/Getting_Started_with_RFNoC_Development#Starting_a_custom_RFNoC_block_using_RFNoC_Modtool and created a module. The skeleton loopback code worked just fine with the testbench. I then modified the testbench similar to the addsub testbench and added code for the cmul IP, and modified the noc block and noc shell settings to support 2 inputs, 1 output. Running that testbench resulted in an unexpected result: it seemed that the second AXI stream wasn't receiving data from the testbench. I figured my best course of action then was to use Andy's combiner block, because it allegedly runs in a testbench. If it runs with my testbench, then I have some verilog problems to debug. However, I was unable to get Andy's combiner working in my testbench. His code has been renamed "multiplycomplex" for the sake of in-situ testing with my multiply-complex testbench. This leads me to believe I'm doing something wrong with how I'm presenting the test vectors in the testbench. Thanks! -Mitch
// /* * Copyright 2017 <+YOU OR YOUR COMPANY+>. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ // module noc_block_multiplycomplex #( parameter NOC_ID = 64'hCF4CE20A208DF91B, parameter STR_SINK_FIFOSIZE = 11) ( input bus_clk, input bus_rst, input ce_clk, input ce_rst, input [63:0] i_tdata, input i_tlast, input i_tvalid, output i_tready, output [63:0] o_tdata, output o_tlast, output o_tvalid, input o_tready, output [63:0] debug ); localparam MTU = 10; //////////////////////////////////////////////////////////// // // RFNoC Shell // //////////////////////////////////////////////////////////// wire [31:0] set_data; wire [7:0] set_addr; wire set_stb; reg [63:0] rb_data; wire [7:0] rb_addr; wire [63:0] cmdout_tdata, ackin_tdata; wire cmdout_tlast, cmdout_tvalid, cmdout_tready, ackin_tlast, ackin_tvalid, ackin_tready; wire [127:0] str_sink_tdata; wire [1:0] str_sink_tlast, str_sink_tvalid, str_sink_tready; wire [63:0] str_src_tdata; wire str_src_tlast, str_src_tvalid, str_src_tready; wire [31:0] src_sid; wire [15:0] next_dst_sid, resp_out_dst_sid; wire [31:0] resp_in_dst_sid; wire clear_tx_seqnum; noc_shell #( .NOC_ID(NOC_ID), .STR_SINK_FIFOSIZE({2{STR_SINK_FIFOSIZE[7:0]}}), .INPUT_PORTS(2), .OUTPUT_PORTS(1)) noc_shell ( .bus_clk(bus_clk), .bus_rst(bus_rst), .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), // Computer Engine Clock Domain .clk(ce_clk), .reset(ce_rst), // Control Sink .set_data(set_data), .set_addr(set_addr), .set_stb(set_stb), .rb_stb(1'b1), .rb_data(rb_data), .rb_addr(rb_addr), // Control Source .cmdout_tdata(cmdout_tdata), .cmdout_tlast(cmdout_tlast), .cmdout_tvalid(cmdout_tvalid), .cmdout_tready(cmdout_tready), .ackin_tdata(ackin_tdata), .ackin_tlast(ackin_tlast), .ackin_tvalid(ackin_tvalid), .ackin_tready(ackin_tready), // Stream Sink .str_sink_tdata(str_sink_tdata), .str_sink_tlast(str_sink_tlast), .str_sink_tvalid(str_sink_tvalid), .str_sink_tready(str_sink_tready), // Stream Source .str_src_tdata(str_src_tdata), .str_src_tlast(str_src_tlast), .str_src_tvalid(str_src_tvalid), .str_src_tready(str_src_tready), // Stream IDs set by host .src_sid(src_sid), // SID of this block .next_dst_sid(next_dst_sid), // Next destination SID .resp_in_dst_sid(resp_in_dst_sid), // Response destination SID for input stream responses / errors .resp_out_dst_sid(resp_out_dst_sid), // Response destination SID for output stream responses / errors // Misc .vita_time('d0), .clear_tx_seqnum(clear_tx_seqnum), .debug(debug)); //////////////////////////////////////////////////////////// // // AXI Wrapper // Convert RFNoC Shell interface into AXI stream interface // //////////////////////////////////////////////////////////// (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [63:0] m_axis_data_tdata; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [1:0] m_axis_data_tlast; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [1:0] m_axis_data_tvalid; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [1:0] m_axis_data_tready; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [255:0] m_axis_data_tuser; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [31:0] s_axis_data_tdata; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire s_axis_data_tlast; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire s_axis_data_tvalid; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire s_axis_data_tready; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [127:0] s_axis_data_tuser; // Handle headers cvita_hdr_modify cvita_hdr_modify_data_0 ( .header_in(m_axis_data_tuser[127:0]), .header_out(s_axis_data_tuser), .use_pkt_type(1'b0), .pkt_type(), .use_has_time(1'b1), .has_time(1'b0), .use_eob(1'b0), .eob(), .use_seqnum(1'b0), .seqnum(), .use_length(1'b0), .length(), .use_src_sid(1'b1), .src_sid(src_sid[15:0]), .use_dst_sid(1'b1), .dst_sid(next_dst_sid), .use_vita_time(1'b0), .vita_time()); axi_wrapper #( .SIMPLE_MODE(0)) axi_wrapper_0 ( .clk(ce_clk), .reset(ce_rst), .clear_tx_seqnum(clear_tx_seqnum), .next_dst(next_dst_sid), .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), .i_tdata(str_sink_tdata[63:0]), .i_tlast(str_sink_tlast[0]), .i_tvalid(str_sink_tvalid[0]), .i_tready(str_sink_tready[0]), .o_tdata(str_src_tdata), .o_tlast(str_src_tlast), .o_tvalid(str_src_tvalid), .o_tready(str_src_tready), .m_axis_data_tdata(m_axis_data_tdata[31:0]), .m_axis_data_tlast(m_axis_data_tlast[0]), .m_axis_data_tvalid(m_axis_data_tvalid[0]), .m_axis_data_tready(m_axis_data_tready[0]), .m_axis_data_tuser(m_axis_data_tuser[127:0]), .s_axis_data_tdata(s_axis_data_tdata), .s_axis_data_tlast(s_axis_data_tlast), .s_axis_data_tvalid(s_axis_data_tvalid), .s_axis_data_tready(s_axis_data_tready), .s_axis_data_tuser(s_axis_data_tuser), .m_axis_config_tdata(), .m_axis_config_tlast(), .m_axis_config_tvalid(), .m_axis_config_tready(), .m_axis_pkt_len_tdata(), .m_axis_pkt_len_tvalid(), .m_axis_pkt_len_tready()); axi_wrapper #( .SIMPLE_MODE(0)) axi_wrapper_1 ( .clk(ce_clk), .reset(ce_rst), .clear_tx_seqnum(), .next_dst(), .set_stb(), .set_addr(), .set_data(), .i_tdata(str_sink_tdata[127:64]), .i_tlast(str_sink_tlast[1]), .i_tvalid(str_sink_tvalid[1]), .i_tready(str_sink_tready[1]), .o_tdata(), .o_tlast(), .o_tvalid(), .o_tready(), .m_axis_data_tdata(m_axis_data_tdata[63:32]), .m_axis_data_tlast(m_axis_data_tlast[1]), .m_axis_data_tvalid(m_axis_data_tvalid[1]), .m_axis_data_tready(m_axis_data_tready[1]), .m_axis_data_tuser(m_axis_data_tuser[255:128]), .s_axis_data_tdata(), .s_axis_data_tlast(), .s_axis_data_tvalid(), .s_axis_data_tready(), .s_axis_data_tuser(), .m_axis_config_tdata(), .m_axis_config_tlast(), .m_axis_config_tvalid(), .m_axis_config_tready(), .m_axis_pkt_len_tdata(), .m_axis_pkt_len_tvalid(), .m_axis_pkt_len_tready()); //////////////////////////////////////////////////////////// // // User code // //////////////////////////////////////////////////////////// // NoC Shell registers 0 - 127, // User register address space starts at 128 localparam SR_USER_REG_BASE = 128; // Control Source Unused assign cmdout_tdata = 64'd0; assign cmdout_tlast = 1'b0; assign cmdout_tvalid = 1'b0; assign ackin_tready = 1'b1; // Settings registers // // - The settings register bus is a simple strobed interface. // - Transactions include both a write and a readback. // - The write occurs when set_stb is asserted. // The settings register with the address matching set_addr will // be loaded with the data on set_data. // - Readback occurs when rb_stb is asserted. The read back strobe // must assert at least one clock cycle after set_stb asserts / // rb_stb is ignored if asserted on the same clock cycle of set_stb. // Example valid and invalid timing: // __ __ __ __ // clk __| |__| |__| |__| |__ // _____ // set_stb ___| |________________ // _____ // rb_stb _________| |__________ (Valid) // _____ // rb_stb _______________| |____ (Valid) // __________________________ // rb_stb (Valid if readback data is a constant) // _____ // rb_stb ___| |________________ (Invalid / ignored, same cycle as set_stb) // localparam [7:0] SR_TEST_REG_0 = SR_USER_REG_BASE; localparam [7:0] SR_TEST_REG_1 = SR_USER_REG_BASE + 8'd1; wire [31:0] test_reg_0; setting_reg #( .my_addr(SR_TEST_REG_0), .awidth(8), .width(32)) sr_test_reg_0 ( .clk(ce_clk), .rst(ce_rst), .strobe(set_stb), .addr(set_addr), .in(set_data), .out(test_reg_0), .changed()); wire [31:0] test_reg_1; setting_reg #( .my_addr(SR_TEST_REG_1), .awidth(8), .width(32)) sr_test_reg_1 ( .clk(ce_clk), .rst(ce_rst), .strobe(set_stb), .addr(set_addr), .in(set_data), .out(test_reg_1), .changed()); // Readback registers // rb_stb set to 1'b1 on NoC Shell always @(posedge ce_clk) begin case(rb_addr) 8'd0 : rb_data <= {32'd0, test_reg_0}; 8'd1 : rb_data <= {32'd0, test_reg_1}; default : rb_data <= 64'h0BADC0DE0BADC0DE; endcase end (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [63:0] pipe_in_tdata; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [1:0] pipe_in_tvalid; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [1:0] pipe_in_tlast; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [1:0] pipe_in_tready; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire pipe_in_join_tvalid; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire pipe_in_join_tlast; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire pipe_in_join_tready; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire [31:0] pipe_out_tdata; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire pipe_out_tvalid; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire pipe_out_tlast; (* DONT_TOUCH = "true", MARK_DEBUG = "TRUE"*) wire pipe_out_tready; // Adding FIFO to ensure Pipeline axi_fifo_flop #(.WIDTH(32+1)) pipeline0_axi_fifo_flop_0 ( .clk(ce_clk), .reset(ce_rst), .clear(clear_tx_seqnum), .i_tdata({m_axis_data_tlast[0],m_axis_data_tdata[31:0]}), .i_tvalid(m_axis_data_tvalid[0]), .i_tready(m_axis_data_tready[0]), .o_tdata({pipe_in_tlast[0],pipe_in_tdata[31:0]}), .o_tvalid(pipe_in_tvalid[0]), .o_tready(pipe_in_tready[0])); // Adding FIFO to ensure Pipeline axi_fifo_flop #(.WIDTH(32+1)) pipeline0_axi_fifo_flop_1 ( .clk(ce_clk), .reset(ce_rst), .clear(clear_tx_seqnum), .i_tdata({m_axis_data_tlast[1],m_axis_data_tdata[63:32]}), .i_tvalid(m_axis_data_tvalid[1]), .i_tready(m_axis_data_tready[1]), .o_tdata({pipe_in_tlast[1],pipe_in_tdata[63:32]}), .o_tvalid(pipe_in_tvalid[1]), .o_tready(pipe_in_tready[1])); // AXI join axi_join #(.INPUTS(2)) pipeline0_axi_join ( .i_tlast(pipe_in_tlast), .i_tvalid(pipe_in_tvalid), .i_tready(pipe_in_tready), .o_tlast(pipe_in_join_tlast), .o_tvalid(pipe_in_join_tvalid), .o_tready(pipe_in_join_tready)); // Combine streams wire [16:0] sum_i = {pipe_in_tdata[31],pipe_in_tdata[31:16]} + {pipe_in_tdata[63],pipe_in_tdata[63:48]}; wire [16:0] sum_q = {pipe_in_tdata[15],pipe_in_tdata[15:0]} + {pipe_in_tdata[47],pipe_in_tdata[47:32]}; // Right shift and pack wire [31:0] sum_iq = {sum_i[16:1],sum_q[16:1]}; axi_fifo_flop #(.WIDTH(32+1)) pipeline1_axi_fifo_flop ( .clk(ce_clk), .reset(ce_rst), .clear(clear_tx_seqnum), .i_tdata({pipe_in_join_tlast,sum_iq}), .i_tvalid(pipe_in_join_tvalid), .i_tready(pipe_in_join_tready), .o_tdata({pipe_out_tlast,pipe_out_tdata}), .o_tvalid(pipe_out_tvalid), .o_tready(pipe_out_tready)); /* Output Signals */ assign pipe_out_tready = s_axis_data_tready; assign s_axis_data_tvalid = pipe_out_tvalid; assign s_axis_data_tlast = pipe_out_tlast; assign s_axis_data_tdata = pipe_out_tdata; /* Simple Loopback */ /*assign m_axis_data_tready[0] = s_axis_data_tready; assign s_axis_data_tvalid = m_axis_data_tvalid[1]; assign s_axis_data_tlast = m_axis_data_tlast[1]; assign s_axis_data_tdata = m_axis_data_tdata[63:32]; assign m_axis_data_tready[1] = s_axis_data_tready;*/ endmodule
/* * Copyright 2019 <+YOU OR YOUR COMPANY+>. * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3, or (at your option) * any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this software; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, * Boston, MA 02110-1301, USA. */ `timescale 1ns/1ps `define NS_PER_TICK 1 `define NUM_TEST_CASES 4 `include "sim_exec_report.vh" `include "sim_clks_rsts.vh" `include "sim_rfnoc_lib.svh" module noc_block_multiplycomplex_tb(); `TEST_BENCH_INIT("noc_block_multiplycomplex",`NUM_TEST_CASES,`NS_PER_TICK); localparam BUS_CLK_PERIOD = $ceil(1e9/166.67e6); localparam CE_CLK_PERIOD = $ceil(1e9/200e6); localparam NUM_CE = 1; // Number of Computation Engines / User RFNoC blocks to simulate localparam NUM_STREAMS = 2; // Number of test bench streams `RFNOC_SIM_INIT(NUM_CE, NUM_STREAMS, BUS_CLK_PERIOD, CE_CLK_PERIOD); `RFNOC_ADD_BLOCK(noc_block_multiplycomplex, 0); localparam SPP = 64; // Samples per packet /******************************************************** ** Verification ********************************************************/ initial begin : tb_main string s; logic [63:0] readback; logic [31:0] random_word; logic [15:0] real_val; logic [15:0] cplx_val; logic last; /******************************************************** ** Test 1 -- Reset ********************************************************/ `TEST_CASE_START("Wait for Reset"); while (bus_rst) @(posedge bus_clk); while (ce_rst) @(posedge ce_clk); `TEST_CASE_DONE(~bus_rst & ~ce_rst); /******************************************************** ** Test 2 -- Check for correct NoC IDs ********************************************************/ `TEST_CASE_START("Check NoC ID"); // Read NOC IDs tb_streamer.read_reg(sid_noc_block_multiplycomplex, RB_NOC_ID, readback); $display("Read multiplycomplex NOC ID: %16x", readback); `ASSERT_ERROR(readback == noc_block_multiplycomplex.NOC_ID, "Incorrect NOC ID"); `TEST_CASE_DONE(1); /******************************************************** ** Test 3 -- Connect RFNoC blocks ********************************************************/ `TEST_CASE_START("Connect RFNoC blocks"); //`RFNOC_CONNECT_BLOCK_PORT(noc_block_multiplycomplex,1,noc_block_tb,1,SC16,SPP); `RFNOC_CONNECT_BLOCK_PORT(noc_block_multiplycomplex,0,noc_block_tb,0,SC16,SPP); `RFNOC_CONNECT_BLOCK_PORT(noc_block_tb,0,noc_block_multiplycomplex,0,SC16,SPP); `RFNOC_CONNECT_BLOCK_PORT(noc_block_tb,1,noc_block_multiplycomplex,1,SC16,SPP); `TEST_CASE_DONE(1); /******************************************************** ** Test 4 -- Test sequence ********************************************************/ `TEST_CASE_START("Test sequence"); fork begin cvita_payload_t send_payload_0; cvita_payload_t send_payload_1; cvita_metadata_t tx_md; for (int i = 0; i < (SPP/2); i++) begin real_val = (i%4 + 1)*100; cplx_val = -1*(i%4 + 1)*100; $display("Send Stream %0d: %0d + %0dj",0, $signed(real_val), $signed(cplx_val)); send_payload_0.push_back({real_val, cplx_val}); //tb_streamer.push_word( {real_val, cplx_val}, 0, 0 ); cplx_val = 1*(i%4 + 1)*100; $display("Send Stream %0d: %0d + %0dj",1, $signed(real_val), $signed(cplx_val)); send_payload_1.push_back({real_val, cplx_val}); //tb_streamer.push_word( {real_val, cplx_val}, 0, 1 ); end $display("Last Payload Stream 0"); tx_md.eob = 1'b1; tb_streamer.send(send_payload_0,tx_md,0); //tb_streamer.push_word( {16'd0,16'd0}, 1, 0 ); $display("Last Payload Stream 1"); tx_md.eob = 1'b1; tb_streamer.send(send_payload_1,tx_md,1); //tb_streamer.push_word( {16'd0,16'd0}, 1, 1 ); end begin cvita_payload_t recv_payload; cvita_metadata_t rx_md; logic [63:0] recv_value; tb_streamer.recv(recv_payload,rx_md,0); for (int i = 0; i < SPP/2; i=i++) begin $display("Pull Word %0d",i); {real_val,cplx_val} = recv_payload[i]; //tb_streamer.pull_word( {real_val,cplx_val}, last ); $display("%0d + %0dj", $signed(real_val), $signed(cplx_val)); //if(last == 1) begin // break; //end end end join `TEST_CASE_DONE(1); `TEST_BENCH_DONE; end endmodule
_______________________________________________ USRP-users mailing list USRP-users@lists.ettus.com http://lists.ettus.com/mailman/listinfo/usrp-users_lists.ettus.com