• 0
Sign in to follow this  
Ignacas

FPGA audio - ADC and DAC

Question

Good day wizards,

I've tried to introduce myself here, but now I would like to ask for a comment on my thoughts.

My goal is to master audio processing (mainly routing and level controls for a beginning) on FPGA. 

The diagram will be very simple:

Audio signal generator => ADC => FPGA => DAC => Analyzer (Spectrum, THD, Level)

Audio signal generator will be made of two NE555 clocks with different frequencies (say 1kHz and 15kHz) to have a difference between L and R channels.

ADC will be CS5381 (24bit@48k), I2S output.

DAC will be CS4390 (24bit@48k), I2S input. (later maybe something better, but for now I'll use whatever I have in a drawer).

Once I get this AD-DA conversion running properly, I'll try routing output of the ADC to my ARTY A7 input and pass that signal directly to the DAC. At this point I would like to see a low noise, low jitter signal passing thru.

Next step could be mixing L and R signals together, adding more converters generating AES/SPDIF signals on FPGA, etc..

 

But at very beginning, I have a fundamental problem with clocks. I want to run this setup at 48kHz, so I obviously need this clock and 48k*256=12.288MHz MSCLK.

Playing around with PLL Clock wizard didn't gave me the desired result (still + or - couple MHz). I understand that it would not be a massive problem and I could run any weird frequency, but there will be a sync problem with external digital equipment if I get around to do, say AES/SPDIF interface. Finding XTAL trimmed to 12.288 is not a problem, but can I just hook it up to any desired pin and use it? I have also seen some posts (if I got it right) discouraging of using multiple clocks as it can get messy (inter-sync problems?).

Before I dive into this, I would appreciate Your insights and critics. I will post all my story here as soon as I have something to share with You:)

Thank You!

 

 

Edited by Ignacas

Share this post


Link to post
Share on other sites

Recommended Posts

  • 0

@Ignacas

Your project idea sounds nifty to me. I like the idea of using parts from a drawer; this could be challenging or at least very interesting for experienced engineers. I like your idea of using separate audio DAC and audio ADC devices from a project perspective ( not necessarily from an audio perspective ).

I assume that you will be breadboarding your FPGA interface through the Arty regular PMOD. For best results you will have to pay attention to how this is accomplished. I suggest that you visit the Cirrus Logic website for application notes on this. Other audio IC vendors will have good guidance as well. As Cirrus Logic evaluation boards are priced to exclude the hobbyist element I assume that you will be breadboarding discrete chips. I'd make my own evaluation board using something like Express PCB. You may find this to be more expensive than you plan unless your parts drawer is stuffed with a lot of parts as buying small quantities of passive components isn't cost effective.

As far as clocking goes, you have already discovered that getting precise clock frequencies from an arbitrary input clock is not always possible using FPGA clock generator resources. The JD PMOD has a number of pins that connect to the Artix clock routing resources if you want to use the correct clock for your ICs.

As far as having multiple clocks is concerned the answer is more complex than I can address here. I'll suggest that you do your homework to understand the difference between derived and unrelated clocks as well as the general concepts in digital design. Basically, multiple clock domains can be tricky even for seasoned FPGA developers. If these clock domains are unrelated then it can be tricky for the Xilinx synthesis and place and route tools as well unless you know how to present them with good constraints. I'd start off keeping all of the ADC, DAC, processing etc. logic in one clock domain preferably at a clock frequency preferred by the devices in your design. As you get farther along in your project you can experiment with ways to move data into another clock domain for extracting information to say, a PC for post-processing.

This comment: "I'll try routing output of the ADC to my ARTY A7 input and pass that signal directly to the DAC. At this point I would like to see a low noise, low jitter signal passing thru" bothers me a little as your audio source is NE555 based, but I'm not sure what you mean by "low jitter" or "low noise".

Anyway, this could develop into quite an interesting enterprise; there are a lot of ways this could go in the future. Best wishes.

Edited by zygot

Share this post


Link to post
Share on other sites
  • 0

