diff --git a/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv b/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv index fbb017f..f8350e8 100644 --- a/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv +++ b/lib/rfnoc/blocks/rfnoc_block_ddc/rfnoc_block_ddc_tb.sv @@ -17,6 +17,8 @@ module rfnoc_block_ddc_tb(); import PkgTestExec::*; import PkgChdrUtils::*; import PkgRfnocBlockCtrlBfm::*; + import PkgRfnocItemUtils::*; + import PkgMath::*; `include "rfnoc_block_ddc_regs.vh" @@ -43,7 +45,6 @@ module rfnoc_block_ddc_tb(); localparam int CIC_MAX_DECIM = 255; localparam int NOC_ID = 32'hDDC00000; - //--------------------------------------------------------------------------- // Clocks //--------------------------------------------------------------------------- @@ -62,6 +63,8 @@ module rfnoc_block_ddc_tb(); //--------------------------------------------------------------------------- typedef ChdrData #(CHDR_W, SAMP_W)::chdr_word_t chdr_word_t; + typedef ChdrData #(CHDR_W, SAMP_W)::item_t item_t; + RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk); AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0); @@ -185,15 +188,18 @@ module rfnoc_block_ddc_tb(); ); `ASSERT_ERROR( cic_rate > 0 && cic_rate <= CIC_MAX_DECIM, - "CIC Decimation rate must be positive, not exceed the max cic decimation rate, and cannot equal 0!" + {"CIC Decimation rate must be positive, not exceed the max cic ", + "decimation rate, and cannot equal 0!"} ); // Setup DDC $display("Set decimation to %0d", decim_rate); $display("- Number of enabled HBs: %0d", hb_enables); $display("- CIC Rate: %0d", cic_rate); - write_reg(port, SR_N_ADDR, decim_rate); // Set decimation rate in AXI rate change - write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate}); // Enable HBs, set CIC rate + // Set decimation rate in AXI rate change + write_reg(port, SR_N_ADDR, decim_rate); + // Enable HBs, set CIC rate + write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate}); endtask @@ -219,7 +225,9 @@ module rfnoc_block_ddc_tb(); pkt_info = 0; for (int i = 0; i < decim_rate*(PKT_SIZE_BYTES/8 + extra_samples); i++) begin - send_payload.push_back({16'(2*i/decim_rate), 16'(2*i/decim_rate), 16'((2*i+1)/decim_rate), 16'((2*i+1)/decim_rate)}); + send_payload.push_back( + {16'(2*i/decim_rate), 16'(2*i/decim_rate), + 16'((2*i+1)/decim_rate), 16'((2*i+1)/decim_rate)}); end $display("Send ramp (%0d words)", send_payload.size()); pkt_info.eob = 1; @@ -248,10 +256,12 @@ module rfnoc_block_ddc_tb(); `ASSERT_ERROR(pkt_info.eob == 1'b1, s); recv_payload = {temp_payload, recv_payload}; if (drop_partial_packet) begin - $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size()); + $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", + PKT_SIZE_BYTES/8, recv_payload.size()); `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s); end else begin - $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size() + extra_samples); + $sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", + PKT_SIZE_BYTES/8, recv_payload.size() + extra_samples); `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8 + extra_samples, s); end samples = 64'd0; @@ -260,9 +270,13 @@ module rfnoc_block_ddc_tb(); samples = recv_payload[i]; for (int j = 0; j < 4; j++) begin // Need to check a range of values due to imperfect gain compensation - $sformat(s, "Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i, - samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4, samples[16*j +: 16]); - `ASSERT_ERROR((samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) && (samples >= samples_old[16*j +: 16]), s); + $sformat(s, + "Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i, + samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4, + samples[16*j +: 16]); + `ASSERT_ERROR( + (samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) && + (samples >= samples_old[16*j +: 16]), s); end samples_old = samples; end @@ -271,6 +285,146 @@ module rfnoc_block_ddc_tb(); join endtask + //--------------------------------------------------------------------------- + // Send tone + //--------------------------------------------------------------------------- + // Generates samples for a sine wave tone and sends them to the specified BFM + // port. + // + // Parameters: + // port: Port number + // decim_rate: Decimation rate + // tone_ampl: Amplitude of generated sine wave + // tone_freq_norm: Normalized frequency of generated sine wave + // dsp_tune_norm: Normalized digital frequency shift + // num_samps_to_send: Number of samples to send + // restrict_input: Coerce input tone samples to 16-bit signed range + // + // Note: The tone_freq_norm and dsp_tune_norm parameters must be in the range + // (-1/2, +1/2). + //--------------------------------------------------------------------------- + task automatic send_tone ( + input int unsigned port = 0, + input int unsigned decim_rate = 1, + input real tone_ampl = 0.9, + input real tone_freq_norm = 0.0, + input real dsp_tune_norm = 0.0, + input int num_samps_to_send = 1000, + input int restrict_input = 1 + ); + chdr_word_t recv_metadata[$]; + item_t recv_payload[$], send_payload[$]; + packet_info_t recv_pkt_info, send_pkt_info; + int unsigned total_decim, cic_decim; + real cic_gain_float; + logic [31:0] cic_gain; + logic [31:0] dsp_tune_word; + + + // Tuning word = F_shift/Fs * 2^32 (Shift freq must be [-Fs/2,Fs/2)) + dsp_tune_word = int'(dsp_tune_norm * 2.0**32); + $display("send_tone(): dsp_tune_word = %d", dsp_tune_word); + write_reg(port, rfnoc_block_ddc_i.SR_FREQ_ADDR, dsp_tune_word); + + // Calculate CIC gain + total_decim = decim_rate; + cic_decim = 1; + for (int i = 0; i < NUM_HB; i++) begin + if (total_decim > 1) begin + if (total_decim[0]) begin // Not divisible by 2 + cic_decim = total_decim; + break; + end else begin // Divisible by 2 + total_decim = total_decim/2; + cic_decim = total_decim; + end + end + end + // DDS Gain is 2.0 + cic_gain_float = 1/((2.0*(cic_decim**4))/(2.0**$clog2(cic_decim**4))); + cic_gain = int'((1 << 15)*cic_gain_float); + $display("send_tone(): CIC Rate = %d", cic_decim); + $display("send_tone(): CIC Gain = %f", cic_gain_float); + + set_decim_rate(port, decim_rate); + write_reg(port, rfnoc_block_ddc_i.SR_SCALE_IQ_ADDR, cic_gain); + + @(posedge rfnoc_block_ddc_i.ce_clk); + if (num_samps_to_send > 0) begin + fork + // Receive samples + begin + automatic item_t prev_item, curr_item; + real prev, curr; + do begin + blk_ctrl.recv_items_adv(port, recv_payload, recv_metadata, recv_pkt_info); + end while (recv_pkt_info.eob == 0); + //check for jumps in of I/Q data + prev_item = recv_payload.pop_front(); + foreach (recv_payload[recv_idx]) begin + curr_item = recv_payload.pop_front(); + // Compare previous sample data to current sample data. If delta is + // more than half of the 16-bit signed value range(e.g. SHORT_MAX), + // we assume an arithmetic overflow has occurred. + prev = real'(signed'(prev_item[SAMP_W/2+:SAMP_W/2])); + curr = real'(signed'(curr_item[SAMP_W/2+:SAMP_W/2])); + `ASSERT_ERROR((Math#(real)::abs(curr-prev) < SHORT_MAX/2), + "Detected jump in I data."); + prev = real'(signed'(prev_item[0+:SAMP_W/2])); + curr = real'(signed'(curr_item[0+:SAMP_W/2])); + `ASSERT_ERROR((Math#(real)::abs(curr-prev) < SHORT_MAX/2), + "Detected jump in Q data."); + prev_item = curr_item; + end + end + // Send tone + begin + typedef logic [15:0] logic_t; //needed for typecasting to packed logic array + automatic real i_float, q_float; + automatic logic [15:0] i, q; + automatic longint phase = 0; + + send_pkt_info = '{ + vc : 0, + eov : 1'b0, + eob : 1'b0, + has_time : 1'b0, + timestamp : 64'd0 + }; + + while (num_samps_to_send > 0) begin + send_payload = {}; // Clear out previous iteration's samples + for (int n = 0; n < SPP; n++) begin + i_float = tone_ampl*(2.0**15)*$cos(2*PI*phase*tone_freq_norm); + q_float = tone_ampl*(2.0**15)*$sin(2*PI*phase*tone_freq_norm); + phase++; + // if restrict_input is set, we need to coerce the float values to + // 16-bit signed values instead of cutting off the additional MSBs, + // ensuring a smooth transition in the tone. + if (restrict_input) begin + i = logic_t'(coerce_to_int16(i_float)); + q = logic_t'(coerce_to_int16(q_float)); + end else begin + i = logic_t'(i_float); + q = logic_t'(q_float); + end + send_payload.push_back({i,q}); + + num_samps_to_send--; + if (num_samps_to_send == 0) begin + break; + end + end + + send_pkt_info.eob = (num_samps_to_send == 0); + blk_ctrl.send_items(port, send_payload, {}, send_pkt_info); + blk_ctrl.wait_complete(port); + end + end + join + end + endtask + //--------------------------------------------------------------------------- // Test Process @@ -318,7 +472,23 @@ module rfnoc_block_ddc_tb(); read_user_reg(port, RB_NUM_HB, val64); `ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value"); read_user_reg(port, RB_CIC_MAX_DECIM, val64); - `ASSERT_ERROR(val64 == CIC_MAX_DECIM, "Register CIC_MAX_DECIM didn't read back expected value"); + `ASSERT_ERROR(val64 == CIC_MAX_DECIM, + "Register CIC_MAX_DECIM didn't read back expected value"); + test.end_test(); + end + + //------------------------------------------------------------------------- + // Test overflow + //------------------------------------------------------------------------- + begin + test.start_test("Test tone", 0.5ms); + send_tone(.port (0), + .decim_rate (2), + .tone_ampl (1.0), + .tone_freq_norm (1.0/1024.0), + .dsp_tune_norm (1.0/1024.0), + .num_samps_to_send(10000), + .restrict_input (1)); test.end_test(); end diff --git a/lib/rfnoc/ddc.v b/lib/rfnoc/ddc.v index f9a0ebd..6de8d83 100644 --- a/lib/rfnoc/ddc.v +++ b/lib/rfnoc/ddc.v @@ -67,6 +67,7 @@ module ddc #( wire phase_tvalid, phase_tready, phase_tlast; wire dds_out_tlast; wire dds_out_tvalid; + wire dds_out_tready; wire [15:0] dds_input_fifo_space, dds_input_fifo_occupied; wire [17:0] scale_factor; @@ -271,17 +272,29 @@ module ddc #( /* IQ output */ .m_axis_dout_tlast(dds_out_tlast), .m_axis_dout_tvalid(dds_out_tvalid), - .m_axis_dout_tready(ddc_chain_tready), + .m_axis_dout_tready(dds_out_tready), .m_axis_dout_tdata({dds_out_q_tdata, dds_out_i_tdata}) ); - //48 = WIDTH*2 - //chop off top byte because it's not actually used and we want to match expected gain/bit use found in freq shift - assign i_dds_clip = {dds_out_i_tdata[15:0],8'h00}; - assign q_dds_clip = {dds_out_q_tdata[15:0],8'h00}; - assign strobe_dds_clip = dds_out_tvalid & sample_out_tready; - assign last_cic_decimate_in = dds_out_tlast; + // Drop MSBs to match expected gain/bit use found in freq shift + axi_clip_complex #( + .WIDTH_IN (WIDTH), + .WIDTH_OUT (WIDTH-8), + .FIFOSIZE (0)) + axi_clip_complex_post_dds ( + .clk(clk), + .reset(reset | clear), + .i_tdata({dds_out_i_tdata, dds_out_q_tdata}), + .i_tlast(dds_out_tlast), + .i_tvalid(dds_out_tvalid), + .i_tready(dds_out_tready), + .o_tdata({i_dds_clip[WIDTH-1:8], q_dds_clip[WIDTH-1:8]}), + .o_tlast(last_cic_decimate_in), + .o_tvalid(strobe_dds_clip), + .o_tready(ddc_chain_tready)); + assign i_dds_clip[7:0] = 8'h00; + assign q_dds_clip[7:0] = 8'h00; /** CIC DECIMATE **/ cic_decimate #(.WIDTH(WIDTH), .N(4), .MAX_RATE(CIC_MAX_DECIM)) cic_decimate_i (