doppler_processor.v
1 `timescale 1ns / 1ps 2 3 // ============================================================================ 4 // doppler_processor.v — Staggered-PRF Doppler Processor (CORRECTED) 5 // ============================================================================ 6 // 7 // ARCHITECTURE: 8 // This module implements dual 16-point FFTs for the AERIS-10 staggered-PRF 9 // waveform. The radar transmits 16 long-PRI chirps followed by 16 short-PRI 10 // chirps per frame (32 total). Rather than a single 32-point FFT over the 11 // non-uniformly sampled frame (which is signal-processing invalid), this 12 // module processes each sub-frame independently: 13 // 14 // Sub-frame 0 (long PRI): chirps 0..15 → 16-pt windowed FFT 15 // Sub-frame 1 (short PRI): chirps 16..31 → 16-pt windowed FFT 16 // 17 // Each sub-frame produces 16 Doppler bins per range bin. The outputs are 18 // tagged with a sub_frame bit and the 4-bit bin index is packed into the 19 // existing 5-bit doppler_bin port as {sub_frame, bin[3:0]}. 20 // 21 // This architecture enables downstream staggered-PRF ambiguity resolution: 22 // the same target velocity maps to DIFFERENT Doppler bins at different PRIs, 23 // and comparing the two sub-frame results resolves velocity ambiguity. 24 // 25 // INTERFACE COMPATIBILITY: 26 // The port list is a superset of the original module. Existing instantiations 27 // that don't connect `sub_frame` will still work. The FORMAL ports are 28 // retained. CHIRPS_PER_FRAME must be 32 (16 per sub-frame). 29 // 30 // WINDOW: 31 // 16-point Hamming window (Q15), symmetric. Computed as: 32 // w[n] = 0.54 - 0.46 * cos(2*pi*n/15), n=0..15 33 // ============================================================================ 34 35 module doppler_processor_optimized #( 36 parameter DOPPLER_FFT_SIZE = 16, // FFT size per sub-frame (was 32) 37 parameter RANGE_BINS = 64, 38 parameter CHIRPS_PER_FRAME = 32, // Total chirps in frame (16+16) 39 parameter CHIRPS_PER_SUBFRAME = 16, // Chirps per sub-frame 40 parameter WINDOW_TYPE = 0, // 0=Hamming, 1=Rectangular 41 parameter DATA_WIDTH = 16 42 )( 43 input wire clk, 44 input wire reset_n, 45 input wire [31:0] range_data, 46 input wire data_valid, 47 input wire new_chirp_frame, 48 output reg [31:0] doppler_output, 49 output reg doppler_valid, 50 output reg [4:0] doppler_bin, // {sub_frame, bin[3:0]} 51 output reg [5:0] range_bin, 52 output reg sub_frame, // 0=long PRI, 1=short PRI 53 output wire processing_active, 54 output wire frame_complete, 55 output reg [3:0] status 56 57 `ifdef FORMAL 58 , 59 output wire [2:0] fv_state, 60 output wire [10:0] fv_mem_write_addr, 61 output wire [10:0] fv_mem_read_addr, 62 output wire [5:0] fv_write_range_bin, 63 output wire [4:0] fv_write_chirp_index, 64 output wire [5:0] fv_read_range_bin, 65 output wire [4:0] fv_read_doppler_index, 66 output wire [9:0] fv_processing_timeout, 67 output wire fv_frame_buffer_full, 68 output wire fv_mem_we, 69 output wire [10:0] fv_mem_waddr_r 70 `endif 71 ); 72 73 // ============================================== 74 // Window Coefficients — 16-point Hamming (Q15) 75 // ============================================== 76 // w[n] = 0.54 - 0.46 * cos(2*pi*n/15), n=0..15 77 // Symmetric: w[n] = w[15-n] 78 reg [DATA_WIDTH-1:0] window_coeff [0:15]; 79 80 integer w; 81 initial begin 82 if (WINDOW_TYPE == 0) begin 83 // 16-point Hamming window, Q15 format 84 // Computed: round(32767 * (0.54 - 0.46*cos(2*pi*n/15))) 85 window_coeff[0] = 16'h0A3D; // 0.0800 * 32767 = 2621 86 window_coeff[1] = 16'h0E5C; // 0.1116 * 32767 = 3676 87 window_coeff[2] = 16'h1B6D; // 0.2138 * 32767 = 7021 88 window_coeff[3] = 16'h3088; // 0.3790 * 32767 = 12424 89 window_coeff[4] = 16'h4B33; // 0.5868 * 32767 = 19251 90 window_coeff[5] = 16'h6573; // 0.7930 * 32767 = 25971 91 window_coeff[6] = 16'h7642; // 0.9245 * 32767 = 30274 92 window_coeff[7] = 16'h7F62; // 0.9932 * 32767 = 32610 93 window_coeff[8] = 16'h7F62; // symmetric 94 window_coeff[9] = 16'h7642; 95 window_coeff[10] = 16'h6573; 96 window_coeff[11] = 16'h4B33; 97 window_coeff[12] = 16'h3088; 98 window_coeff[13] = 16'h1B6D; 99 window_coeff[14] = 16'h0E5C; 100 window_coeff[15] = 16'h0A3D; 101 end else begin 102 for (w = 0; w < 16; w = w + 1) begin 103 window_coeff[w] = 16'h7FFF; 104 end 105 end 106 end 107 108 // ============================================== 109 // Memory Declaration - FIXED SIZE 110 // ============================================== 111 localparam MEM_DEPTH = RANGE_BINS * CHIRPS_PER_FRAME; 112 (* ram_style = "block" *) reg [DATA_WIDTH-1:0] doppler_i_mem [0:MEM_DEPTH-1]; 113 (* ram_style = "block" *) reg [DATA_WIDTH-1:0] doppler_q_mem [0:MEM_DEPTH-1]; 114 115 // ============================================== 116 // Control Registers 117 // ============================================== 118 reg [5:0] write_range_bin; 119 reg [4:0] write_chirp_index; 120 reg [5:0] read_range_bin; 121 reg [4:0] read_doppler_index; 122 reg frame_buffer_full; 123 reg [9:0] chirps_received; 124 reg [1:0] chirp_state; 125 126 // Sub-frame tracking 127 reg current_sub_frame; // 0=processing long, 1=processing short 128 129 // ============================================== 130 // FFT Interface 131 // ============================================== 132 reg fft_start; 133 wire fft_ready; 134 reg [DATA_WIDTH-1:0] fft_input_i; 135 reg [DATA_WIDTH-1:0] fft_input_q; 136 reg signed [31:0] mult_i, mult_q; 137 reg signed [DATA_WIDTH-1:0] window_val_reg; 138 reg signed [31:0] mult_i_raw, mult_q_raw; 139 140 reg fft_input_valid; 141 reg fft_input_last; 142 wire [DATA_WIDTH-1:0] fft_output_i; 143 wire [DATA_WIDTH-1:0] fft_output_q; 144 wire fft_output_valid; 145 wire fft_output_last; 146 147 // ============================================== 148 // Addressing 149 // ============================================== 150 wire [10:0] mem_write_addr; 151 wire [10:0] mem_read_addr; 152 153 assign mem_write_addr = (write_chirp_index * RANGE_BINS) + write_range_bin; 154 assign mem_read_addr = (read_doppler_index * RANGE_BINS) + read_range_bin; 155 156 // ============================================== 157 // State Machine 158 // ============================================== 159 reg [2:0] state; 160 localparam S_IDLE = 3'b000; 161 localparam S_ACCUMULATE = 3'b001; 162 localparam S_PRE_READ = 3'b101; 163 localparam S_LOAD_FFT = 3'b010; 164 localparam S_FFT_WAIT = 3'b011; 165 localparam S_OUTPUT = 3'b100; 166 167 // Frame sync detection 168 reg new_chirp_frame_d1; 169 always @(posedge clk or negedge reset_n) begin 170 if (!reset_n) new_chirp_frame_d1 <= 0; 171 else new_chirp_frame_d1 <= new_chirp_frame; 172 end 173 wire frame_start_pulse = new_chirp_frame & ~new_chirp_frame_d1; 174 175 // ============================================== 176 // Main State Machine 177 // ============================================== 178 reg [4:0] fft_sample_counter; // Reduced: only need 0..17 for 16-pt FFT 179 reg [9:0] processing_timeout; 180 181 // Memory write enable and data signals 182 reg mem_we; 183 reg [10:0] mem_waddr_r; 184 reg [DATA_WIDTH-1:0] mem_wdata_i, mem_wdata_q; 185 186 // Memory read data 187 reg [DATA_WIDTH-1:0] mem_rdata_i, mem_rdata_q; 188 189 `ifdef FORMAL 190 assign fv_state = state; 191 assign fv_mem_write_addr = mem_write_addr; 192 assign fv_mem_read_addr = mem_read_addr; 193 assign fv_write_range_bin = write_range_bin; 194 assign fv_write_chirp_index = write_chirp_index; 195 assign fv_read_range_bin = read_range_bin; 196 assign fv_read_doppler_index = read_doppler_index; 197 assign fv_processing_timeout = processing_timeout; 198 assign fv_frame_buffer_full = frame_buffer_full; 199 assign fv_mem_we = mem_we; 200 assign fv_mem_waddr_r = mem_waddr_r; 201 `endif 202 203 // ---------------------------------------------------------- 204 // Separate always block for memory writes — NO async reset 205 // ---------------------------------------------------------- 206 always @(posedge clk) begin 207 if (mem_we) begin 208 doppler_i_mem[mem_waddr_r] <= mem_wdata_i; 209 doppler_q_mem[mem_waddr_r] <= mem_wdata_q; 210 end 211 mem_rdata_i <= doppler_i_mem[mem_read_addr]; 212 mem_rdata_q <= doppler_q_mem[mem_read_addr]; 213 end 214 215 // ---------------------------------------------------------- 216 // Block 1: FSM / Control — async reset 217 // ---------------------------------------------------------- 218 always @(posedge clk or negedge reset_n) begin 219 if (!reset_n) begin 220 state <= S_IDLE; 221 write_range_bin <= 0; 222 write_chirp_index <= 0; 223 frame_buffer_full <= 0; 224 doppler_valid <= 0; 225 fft_start <= 0; 226 fft_input_valid <= 0; 227 fft_input_last <= 0; 228 fft_sample_counter <= 0; 229 processing_timeout <= 0; 230 status <= 0; 231 chirps_received <= 0; 232 chirp_state <= 0; 233 doppler_output <= 0; 234 doppler_bin <= 0; 235 range_bin <= 0; 236 sub_frame <= 0; 237 current_sub_frame <= 0; 238 end else begin 239 doppler_valid <= 0; 240 fft_input_valid <= 0; 241 fft_input_last <= 0; 242 243 if (processing_timeout > 0) begin 244 processing_timeout <= processing_timeout - 1; 245 end 246 247 case (state) 248 S_IDLE: begin 249 if (frame_start_pulse) begin 250 write_chirp_index <= 0; 251 write_range_bin <= 0; 252 frame_buffer_full <= 0; 253 chirps_received <= 0; 254 end 255 256 if (data_valid && !frame_buffer_full) begin 257 state <= S_ACCUMULATE; 258 write_range_bin <= 1; 259 end 260 end 261 262 S_ACCUMULATE: begin 263 if (data_valid) begin 264 if (write_range_bin < RANGE_BINS - 1) begin 265 write_range_bin <= write_range_bin + 1; 266 end else begin 267 write_range_bin <= 0; 268 write_chirp_index <= write_chirp_index + 1; 269 chirps_received <= chirps_received + 1; 270 271 if (write_chirp_index >= CHIRPS_PER_FRAME - 1) begin 272 frame_buffer_full <= 1; 273 chirp_state <= 0; 274 state <= S_PRE_READ; 275 fft_sample_counter <= 0; 276 write_chirp_index <= 0; 277 write_range_bin <= 0; 278 // Start with sub-frame 0 (long PRI chirps 0..15) 279 current_sub_frame <= 0; 280 end 281 end 282 end 283 end 284 285 S_PRE_READ: begin 286 // Prime BRAM pipeline for current sub-frame 287 // read_doppler_index already set in Block 2 to sub-frame base 288 fft_start <= 1; 289 state <= S_LOAD_FFT; 290 end 291 292 S_LOAD_FFT: begin 293 fft_start <= 0; 294 295 // Pipeline: 2 priming cycles + CHIRPS_PER_SUBFRAME data cycles 296 if (fft_sample_counter <= 1) begin 297 fft_sample_counter <= fft_sample_counter + 1; 298 end else if (fft_sample_counter <= CHIRPS_PER_SUBFRAME + 1) begin 299 fft_input_valid <= 1; 300 301 if (fft_sample_counter == CHIRPS_PER_SUBFRAME + 1) begin 302 fft_input_last <= 1; 303 state <= S_FFT_WAIT; 304 fft_sample_counter <= 0; 305 processing_timeout <= 1000; 306 end else begin 307 fft_sample_counter <= fft_sample_counter + 1; 308 end 309 end 310 end 311 312 S_FFT_WAIT: begin 313 if (fft_output_valid) begin 314 doppler_output <= {fft_output_q[15:0], fft_output_i[15:0]}; 315 // Pack: {sub_frame, bin[3:0]} 316 doppler_bin <= {current_sub_frame, fft_sample_counter[3:0]}; 317 range_bin <= read_range_bin; 318 sub_frame <= current_sub_frame; 319 doppler_valid <= 1; 320 321 fft_sample_counter <= fft_sample_counter + 1; 322 323 if (fft_output_last) begin 324 state <= S_OUTPUT; 325 fft_sample_counter <= 0; 326 end 327 end 328 329 if (processing_timeout == 0) begin 330 state <= S_OUTPUT; 331 end 332 end 333 334 S_OUTPUT: begin 335 if (current_sub_frame == 0) begin 336 // Just finished long PRI sub-frame — now do short PRI 337 current_sub_frame <= 1; 338 fft_sample_counter <= 0; 339 state <= S_PRE_READ; 340 // read_range_bin stays the same, read_doppler_index 341 // will be set to CHIRPS_PER_SUBFRAME in Block 2 342 end else begin 343 // Finished both sub-frames for this range bin 344 current_sub_frame <= 0; 345 if (read_range_bin < RANGE_BINS - 1) begin 346 fft_sample_counter <= 0; 347 state <= S_PRE_READ; 348 // read_range_bin incremented in Block 2 349 end else begin 350 state <= S_IDLE; 351 frame_buffer_full <= 0; 352 end 353 end 354 end 355 356 endcase 357 358 status <= {state, frame_buffer_full}; 359 end 360 end 361 362 // ---------------------------------------------------------- 363 // Block 2: BRAM address/data & DSP datapath — synchronous reset 364 // ---------------------------------------------------------- 365 always @(posedge clk) begin 366 if (!reset_n) begin 367 mem_we <= 0; 368 mem_waddr_r <= 0; 369 mem_wdata_i <= 0; 370 mem_wdata_q <= 0; 371 mult_i <= 0; 372 mult_q <= 0; 373 mult_i_raw <= 0; 374 mult_q_raw <= 0; 375 window_val_reg <= 0; 376 fft_input_i <= 0; 377 fft_input_q <= 0; 378 read_range_bin <= 0; 379 read_doppler_index <= 0; 380 end else begin 381 mem_we <= 0; 382 383 case (state) 384 S_IDLE: begin 385 if (data_valid && !frame_buffer_full) begin 386 mem_we <= 1; 387 mem_waddr_r <= mem_write_addr; 388 mem_wdata_i <= range_data[15:0]; 389 mem_wdata_q <= range_data[31:16]; 390 end 391 end 392 393 S_ACCUMULATE: begin 394 if (data_valid) begin 395 mem_we <= 1; 396 mem_waddr_r <= mem_write_addr; 397 mem_wdata_i <= range_data[15:0]; 398 mem_wdata_q <= range_data[31:16]; 399 400 if (write_range_bin >= RANGE_BINS - 1 && 401 write_chirp_index >= CHIRPS_PER_FRAME - 1) begin 402 read_range_bin <= 0; 403 // Start reading from chirp 0 (long PRI sub-frame) 404 read_doppler_index <= 0; 405 end 406 end 407 end 408 409 S_PRE_READ: begin 410 // Set read_doppler_index to first chirp of current sub-frame + 1 411 // (because address is presented this cycle, data arrives next) 412 if (current_sub_frame == 0) 413 read_doppler_index <= 1; // Long PRI: chirps 0..15 414 else 415 read_doppler_index <= CHIRPS_PER_SUBFRAME + 1; // Short PRI: chirps 16..31 416 417 // BREG priming: window coeff for sample 0 418 window_val_reg <= $signed(window_coeff[0]); 419 end 420 421 S_LOAD_FFT: begin 422 if (fft_sample_counter == 0) begin 423 // Pipe stage 1: multiply using pre-registered BREG value 424 mult_i_raw <= $signed(mem_rdata_i) * window_val_reg; 425 mult_q_raw <= $signed(mem_rdata_q) * window_val_reg; 426 window_val_reg <= $signed(window_coeff[1]); 427 // Advance to chirp base+2 428 if (current_sub_frame == 0) 429 read_doppler_index <= (2 < CHIRPS_PER_SUBFRAME) ? 2 430 : CHIRPS_PER_SUBFRAME - 1; 431 else 432 read_doppler_index <= (CHIRPS_PER_SUBFRAME + 2 < CHIRPS_PER_FRAME) 433 ? CHIRPS_PER_SUBFRAME + 2 434 : CHIRPS_PER_FRAME - 1; 435 end else if (fft_sample_counter == 1) begin 436 mult_i <= mult_i_raw; 437 mult_q <= mult_q_raw; 438 mult_i_raw <= $signed(mem_rdata_i) * window_val_reg; 439 mult_q_raw <= $signed(mem_rdata_q) * window_val_reg; 440 if (2 < CHIRPS_PER_SUBFRAME) 441 window_val_reg <= $signed(window_coeff[2]); 442 // Advance to chirp base+3 443 begin : advance_chirp3 444 reg [4:0] next_chirp; 445 next_chirp = (current_sub_frame == 0) ? 3 : CHIRPS_PER_SUBFRAME + 3; 446 if (next_chirp < CHIRPS_PER_FRAME) 447 read_doppler_index <= next_chirp; 448 else 449 read_doppler_index <= CHIRPS_PER_FRAME - 1; 450 end 451 end else if (fft_sample_counter <= CHIRPS_PER_SUBFRAME + 1) begin 452 // Steady state 453 fft_input_i <= (mult_i + (1 << 14)) >>> 15; 454 fft_input_q <= (mult_q + (1 << 14)) >>> 15; 455 mult_i <= mult_i_raw; 456 mult_q <= mult_q_raw; 457 458 if (fft_sample_counter <= CHIRPS_PER_SUBFRAME - 1) begin 459 mult_i_raw <= $signed(mem_rdata_i) * window_val_reg; 460 mult_q_raw <= $signed(mem_rdata_q) * window_val_reg; 461 // Window coeff index within sub-frame 462 begin : advance_window 463 reg [4:0] win_idx; 464 win_idx = fft_sample_counter[3:0] + 1; 465 if (win_idx < CHIRPS_PER_SUBFRAME) 466 window_val_reg <= $signed(window_coeff[win_idx]); 467 end 468 // Advance BRAM read 469 begin : advance_bram 470 reg [4:0] chirp_offset; 471 reg [4:0] chirp_base; 472 chirp_offset = fft_sample_counter[3:0] + 2; 473 chirp_base = (current_sub_frame == 0) ? 0 : CHIRPS_PER_SUBFRAME; 474 if (chirp_base + chirp_offset < CHIRPS_PER_FRAME) 475 read_doppler_index <= chirp_base + chirp_offset; 476 else 477 read_doppler_index <= CHIRPS_PER_FRAME - 1; 478 end 479 end 480 481 if (fft_sample_counter == CHIRPS_PER_SUBFRAME + 1) begin 482 // Reset read index for potential next operation 483 if (current_sub_frame == 0) 484 read_doppler_index <= CHIRPS_PER_SUBFRAME; // Ready for short sub-frame 485 else 486 read_doppler_index <= 0; 487 end 488 end 489 end 490 491 S_OUTPUT: begin 492 if (current_sub_frame == 0) begin 493 // Transitioning to short PRI sub-frame 494 // Set read_doppler_index to start of short sub-frame 495 read_doppler_index <= CHIRPS_PER_SUBFRAME; 496 end else begin 497 // Both sub-frames done 498 if (read_range_bin < RANGE_BINS - 1) begin 499 read_range_bin <= read_range_bin + 1; 500 read_doppler_index <= 0; // Next range bin starts with long sub-frame 501 end 502 end 503 end 504 505 default: begin 506 // S_FFT_WAIT: no BRAM-write or address operations needed 507 end 508 endcase 509 end 510 end 511 512 // ============================================== 513 // FFT Module — 16-point 514 // ============================================== 515 xfft_16 fft_inst ( 516 .aclk(clk), 517 .aresetn(reset_n), 518 .s_axis_config_tdata(8'h01), 519 .s_axis_config_tvalid(fft_start), 520 .s_axis_config_tready(fft_ready), 521 .s_axis_data_tdata({fft_input_q, fft_input_i}), 522 .s_axis_data_tvalid(fft_input_valid), 523 .s_axis_data_tlast(fft_input_last), 524 .m_axis_data_tdata({fft_output_q, fft_output_i}), 525 .m_axis_data_tvalid(fft_output_valid), 526 .m_axis_data_tlast(fft_output_last), 527 .m_axis_data_tready(1'b1) 528 ); 529 530 // ============================================== 531 // Status Outputs 532 // ============================================== 533 assign processing_active = (state != S_IDLE); 534 assign frame_complete = (state == S_IDLE && frame_buffer_full == 0); 535 536 endmodule