/ 9_Firmware / 9_2_FPGA / doppler_processor.v
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