0
\$\begingroup\$

I'm working on a project and I need to write a Pulse Integration block for RADAR project.
To do it I using some kind of buffer and in the first loop insert the data input to it: buffer[0]=din.
afterwards every loop I adding the buffer content with the new data input: buffer[i]=buffer[i]+din.
the buffer width is the estimated pulse length [~1024 bins],
the loops counts is the requested factor for pulse integration [32 or 64 pulses].
Schematic: enter image description here

My code:

`timescale 1ns/100ps   

module pulseint
(
    input                   clk,            // Clock Syncronized with ADC Buffer Output
    input                   rst_n,          // Reset Negative
    input                   factor,         // Factor: 0=32, 1=64
    input signed  [IW-1:0]  din,
    input                   div,
    output signed [OW-1:0]  dout,
    output                  dov
);  
//! ---------------- Parameters && Signals && initialization ---------------- !//
//* Parameters:
    ............
    
//* Signals: 
    // FSM Signals:
    ............
    
    // Buffer Phase Signals:
   .............
    
    // Divide Phase Signals:
    .............
    
    // Data Output Phase Signals:
    .............
    
//* Initialization: 
    initial begin
        ............
    end
    
//! -------------------------------- Logic -------------------------------- !//
    
    //* Encode the factor requested to: (1)Loops needed for Integration. (2)Shift needed to divide.
    always @*
        casex (factor)
            1'b1:   loops   <=  64; 
            1'b0:   loops   <=  32; 
        endcase
        
    always @*
        casex (factor)
            1'b1:   shift   <=  6;  
            1'b0:   shift   <=  5;  
        endcase
    
    
    //* Delay Block:
    always @(posedge clk, negedge rst_n) begin
        if (~rst_n) begin
            din_delay <= {IW{1'b0}};
            div_delay <= 1'b0;
        end else begin
            din_delay <= din;
            div_delay <= div;
        end
    end 
    
    //* State Machine Blocks:
    always @(state)  begin
        case (state)
            INIT: begin
                fp_f = 1'b0;
                w_f  = 1'b0;
                lp_f = 1'b0;                
                d_f  = 1'b0;
            end
            FIRST_PULSE: begin
                fp_f = 1'b1;
                w_f  = 1'b0;
                lp_f = 1'b0;                
                d_f  = 1'b0;
            end
            WORKING: begin
                fp_f = 1'b0;
                w_f  = 1'b1;
                lp_f = 1'b0;                
                d_f  = 1'b0;
            end
            LAST_PULSE: begin
                fp_f = 1'b0;
                w_f  = 1'b0;
                lp_f = 1'b1;
                d_f  = 1'b0;
            end
            DONE: begin
                fp_f = 1'b0;
                w_f  = 1'b0;
                lp_f = 1'b0;                
                d_f  = 1'b1;
            end
            default: begin
                fp_f = 1'b0;
                w_f  = 1'b0;
                lp_f = 1'b0;                
                d_f  = 1'b0;
            end
        endcase
    end

    always @(posedge clk or negedge rst_n) begin
        if (~rst_n)
            state <= INIT;
        else
            case (state)
                INIT:
                    if (div && loop==0)
                        state <= FIRST_PULSE;
                    else
                        state <= INIT;
                FIRST_PULSE:
                    if (loop==1)
                        state <= WORKING;
                WORKING:
                    if (loop==(loops-1))
                        state <= LAST_PULSE;
                LAST_PULSE:
                    if (loop==loops)
                        state <= DONE;
                DONE:
                    state <= INIT;
                default:
                    state <= INIT;
            endcase
    end
    
    //* Buffer Logic:
    
    // Bins Counter:
    always @(posedge clk, negedge rst_n) begin
        if (~rst_n) begin
            bin        <= {BCL{1'b0}};
            bin_dly    <= {BCL{1'b0}};
        end else begin
            if ((bin>=BINS-1) || (~div&&div_delay)) begin
                bin     <= {BCL{1'b0}};
                bin_dly <= {BCL{1'b0}};
            end else begin
                if (div) begin
                    bin     <= bin+1'b1;
                    bin_dly <= bin;
                end
            end
        end
    end
    
    // Loops Counter:
    always @(posedge clk, negedge rst_n) begin
        if (~rst_n) begin
            loop <= {LCL{1'b0}}; 
        end else begin
            if ((bin>=BINS-1) || (~div&&div_delay))
                loop <= loop+1'b1;
            if (loop==loops)
                loop <= {LCL{1'b0}};
        end
    end
    
    // Decoder to (1) Allign and Sum the data in the Buffer.
    //            (2) Move the data in the last loop to divide_reg.
    always @(posedge clk, negedge rst_n) begin
        if (~rst_n) begin
            for (index = 0; index<(BINS-1); index=index+1) begin
                buffer[index]   <= {AW{1'b0}};
            end
            divide_rdy <= 1'b0;
            divide_reg <= {AW{1'b0}};
        end else begin
            case ({fp_f, w_f, lp_f, d_f})
                4'b0001: begin  
                    divide_reg <= {AW{1'b0}};   
                    divide_rdy <= 1'b0;                 
                end
                4'b0010: begin
                    if (div) begin
                        divide_reg <= buffer[bin_dly] + (din_delay[IW-1] ? {5'b11111, din_delay} : {5'b0, din_delay});
                        divide_rdy <= 1'b1;
                    end
                end
                4'b0100: begin
                    if (div)
                        buffer[bin_dly] <= buffer[bin_dly] + (din_delay[IW-1] ? {5'b11111, din_delay} : {5'b0, din_delay});
                end
                4'b1000: begin
                    if (div)
                        buffer[bin_dly] <= din_delay;
                end
                default: begin
                    for (index=0; index<(BINS-1); index=index+1) begin
                        buffer[index]   <= {AW{1'b0}};
                    end
                    divide_reg <= {AW{1'b0}};   
                    divide_rdy <= 1'b0; 
                end
            endcase
        end
    end
    

    // Divie the data in divide_reg and store the result in output register: dout_reg.
    always @(posedge clk, negedge rst_n) begin
        if (~rst_n) begin
            dout_reg <= {OW{1'b0}};
            dov_reg  <= 1'b0;
        end else begin
            if (divide_rdy) begin
                dout_reg <= divide_reg >> shift;
                dov_reg  <= 1'b1;               
            end else begin
                dout_reg <= {OW{1'b0}};
                dov_reg  <= 1'b0;   
            end
        end
    end


endmodule

In general.. this logic is working and Im successfully calculating the pulse integration of 32 or 64 pulses.
So what I'm asking for is some suggestions how to improve the logic of this block to work at higher clock speeds? for now this block can run at ~45MHz, but Im aiming towards 100MHz.
how I can make it happen? how can I pipeline this calculation?

Im have problem to pipeline it because the number of summation should be dynamic and the summation had feedback with the buffer: buffer[i]=buffer[i]+din.

Any leads on how I can improve this block?
I would be happy for any comment about my code
Thanks!!

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

First of all, I'm not sure how "successfully" you're calculating the pulse integration. I see a number of errors in your code. However, I can't run a simulation myself, because you elided some important declarations and also didn't include your testbench.

But as far as performance goes, I think the biggest issue is that you've forced buffer[] to be implemented entirely as FFs, rather than taking advantage of the dual-port block RAM resources (BRAM) that nearly every FPGA offers in some form.

Normally, when you declare an array, the synthesis tools will attempt to use BRAM to hold it, but if you do certain things like initialize the entire array in one clock, then BRAM can't be used. In your code (2 places), you have a for loop that sets the entire buffer to zero. This is completely unnecessary, because during the FIRST_PULSE state, you explicitly set the value of every buffer entry anyway, ignoring whatever previous value it might have had.

A pipelined implementation would use BRAM, using one of its two ports to read the previous data for a given index during one clock, and using the second port to write the updated data on the next clock. This should be capable of running at several hundred MHz. One of my own projects involves calculating histograms of video data at 150 MHz — structurally, a very similar problem — and this is the technique that I used.


Since I was intrigued by your problem, I went ahead and worked out my own implementation using BRAM and fully-pipelined read and write accesses. In the following diagram, rectangular boxes represent registers (including counters) and ovals represent combinatorial logic. You can see how the horizontal rows of registers form pipeline stages. "bram_read" and "bram_write" are two ports into the same BRAM that holds the "buffer" memory.

block diagram

And here's the code:

/* pulseint.v */

/* This module receives bursts of data representing received radar pulses, and
 * integrates 32 or 64 of them together to improve the SNR, outputting a single
 * pulse at the end. We assume that div is negated for at least one clock
 * between bursts.
 *
 * Dual-port BRAM is used to hold the sample data during integration. We reclock
 * (pipeline) the inputs and outputs to the BRAM (both ports) for maximum
 * throughput.
 *
 * The BRAM contents need to be scaled down (divided by 32 or 64, which is just
 * a right shift of the binary data). But rather than having an adjustable
 * shifter at the output, we pre-shift the data on the input instead, and use
 * a fixed shift (i.e., just wires) at the output, again for performance.
 *
 * The pipeline timing looks like this:
 *                   _____ _____ _____ _____ _____ ___
 * din         -----X__d0_X__d1_X__d2_X__d3_X__d4_X___
 *                   _________________________________
 * div         _____/
 *              __________ _____ _____ _____ _____ ___
 * buffer_raddr _______0__X__1__X__2__X__3__X__4__X___
 *              ________________ _____ _____ _____ ___
 * buffer_rdata _____________b0_X__b1_X__b2_X__b3_X___
 *                         _____ _____ _____ _____ ___
 * din_delay   -----------X__d0_X__d1_X__d2_X__d3_X___
 *                         ___________________________
 * div_delay   ___________/
 *             _________________ _____ _____ _____ ___
 * raddr_delay ______________0__X__1__X__2__X__3__X___
 *                               _____ _____ _____ ___
 * buffer_wdata ----------------X__s0_X__s1_X__s2_X___
 *              ______________________ _____ _____ ___
 * buffer_waddr ____________________0_X___1_X___2_X___
 *                               _____________________
 * buffer_we    ________________/
 *
 * On the last burst of a group, dout and dov have the same timing as
 * buffer_wdata and buffer_we, respectively.
 */

`timescale 1ns/100ps