You only need a single 12.288MHz clock for the entire design. Generate the I2S bclk and lrclk signals using counters and registers clocked by the 12.288MHz clock. The Xilinx MMCM is capable of generating an exact 12.288MHz clock from the 100MHz clock input on the Arty board. If you need additional clock frequencies like 166.667MHz or 200MHz or something unrelated to 12.288MHz, you will need to use more than one MMCM in your design. Use a single clock wide enable in the 12.288MHz clock domain to indicate the presence of a new sample from or for your I2S interface.

It's been a very long time since I've looked at audio ADCs, DACs, and CODECs, but I seem to remember that most of the higher quality converters derive all their internal clocks from the 12.288MHz mclk clock and have internal PLLs to clean up any jitter on this 12.288MHz clock. The bclk and lrclk signals really are just signals used to frame the data and are not used as clocks for the converters.

Edited by bikerglen

Share this post


Link to post
Share on other sites
  • 0
14 hours ago, zygot said:

@Ignacas

Your project idea sounds nifty to me. I like the idea of using parts from a drawer; this could be challenging or at least very interesting for experienced engineers. I like your idea of using separate audio DAC and audio ADC devices from a project perspective ( not necessarily from an audio perspective ).I

I chose DACs and ADCs against combined codecs so I would not have to work on I2C to control it and I could easily replace it. Also there are not so many high end codecs and standalone converters scale better. 

I assume that you will be breadboarding your FPGA interface through the Arty regular PMOD. For best results you will have to pay attention to how this is accomplished. I suggest that you visit the Cirrus Logic website for application notes on this. Other audio IC vendors will have good guidance as well. As Cirrus Logic evaluation boards are priced to exclude the hobbyist element I assume that you will be breadboarding discrete chips. I'd make my own evaluation board using something like Express PCB. You may find this to be more expensive than you plan unless your parts drawer is stuffed with a lot of parts as buying small quantities of passive components isn't cost effective.

My first milestone is to get converters running, so it will land on breadboard first and then prototype PCB. I will definitely look at Express PCB, but previously I've used http://kicad-pcb.org/ 

As far as clocking goes, you have already discovered that getting precise clock frequencies from an arbitrary input clock is not always possible using FPGA clock generator resources. The JD PMOD has a number of pins that connect to the Artix clock routing resources if you want to use the correct clock for your ICs.

As far as having multiple clocks is concerned the answer is more complex than I can address here. I'll suggest that you do your homework to understand the difference between derived and unrelated clocks as well as the general concepts in digital design. Basically, multiple clock domains can be tricky even for seasoned FPGA developers. If these clock domains are unrelated then it can be tricky for the Xilinx synthesis and place and route tools as well unless you know how to present them with good constraints. I'd start off keeping all of the ADC, DAC, processing etc. logic in one clock domain preferably at a clock frequency preferred by the devices in your design. As you get farther along in your project you can experiment with ways to move data into another clock domain for extracting information to say, a PC for post-processing.

This comment: "I'll try routing output of the ADC to my ARTY A7 input and pass that signal directly to the DAC. At this point I would like to see a low noise, low jitter signal passing thru" bothers me a little as your audio source is NE555 based, but I'm not sure what you mean by "low jitter" or "low noise".

