Frequency issue with CIC filter on Au

Hello!

I’m trying to to make a simple PDM microphones array DSP using my Au using verilog.

I’m using verilog instead of Lucid for a few reasons :

  • I try to learn verilog
  • I want to learn simulation tools (for now Vivado and Questa)
  • I aim to make the design portable to other FPGAs
  • I may ultimately build a custom board using a much smaller FPGA or CPLD

So far, I try to set up a single mic with a single CIC filter and I can :

  • Generate the PDM clock at 3.072 MHz and read data from mic. (using clock wizard)
  • Configured the Xilinx CIC filter IP (5 stages with 64 decimation ration, 16 bits) => should result in 48 kHz audio rate
  • Routed the CIC output to the Ft Element board
  • Pull the data from the Ft on PC to a raw file

The CIC frames are fed directly into the ft600_write_only module from Labs and then read by a very simple streaming app that just issue blocking bulks reads and dump anything read into a raw file.

I can import this raw file into audacity and I get a working audio signal, but I have an issue :
The audio sounds accelerated. The resulting samplerate seems to be around 32kHz instead of 48 kHz.

I looked into a potential synchronisation issue, but couldn’t find anything obvious.

Thanks to the recent Xilinx move of gifting us with the ability to use the Au with Vivado, I used the Xilinx ILA module to capture the real-life internal signals from the Au.

From the graph, I can see that the PDM clock and synchronisation looks fine, and the CIC is generating frames every 2083 ticks (so 48 kHz).

I don’t understand why this is hapenning…

Do I need to keep “ui_din_valid” asserted for more than 1 clock cycle?
Could it be the whole board running too slow? (100 MHz clock beeing bad?)

I can provide the projet files if needed.

Thanks :slight_smile:

Edit: Here is a screen of the timings captured using the ILA (note : ft_ready is low because the FT600 is not beeing read) :

Edit 2:
I Used my PC to play a 440 Hz sine wave and record it through the design.
It looks like the output sample frequency I get is around 34864 Hz and slightly fluctuating (I can clearly hear the note going slightly up and down).

Any idea?

What clock are you using to run your design? The FT clock?

If you’re using the FT clock, know that it can be configured in software to be 66MHz which would be suspiciously perfect for 32KHz instead of 48KHz.

Make sure whatever you’re using to read the FT600 sets the config to use 100MHz and not 66MHz.

My design is clocked by the main 100 MHz sysclock and relies on your Lucid module (from the sample project) to do the sync work using the internal FIFO.

I tried your lucid FIFO and the Xilinx FIFO with the same result.

Today I tried to replace my whole design with
a basic clock divider => an edge detector => a 16 bits counter => ft600.

With this, I should get a pure seesaw waveform with a period of 65536 samples, right?
I do get a seesaw but when zooming enough on it, it looks a bit decayed and have a period of 42749 samples :open_mouth:
Here is a zoomed portion of the resulting wave :

As you can see, it’s not straight as it should be, as if some frame were lost.

I checked the config of the FT600 as you suggested, here is what I get :

Can you post the version of the ft600_write_only module you’re using?

Give this one a try. I found on a board with a FT601 that adding the tx_empty dff was necessary for timing. I didn’t notice this issue on the Ft but worth a shot.

module ft600_write_only (
    input clk,
    input rst,  // reset
    input ft_clk,
    input ft_rxf,
    input ft_txe,
    output ft_data[16],
    output ft_be[2],
    output ft_rd,
    output ft_wr,
    output ft_oe,
    input ui_din[16],
    input ui_din_valid,
    output ui_full
  ) {
  
  .clk(ft_clk) {
    dff tx_empty;
  }
  
  fifo_generator_0 write_fifo(.rst(rst), .wr_clk(clk), .rd_clk(ft_clk));
  
  always {
    write_fifo.wr_en = ui_din_valid;
    write_fifo.din = ui_din;
    ui_full = write_fifo.full;
  }
  
  always {
    // default values
    ft_oe = 1;
    ft_rd = 1;
    ft_wr = 1;
    ft_data = write_fifo.dout;
    ft_be = 2b11;
    
    write_fifo.rd_en = 0;
    
    tx_empty.d = ft_txe;
    
    if (tx_empty.q == 0 && write_fifo.empty == 0) {
      ft_wr = 0;
      write_fifo.rd_en = ft_txe == 0;
    } 
  }
}

I used this version so far :

module ft600_write_only (
    input clk,
    input rst,  // reset
    input ft_clk,
    input ft_rxf,
    input ft_txe,
    output ft_data[16],
    output ft_be[2],
    output ft_rd,
    output ft_wr,
    output ft_oe,
    input ui_din[16],
    input ui_din_valid,
    output ui_full
  ) {
  
  .clk(ft_clk) {
   // reset_conditioner ft_rst_cond(.in(rst));
  }
  
  //async_fifo_fix write_fifo(#SIZE(16), #DEPTH(512), .rclk(ft_clk), .rrst(ft_rst_cond.out), .wclk(clk), .wrst(rst));
  fifo_generator_0 write_fifo(.rd_clk(ft_clk), .wr_clk(clk), .rst(rst));
  
  always {
    write_fifo.wr_en = ui_din_valid;
    write_fifo.din = ui_din;
    ui_full = write_fifo.full;
  }
  
  always {
    // default values
    ft_oe = 1;
    ft_rd = 1;
    ft_wr = 1;
    ft_data = write_fifo.dout;
    ft_be = 2b11;
    
    write_fifo.rd_en = 0;
    
    if (ft_txe == 0 && write_fifo.empty == 0) {
      ft_wr = 0;
      write_fifo.rd_en = 1;
    } 
  }
}

I will try the new one now.

Now that looks MUCH better :open_mouth:
image
But I have a period of 60212 samples, which is much better but still missing some…

So something that the base FT example code does wrong is that in order to not drop the last 16 bits of written data, you need to hold the write enable for the cycle after your last write before disabling it. Maybe that’s what is causing you to drop data, if you haven’t already found and fixed that issue?

I’d suggest sending sequential values then reading them on the PC side to see if there is any obvious pattern on what is missing.

@Jflanagan I don’t believe this is true. In the datasheet (page 17), you see wr_n being held down an extra cycle but I think this is just to illustrate you can hold it down when txe_n is 1 and it is ignored. The updated module I posted above does this for timing since you shouldn’t make wr_n depend directly on the current state of txe_n. That would double the signal delay (there and back).

I don’t see any other mention of holding it down for a dummy cycle when txe_n is still 0.

Well, what I can tell you is that i did in fact look at what data was missing when I was sending batches of data, and it was always the last 16 bits of any given transmission, and it stopped dropping them (and successfully transmitted my data stream with 100% accuracy) when I held wr_n down for exactly one cycle after my last 16 bit transmit.

I doubt you will believe me until you try it, but that’s what I did, and it worked.

Actually looking at my design, I’m not sure if I believe me either. I need to revisit what’s going on closely. I definitely had a problem where the last 16 bits of a transmit got dropped, but the current state of my design doesn’t do what I thought it does to fix it? I retract any claim of understanding what’s going on here!

You were likely running into timing problems that my modified version fixes. Basically, you don’t want any of the output signals to be directly based on any of the input signals as that more than doubles the delay.