module pulseint (
  input                   clk,          // ADC Buffer Output Clock
  input                   rst_n,        // Reset (active-low)
  input                   factor,       // Factor: 0=32, 1=64
  input signed  [IW-1:0]  din,          // Raw data in
  input                   div,          // Data in valid
  output reg signed [OW-1:0] dout,      // Integrated data out
  output reg              dov           // Data out valid
);

  parameter IW = 16;                    // Input data width
  parameter OW = 16;                    // Output data width

  parameter LCL = 7;                    // length of the loop counter register
  parameter BCL = 10;                   // length of the bins counter register
  localparam BINS = (1 << BCL);
  localparam MAX_SHIFT = LCL - 1;
  parameter MW = IW + MAX_SHIFT;        // memory data width

  wire [LCL-1:0] loops = factor ? 64 : 32;

  /* The BRAM
   */
  reg [MW-1:0] buffer [0:BINS-1];

  /* FIrst pipeline stage -- counters and input data processing
   */
  // Bins Counter (buffer read address):
  reg [BCL-1:0] buffer_raddr;
  always @(posedge clk) buffer_raddr <= div ? buffer_raddr + 1'b1 : {BCL{1'b0}};

  // Loops Counter:
  reg [LCL-1:0] loop;
  always @(posedge clk, negedge rst_n) begin
    if (~rst_n || (loop==loops)) begin
      loop <= {LCL{1'b0}};
    end else begin
      if ((buffer_raddr >= BINS-1) || (~div && div_delay)) loop <= loop + 1'b1;
    end
  end

  reg signed     [IW:0] din_delay;
  reg                   div_delay;
  reg signed   [MW-1:0] buffer_rdata;
  reg         [BCL-1:0] raddr_delay;
  reg         [LCL-1:0] loop_delay;
  always @(posedge clk) begin
    /* Shift data left by one if we're only doing 32 samples.
     */
    din_delay <= factor ? din : (din << 1);
    div_delay <= div;
    buffer_rdata <= buffer[buffer_raddr];
    raddr_delay <= buffer_raddr;
    loop_delay <= loop;
  end

  /* Second pipeline stage -- addition and selection of buffer write data
   */
  reg                   buffer_we;
  reg signed   [MW-1:0] buffer_wdata;
  reg         [BCL-1:0] buffer_waddr;

  wire                  first_loop = (loop == 0);
  wire                  last_loop = (loop == loops-1);
  wire signed  [MW-1:0] sum = buffer_rdata + din_delay;

  always @(posedge clk) begin
    buffer_we <= div_delay;
    buffer_wdata <= first_loop ? din_delay : sum;
    buffer_waddr <= raddr_delay;
    dout <= sum >> MAX_SHIFT;
    dov <= div_delay & last_loop;
  end

  /* Final pipeline stage -- write the updated data into the buffer
   */
  always @(posedge clk) begin
    if (buffer_we) buffer[buffer_waddr] <= buffer_wdata;
  end

endmodule
\$\endgroup\$
2
  • \$\begingroup\$ OK thanks on the comment! I will work on your suggestions and re-open or edit this post. BTW does FIFO is also a good choice or I should use BRAM only? using RAM makes it harder because it takes 1 clock cycle to write and 1 clock cycle to read from RAM no? it will change the logic completely \$\endgroup\$ Jul 19, 2020 at 12:29
  • \$\begingroup\$ I don't think using a FIFO would be any simpler. It's BRAM anyway, and you'd still have to account for the fall-through latency in your logic, which could potentially be more confusing rather than less. Stick with using the BRAM directly, where you have explicit control over everything it does. \$\endgroup\$
    – Dave Tweed
    Jul 19, 2020 at 12:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.