NE555 can give 50% duty cycle square and if its filtered properly, one can get quite good sinus signal on the output. Of course it can be replaced by any other generator, including soundcard. Unfortunately my Neutrik A1, died just a couple days ago.. :(((

Seeing that test signal at the other end of the FPGA would be what I am after. Unfortunately, because of the design, components, power, cabling, clocks etc., some noise, harmonic distortion will be present as well. 

Anyway, this could develop into quite an interesting enterprise; there are a lot of ways this could go in the future. Best wishes.

 

Share this post


Link to post
Share on other sites
  • 0

@bikerglen thanks for Your comment - I will definitely try Your I2S code. 

For me its still quite hard to understand the whole architecture and design patterns of FPGA programming. I still have to read a lot and many videos to watch before I even try it. But lets see how fast I can get it:))

I see You have some experience with audio, maybe You have an idea how to calculate I2S delay on FPGA? If its 48k 24bit signal then it takes 

1/48000 = 0.00002083333 s for every bit to arrive

0.00002083333 * 24 = 0.0005 s for every 24 bits and that is only deserialization, same for serialization.. so 1 ms only to pass it thru? That doesn't sound right.. 

What am I missing here? Must be something fundamental.

Share this post


Link to post
Share on other sites
  • 0

Bits are arriving at the much faster bclk rate. Entire samples are arriving at the lrclk rate. (lrclk rate == sample rate). Latency through the FPGA is going to be on the order of four or five samples—closer to 1/10 ms.

Share this post


Link to post
Share on other sites
  • 0

First thing to do is understand the data sheets for your audio DAC and ADC. The SCLK period for the CS5381 is 72-145 ns depending on the mode. The SCK frequency will define the data rate, the LRCK will define the digital sample rate including two channels of data. Actual delays will include the ADC and digital filters in the CS5381.

Share this post


Link to post
Share on other sites
  • 0

Good day everyone,

so I'm trying out @bikerglen i2s IP, but facing some general Vivado error even before that..

My current setup (Arty-7):

 

image.png.6d26d57041585665617097a9646a381d.png

But when generating bitstream im getting:

image.thumb.png.143a1a72f8dce9b5dd64caaaa14d14be.png

I was trying to google that one, but unfortunately its not clear why Vivado does not recognize my default declarations in .xdc file.. any ideas?

Edited by Ignacas

Share this post


Link to post
Share on other sites
  • 0

Hi,

split the set_property into two lines per pin: one for PACKAGE_PIN G13, one for IOSTANDARD LVCMOS33 (99 % certain I've tried myself to combine them and it didn't work).

Audio processing on FPGA is an uphill battle, but I can learn a lot from that one.

If I just write out the signal flow, e.g. channel and master faders on a mixing console, I'll run out of multipliers really quickly.

But since the sample rate is extremely low, I can use a dual port RAM-multiplier, state machine and mux a whole mixing console onto a single DSP block, channel filters and all. Taken to extremes, it's like programming in assembly language, except you're designing the language, the compiler and the computer at the same time besides the DSP algorithms :). Even a small FPGA can do a crazy amount of processing, but it's much more challenging than e.g. using a CPU.

I'd use a CODEC, not separate AD and DA. And I'd avoid "slipping" sample rates (e.g. between one crystal on the board and another on the PC end) at all costs, or at least do it in software on the PC. Fixed-ratio resampling (e.g. 44.1 <-> 48 kHz) is painful enough, variable-rate with e.g. a control loop around a Farrow stage is a very serious project in its own respect,

Share this post


Link to post
Share on other sites
  • 0

thanks for the comment, ive tried that now..

##Pmod Header JA

set_property -dict { PACKAGE_PIN G13 } [get_ports { sdata }];
set_property -dict { IOSTANDARD LVCMOS33 } [get_ports { sdata }]; #IO_0_15 Sch=ja[1]
set_property -dict { PACKAGE_PIN B11 } [get_ports { sclock }];
set_property -dict { IOSTANDARD LVCMOS33 } [get_ports { sclock }]; #IO_L4P_T0_15 Sch=ja[2]
set_property -dict { PACKAGE_PIN A11 } [get_ports { mlck }];
set_property -dict { IOSTANDARD LVCMOS33 } [get_ports { mlck }]; #IO_L4N_T0_15 Sch=ja[3]
set_property -dict { PACKAGE_PIN D12 } [get_ports { lrck }];
set_property -dict { IOSTANDARD LVCMOS33 } [get_ports { lrck }]; #IO_L6P_T0_15 Sch=ja[4]

but still getting the same :

[DRC NSTD-1] Unspecified I/O Standard: 136 out of 136 logical ports use I/O standard (IOSTANDARD) value 'DEFAULT', instead of a user assigned specific value. This may cause I/O contention or incompatibility with the board power or connectivity affecting performance, signal integrity or in extreme cases cause damage to the device or the components to which it is connected. To correct this violation, specify all I/O standards. This design will fail to generate a bitstream unless all logical ports have a user specified I/O standard value defined. To allow bitstream creation with unspecified I/O standard values (not recommended), use this command: set_property SEVERITY {Warning} [get_drc_checks NSTD-1].  NOTE: When using the Vivado Runs infrastructure (e.g. launch_runs Tcl command), add this command to a .tcl file and add that file as a pre-hook for write_bitstream step for the implementation run.

