JulianKemmerer / PipelineC

A C-like hardware description language (HDL) adding high level synthesis(HLS)-like automatic pipelining as a language construct/compiler feature.
https://github.com/JulianKemmerer/PipelineC/wiki
GNU General Public License v3.0
606 stars 50 forks source link

How does pipelineC deal with Floating point and pipeline in the FIR filter? #207

Closed yourcomrade closed 1 month ago

yourcomrade commented 1 month ago

Hi all, I'm interested in using pipelineC for FIR filter with floating point. As far as I know, the pipelineC seems to support floating point addition and multiplication. However, I don't understand how pipelineC generates floating point core? How much optimize and how depth the pipeline of the floating point adder and multiplier? If I write an FIR filter like this:


// Function to implement the FIR filter
void fir_filter(float x, float *y, const float coeffs[N]) {
    // Shift register for input samples
    static float shift_reg[N] = {0};

    // Accumulate the output
    float acc = 0.0;

    // Shift and update the shift register
    for (int i = N-1; i > 0; i--) {
        shift_reg[i] = shift_reg[i-1];
    }
    shift_reg[0] = x;

    // FIR calculation
    for (int i = 0; i < N; i++) {
        acc += shift_reg[i] * coeffs[i];
    }

    // Output the result
    *y = acc;
}

, will the pipelineC auto-correct pipeline for me in the generated Verilog? BTW, I'm targeting the Arty board.

JulianKemmerer commented 1 month ago

Hey there :wave: you are close to having the right code. Yes PipelineC supports floating point math and will autopipeline for you. Faster target clock rates will require more pipeline stages / longer latency. The tool first produces VHDL, and then another conversion layer --verilog via open source ghdl and yosys tools can produce Verilog.

First minor point is no pointers, all compile time fixed size arrays (i.e. #define SIZE stuff typically).

Important things to note is the static float shift_reg[N] state causes the entire function it lives inside to not be pipelined. So the first thing we've done before is wrap that static shifting window in its own function. Looking like (using a stream with valid flag for each sample):

fir_samples_window_t fir_samples_window(fir_data_stream_t input)
{
  static fir_samples_window_t window;
  //fir_samples_window_t rv = window;
  if(input.valid){
    uint32_t i;
    for(i=(FIR_N_TAPS-1); i>0; i=i-1)
    {
      window.data[i] = window.data[i-1];
    }
    window.data[0] = input.data;
  }
  return window;
}

From there if you have just the fir math pure function (mults, adder tree) using that window looked like:

fir_out_stream_t fir_name(fir_data_stream_t input)
{
  // buffer up N datas in shift reg
  fir_samples_window_t sample_window = fir_samples_window(input);
  // compute FIR func on the sample window
  fir_out_stream_t rv;
  rv.valid = input.valid;
  rv.data = fir(sample_window.data);
  return rv;
}

All of that and multipliers and binary tree of adders for inside fir is wrapped up in a helper library header-as-macro kind of thing fir.h. An example of using fir.h is in fir.c. The examples so far have used fixed point int types, but specifying float types in the FIR configuration should work too.

A full example using several different types of FIRs (decimation, interpolation, using fewer resource variants, etc) is shown as part of the bigger FM radio demod project .

And finally can also find help on the PipelineC Discord DSP Channel.

Thanks for reaching out