fpga: rfnoc: Fix DDC overflow issue

Fixed an issue where the DDC could experience an arithmetic overflow
when receiving a full-scale signal with non-zero DDS frequency.


Original-commit: 5030d0106b6d2230a1c28e72f765098c7916cf1b
This commit is contained in:
Niels Garibaldi
2025-02-26 22:09:04 -06:00
committed by Wade Fife
parent 2535aa4f1b
commit cfa8afcf37
2 changed files with 201 additions and 18 deletions
@@ -17,6 +17,8 @@ module rfnoc_block_ddc_tb();
import PkgTestExec::*; import PkgTestExec::*;
import PkgChdrUtils::*; import PkgChdrUtils::*;
import PkgRfnocBlockCtrlBfm::*; import PkgRfnocBlockCtrlBfm::*;
import PkgRfnocItemUtils::*;
import PkgMath::*;
`include "rfnoc_block_ddc_regs.vh" `include "rfnoc_block_ddc_regs.vh"
@@ -43,7 +45,6 @@ module rfnoc_block_ddc_tb();
localparam int CIC_MAX_DECIM = 255; localparam int CIC_MAX_DECIM = 255;
localparam int NOC_ID = 32'hDDC00000; localparam int NOC_ID = 32'hDDC00000;
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
// Clocks // 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)::chdr_word_t chdr_word_t;
typedef ChdrData #(CHDR_W, SAMP_W)::item_t item_t;
RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk); RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk);
AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0); AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0);
@@ -185,15 +188,18 @@ module rfnoc_block_ddc_tb();
); );
`ASSERT_ERROR( `ASSERT_ERROR(
cic_rate > 0 && cic_rate <= CIC_MAX_DECIM, 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 // Setup DDC
$display("Set decimation to %0d", decim_rate); $display("Set decimation to %0d", decim_rate);
$display("- Number of enabled HBs: %0d", hb_enables); $display("- Number of enabled HBs: %0d", hb_enables);
$display("- CIC Rate: %0d", cic_rate); $display("- CIC Rate: %0d", cic_rate);
write_reg(port, SR_N_ADDR, decim_rate); // Set decimation rate in AXI rate change // Set decimation rate in AXI rate change
write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate}); // Enable HBs, set CIC rate write_reg(port, SR_N_ADDR, decim_rate);
// Enable HBs, set CIC rate
write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate});
endtask endtask
@@ -219,7 +225,9 @@ module rfnoc_block_ddc_tb();
pkt_info = 0; pkt_info = 0;
for (int i = 0; i < decim_rate*(PKT_SIZE_BYTES/8 + extra_samples); i++) begin 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 end
$display("Send ramp (%0d words)", send_payload.size()); $display("Send ramp (%0d words)", send_payload.size());
pkt_info.eob = 1; pkt_info.eob = 1;
@@ -248,10 +256,12 @@ module rfnoc_block_ddc_tb();
`ASSERT_ERROR(pkt_info.eob == 1'b1, s); `ASSERT_ERROR(pkt_info.eob == 1'b1, s);
recv_payload = {temp_payload, recv_payload}; recv_payload = {temp_payload, recv_payload};
if (drop_partial_packet) begin 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); `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s);
end else begin 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); `ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8 + extra_samples, s);
end end
samples = 64'd0; samples = 64'd0;
@@ -260,9 +270,13 @@ module rfnoc_block_ddc_tb();
samples = recv_payload[i]; samples = recv_payload[i];
for (int j = 0; j < 4; j++) begin for (int j = 0; j < 4; j++) begin
// Need to check a range of values due to imperfect gain compensation // 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, $sformat(s,
samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4, samples[16*j +: 16]); "Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i,
`ASSERT_ERROR((samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) && (samples >= samples_old[16*j +: 16]), s); 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 end
samples_old = samples; samples_old = samples;
end end
@@ -271,6 +285,146 @@ module rfnoc_block_ddc_tb();
join join
endtask 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 // Test Process
@@ -318,7 +472,23 @@ module rfnoc_block_ddc_tb();
read_user_reg(port, RB_NUM_HB, val64); read_user_reg(port, RB_NUM_HB, val64);
`ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value"); `ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value");
read_user_reg(port, RB_CIC_MAX_DECIM, val64); 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(); test.end_test();
end end
+20 -7
View File
@@ -67,6 +67,7 @@ module ddc #(
wire phase_tvalid, phase_tready, phase_tlast; wire phase_tvalid, phase_tready, phase_tlast;
wire dds_out_tlast; wire dds_out_tlast;
wire dds_out_tvalid; wire dds_out_tvalid;
wire dds_out_tready;
wire [15:0] dds_input_fifo_space, dds_input_fifo_occupied; wire [15:0] dds_input_fifo_space, dds_input_fifo_occupied;
wire [17:0] scale_factor; wire [17:0] scale_factor;
@@ -271,17 +272,29 @@ module ddc #(
/* IQ output */ /* IQ output */
.m_axis_dout_tlast(dds_out_tlast), .m_axis_dout_tlast(dds_out_tlast),
.m_axis_dout_tvalid(dds_out_tvalid), .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}) .m_axis_dout_tdata({dds_out_q_tdata, dds_out_i_tdata})
); );
//48 = WIDTH*2 // Drop MSBs to match expected gain/bit use found in freq shift
//chop off top byte because it's not actually used and we want to match expected gain/bit use found in freq shift axi_clip_complex #(
assign i_dds_clip = {dds_out_i_tdata[15:0],8'h00}; .WIDTH_IN (WIDTH),
assign q_dds_clip = {dds_out_q_tdata[15:0],8'h00}; .WIDTH_OUT (WIDTH-8),
assign strobe_dds_clip = dds_out_tvalid & sample_out_tready; .FIFOSIZE (0))
assign last_cic_decimate_in = dds_out_tlast; 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 **/
cic_decimate #(.WIDTH(WIDTH), .N(4), .MAX_RATE(CIC_MAX_DECIM)) cic_decimate_i ( cic_decimate #(.WIDTH(WIDTH), .N(4), .MAX_RATE(CIC_MAX_DECIM)) cic_decimate_i (