Problem ports: pdin_l[31:0], pdin_r[31:0], pdout_l[31:0], pdout_r[31:0], bclk, lrclk, mclk, pdin_req, pdout_ack, rst, sdin, and sdout.

Share this post


Link to post
Share on other sites
  • 0

If you copied it verbatim, the names don't match:

set_property ... lrck but "Problem ports": lrclk
mlck vs mclk

etc.

The idea is that every pin going out of your top level design is assigned a package pin and IOSTANDARD. Otherwise, VIVADO will randomly assign package pins, but it does not pick an IO standard and that ultimately leads to the error message.

 

 

Share this post


Link to post
Share on other sites
  • 0

I'm trying to construct different signal generators.. so I could play some data to my breadboarded dac.

The idea is to have a tick generator at, say 1kHz frequency and then different sort of samplers to play samples.

https://gist.github.com/ignazas/dc15fb4afd7e0204825baa359cd17bbb

This is what i have hacked together to get sinus and I also thoughts regarding sawtooth - this one could be generated as a simple counter but i dont know how to control the frequency..

Even if I have a predefined step(resolution), say (full scale)/256, i will get signal frequency 256 times slower than tick.. Any ideas how to sort that out? Do I absolutely need some sort of freq multiplier in same RTL to play samples faster?

Share this post


Link to post
Share on other sites
  • 0

If I take it to extremes, the answer is surprisingly complex (this is how it's done inside the clock management tiles or a cellphone, for example).

For household use, take (e.g.) a 32 bit counter and add "delta" at a high frequency, e.g. 100 MHz. Don't check for overflow, it will wrap around cyclically (which is the "correct" way to behave in this application. E.g. 0xFFFFFFFF + 3 becomes 0x00000002)

For example, at 100 MHz clock, a delta of 1 gives a cycle time of 42.9 seconds (2^32 / 100e6).
A delta of 43 gives a cycle time of one second. A delta of 42950 gives a cycle time of exactly 1 ms => 1 kHz.

Now we've got a 32 bit number. Take the highest bits (as many as the DAC needs), voilà, a sawtooth generator.

Plug that into your  wavetable (you'll have to recalculate for a 0..31 range if using five ramp bits).

Simple lookup from a block RAM (aka "nearest-neighbor / zero-order interpolation") will give abysmal audio quality, or the wavetable grows so large that it doesn't fit into the FPGA. So I need higher-order interpolation or a different algorithm (e.g. CORDIC for sine), and things get messy.

PS: When working with signed numbers, check Verilog's "signed" keyword. It's not mandatory, but makes life easier.

Edited by xc6lx45

Share this post


Link to post
Share on other sites
  • 0

@xc6lx45 thanks for Your detailed answer. It makes a lot of sense and it should not be too hard for me to do it. Also a bit distorted sine is ok for now (maybe I'll get back to it later) just to make sure it uses the whole dynamic range for (in my case 24bits).

 

Share this post


Link to post
Share on other sites
  • 0

I still dont get the basic parts of design..

if I have something like this

image.png.f19505c6d56e568499f745cbe69be4e0.png

It seems that RTL is still looking for hardware ports by the name of its output - it ignores if I have those nice ports created..

why is it so? or am I missing something? It this case I could just delete these ports and it wouldn't care.

Same with the clock.. If i start clocking wizard, setup desired freq and then rightclick that block and use this wiring wizard it connects those two ports (reset and sys_clock). But how does it know where to get the clock as my constraints file is commented out..

I would think that if I declare

set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { CLK100MHZ }]; #IO_L12P_T1_MRCC_35 Sch=gclk[100]
create_clock -add -name sys_clk_pin -period 10.00 -waveform {0 5} [get_ports { CLK100MHZ }];

then add port named CLK100MHZ and connect it to clocking wizard input would be the right pattern, but that doesnt seem to be the case.

Also what is the meaning of the TOP source? For me all the blocks are the same, what should be in the top then?

Learning process is somewhat awkward as I am looking at the same thing for the third evening (pin and clock errors).. Vivado is obviously powerful tool but lacks intelligence and support in the very begining starting with text formating and intelli-sense type of suggestions. As a Visual Studio and PHPStorm user I am a bit surprised not get these tools.

However, You guys seem to be all right:) Thanks for Your help and support!

Edited by Ignacas

Share this post


Link to post
Share on other sites
  • 0

Welcome to FPGA hell... I think it's the same for everyone, three days doesn't seem that much.
And yes, this isn't Visual Studio... Emacs Verilog mode (e.g. indent-region) is probably the next best thing...

>>Also what is the meaning of the TOP source? For me all the blocks are the same, what should be in the top then?

You need to make one module the "top" level. Vivado will instantiate that module once and consider its ports top-level connections. Those are then the same names as in the constraints, to assign a certain package pin and IOSTANDARD.

I'm not familiar with graphical entry but for plain .v files added as sources, go to Vivado, "Project manager", "Sources", click "Hierarchy" at the bottom, then right-click your top level design in the list and select "set as Top" from the drop-down menu.

For example, this is how a "top" module may look (I think you can also use any other name than "top" as long as you "set it as top" from the menu).

`default_nettype none
`include "xyz.v"
module top(input wire CLK12, 
	   output wire [1:0]  LED, 
	   output wire 	      RGB0_Red, 
	   output wire 	      RGB0_Green, 
	   output wire 	      RGB0_Blue, 
	   input wire [14:1]  pioA, // 14 
	   input wire [23:17] pioB, // 7
	   input wire [48:26] pioC, // 23
	   inout wire [7:0]   ja, // 8
	   input wire [1:0]   BTN, 
	   input wire [1:0]   xa_n, 
	   input wire [1:0]   xa_p);

   // 31:16: breaking change
   // 15:0 non-breaking change
   localparam REVISION = 32'h00020010;   
			 
   //          _               _                       
   //         | |             | |                      
   //     ___ | |  ___    ___ | | __ __ _   ___  _ __  
   //    / __|| | / _ \  / __|| |/ // _` | / _ \| '_ \ 
   //   | (__ | || (_) || (__ |   <| (_| ||  __/| | | |
   //    \___||_| \___/  \___||_|\_\\__, | \___||_| |_|
   //                                __/ |             
   //                               |___/             
   // note: 12 MHz would be too low when running at 30 MHz FTDI clock
   wire 		      CLK; 
   wire 		      CLKDAC;
   wire 		      CLK300;   
   clk12to300 myPll1(.clk_in1(CLK12), .clk_out1(CLK300), .locked());
   clkMul myPll2(.clk_in1(CLK300), .clk_out1(CLK), .clk_out2(CLKDAC));
   
...

The two clocking blocks are then added as IP with the names of clk12to300 and clkMul.

PS: Disable generation of the "reset" input. You don't need it in a simple design, and it's one more thing that can go wrong.

Edited by xc6lx45

Share this post


Link to post
Share on other sites
  • 0

@xc6lx45 thanks for picturesque code snippet. That actually opened my eyes - verilog is OOP and all blocks has to be instantiated.

So I've managed to hack this together:

image.thumb.png.adbd2b3fdd7a55dfd944f70655aeaa77.png

and even write test bench with 100MHz sysclock, so I could simulate the whole thing.

Most of the thing seems to be working, except the output serializer.. Just cant make it work..

I was looking at @bikerglen i2s example for couple hours, but I just cant figure it out, even if I throw everything input related out. Somehow I'm not completely sure how it works.

Any suggestions where to look/read or maybe the whole concept is wrong and only possible options is @bikerglen I/O transceiver option?

 

 

Edited by Ignacas

Share this post


Link to post
Share on other sites
  • 0
1 hour ago, Ignacas said:

verilog is OOP and all blocks has to be instantiated.

@Ignacas,

No, Verilog is not OOP. In the context of this thread VHDL and Verilog are languages that along with the appropriate synthesis and place and route tools allow you to express digital logic in a particular target architecture. You can certainly use a GUI like the board design schematic approach offered by Vivado to connect blocks of IP and user RTL code and have some success. In the beginning at least this might seem to be quicker and easier. If the overall objective is to use FPGAs to experiment with audio data I think that you'll find this flow will be in the way of getting projects done.

At this point in the discussion I'd like to say that in the long run you're likely to be better off learning an HDL and using a text editor to create your own IP. That's just an observation that may well be immaterial to your immediate objectives. Regardless, I'll follow the thread because I'm interested in seeing how this works out. Whether you use schematic symbols or text source files to do it FPGA development is computer aided digital design. You may well prove me wrong but I believe that you'll get to your destination faster developing a competency in VHDL or Verilog. Of course I might be wrong about where I think your destination is.

For what, if anything, it worth.

Share this post


Link to post
Share on other sites
  • 0

Well, i meant OOP in away that You have a definition of the object and then have to instantiate it (even several times if need be). The GUI way is quite ok, once You figure out that it still has to be generated and set as top, later its much easier to understand what is going on with the desing. Yesterday I've figured that I can use external editor (Sublime) and that was great performance enhancement for me.

It took some time to realise that for this purpose I need PISO shifter and after whole day today I've got clocks and that shifter working.

image.thumb.png.9d7b1e0cbbfa42ba95151f713dbb41ec.png

sys_clock = 100MHZ, mclock = 12.288MHz, bclock = 1.536MHz, lrclock = 48kHz;

out_sample_load is a pulse to sample the data,

sdata is serial i2s data.

This looks ok form me, however my dac (CS4390) does not give me any signal. Im not sure what is wrong here. Any thoughts?

Share this post


Link to post
Share on other sites
  • 0

Check your warnings and understand every one of them (e.g. look out for logic that gets optimized away. Often it indicates a missing connection). You must fix "critical warnings", if there are any. Otherwise timing may be completely off (and if timing closure fails, the implementation is NOT "best effort" in any useful sense).

If you're certain the logic is correct, you may have a hazard problem: Logic levels are guaranteed to be stable at clock edges within setup / hold times (see static timing analysis), but in-between they may do whatever they like. And when an output signal acts as a clock for an external chip, hazards may cause double-triggering of the edge-sensitive input.

If in doubt, put e.g. (* DONT_TOUCH = "TRUE" *) reg xyz into every signal that leaves the FPGA to prevent any combinational logic downstream of the final register that could cause glitches.

Edited by xc6lx45

Share this post


Link to post
Share on other sites
  • 0

@hamster that is impressive list of projects You have done. Also few related to audio - Its going to be quite a nice read for me about spdif and Analog Wing.

STGL5000 example is nice, however I doesn't seem to face and solve same (clocking) problems as I have.. Thanks anyway!

Share this post


Link to post
Share on other sites
  • 0

If clocking is your problem, with a MMCME2_BASE with a 100MHz clock you can get to:

100,000,000Hz * 7.25 / 59.0 = 12,288,135 Hz

Out by about 11 parts per million - The 100MHz oscillator on the Arty A7 (ASEM1-1 0 0.0 0 0MHZ-LC-T) is +/-50 ppm, so maybe nobody will really notice. :)

 

 

Share this post


Link to post
Share on other sites
  • 0

Good day everyone, just a quick update.

Everything is going quite slow but fine with this project. Unfortunately I can only spare up to 1-2 hours a day for it, but have learned a lot already.

So far I've managed to:

Design the DAC pcb and generate all clocks for it (16 bit, 48kHz);

Build quite stable square, triangle and sine (1000 sample LUT) generators;

Make small internal audio switcher (dependant on hardware switch position);

Achieve ~0.2% THD+N on the differential (DRV134) output with 1kHz @ ~4Vpp;

Next step is to wait for revisioned and better quality PCBs and reclock it for 24bits. Then it will be the time either for AES/SPDIF output or ADC design. AES output would be nice to have for development as I have a RTW AES audio meter (img attached) collecting dust.

I've tried to include MicroBlaze with TCP server stuff so I could control the audio switch and generators, but it just got messy and I still couldn't find a way how could I make MicroBlaze to interact with my RTLs.. This will have to wait until ADC is finished.

The saga continues..

DAC.jpg

28944182_10204382683843303_1243229010_o.pngmaxresdefault[1].jpg

Edited by Ignacas

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this