h1

ADC In An FPGA

May 1, 2011

Geek Alert!  What follows is very technical.  It involves ADC’s, FPGA’s, and sophisticated electrical design.  If this doesn’t sound like you then you can safely skip the rest of this blog post.

EDIT:  Part 2 of this series is up.  Read it here, when you’re done with Part 1, of course.

I’ve always had this crazy idea to implement an ADC (Analog to Digital Converter) in an FPGA.  Recently I worked out a lot of the details, enough where I started to get concerned about future patent violations and associated legal issues.  I’ve decided to post a summary of my work, as a record of prior art, just in case something actually comes from this.  Along the way, some other geeks might be interested in this.

This ADC is supposed to be inexpensive– that’s the main goal.  My requirements for this were to have no active external components.  The idea is that if you already have an FPGA with some unused pins, and add some passive components then you could have a simple ADC for almost nothing.  Along that line of thought, the logic inside the FPGA should also be super small.

These design parameters makes things quite difficult, and requires some out-of-the-box thinking.  Normally one would consider the traditional ADC architectures and then shoehorn that into an FPGA.  But after looking at them, I decided that none of them really fit the FPGA implementation very well.  So instead of doing a top-down approach, I went with the bottom up.

At the core, all ADC’s rely on some sort of voltage comparator.  In the FPGA, the only real comparator would be a differential input buffer.  LVDS, for example.  Of course, such a buffer has lots of bad behaviors like hysteresis– but that’s all we have to work with.  The architecture I came up with should work around these issues.

Below is a block diagram for my ADC, click on it to enlarge.  For the moment, ignore the RC Filter Model and Low Pass Filter.

ADC Block Diagram

Normally, Ref would mostly track Vin.  The LVDS receiver, used as a voltage comparator, senses if Ref is too high or too low, and feeds the external RC filter accordingly.  There will be some ripple on Ref, which is mostly the result of the RC filter characteristics and the hysteresis of the LVDS receiver.  Oddly enough, however, this ripple helps the circuit.

The RC Filter Model is simply a digital representation of the external RC filter.  The output of this block is the voltage of the Ref signal (including all the ripple).  The Low Pass Filter is there to smooth out the ripple on the RC Filter Model.

That’s it!  Ok, the devil is in the details, but that’s most of it.  I’ll get to some of those details, but let’s look at some waveforms.

For these waveforms and unless otherwise said, Vin is a 10 KHz sine wave from 0.4v to 2.9v,  C=220 pF, R=3.3K ohms, and the LVDS receiver hysteresis is 50 mV.

In the image above (click to enlarge), you’ll see Vin doing it’s thing with Ref tracking it.  Of course Ref has the expected ripple.  The Err signal is the difference between Vin and Ref, and has a swing of about +/- 65 mV.  Dout and Clk are there, but when zoomed out this far you can’t see anything useful.  Here is the same waveforms, but zoomed in a lot:

In this image you can clearly see the ripple, and resulting Err, on Ref.  You can also see Dout.  One thing that should be mentioned at this point is that Dout is not simply a PWM’d representation of Ref.  As Ref changes voltage, the ability for Dout to charge/discharge the external cap changes due to the voltage across R.  This image shows Ref when it’s at its lowest level, so Dout has an easy time driving Ref higher but has difficulty bringing it lower.  This is shown as a change in slew rate on Err, fast rising edge and slow falling edge.

The LVDS hysteresis and RC time constant will effect the ripple frequency.  Also, this frequency will slow down as Vin/Ref get closer to the power and ground rails.  As a general rule, you want this ripple to be as high frequency as possible while still maintaining a somewhat analog appearance.  I’ve found that 220 pF and 3.3K Ohms is about the shortest RC time constant that’s practical using LVDS and LVTTL.

The RC Filter Model sounds daunting at first, but isn’t too bad in reality.  First, we’ll go over the math.  The formula is this:

Ref_New= Ref_Current + (Dout-Ref_Current) * (1-exp(-T/RC))

Where Ref_New is the new value of Ref, Ref_Current is the current value of Ref, Dout is either 0.0 or 3.3 depending on the state of Dout, T is the clock period in seconds, R is in ohms, and C is in farads.  The function exp(x) returns e to the power of x.

In the FPGA, it’s only the last part of that formula that’s hard, the “* (1-exp(-T/RC))” part.  But on closer inspection it’s not that bad.  Since T, R, & C do not change at runtime, the whole thing becomes a multiplication by a constant.  That’s a good thing, because doing an exp() function in an FPGA is challenging and uses lots of logic.

That constant would normally be represented as fixed point.  By careful selection of R and C, that constant can have very few ‘1’ bits in it– maybe even a single ‘1’ bit.  This is super important.  Fixed point multiplication by a number with a single ‘1’ bit is the same as a bit-shift! And, as you know, a bit-shift in an FPGA takes no logic.  If there are two ‘1’  bits then it’s two shifts and an addition– still not bad.  One of my goals in the near future is to write a program that will find combinations of T, R, and C that make the math easier.  I’ve done something similar in the past and it works out really slick.

Another “optimization” is to scale the numbers.  Instead of Ref being represented by a fixed point number from 0.0 to 3.3, it can be represented as 0.0 to 0.999999.   In binary, that would be from “00000000” to “11111111”.  While that doesn’t make the math any easier, it does make the ADC somewhat independent of the I/O Standards used, and puts the ADC output in a more useful form.

The Low Pass Filter can be just about anything, depending on what quality of output you require.  A simple averaging filter can be good enough for most things.  For better performance, there are many types of IIR filters that would work well.  I don’t recommend a FIR filter for this, due to the sample rates and the number of taps that you would need.

It’s a little preliminary to talk about performance of this ADC.  I have simulated enough of this to know that the theory is sound, but I have not built an actual circuit or done an FFT analysis on the output.  But based on what I’ve seen so far, it seems like a slam dunk to get 10 KHz bandwidth (20 KHz output sample rate) and 12-bit performance.  Higher sample rates are possible with sacrificing rail to rail operation and bit resolution.

I should also mention that the same RC Filter Model can be used to make a nice DAC in an FPGA.  Doing so would be better than using PWM or a first-order Delta Sigma modulator (similar to a Xilinx app note) in that the DAC settling time is faster, it has a more linear input to output response, and can operate with a faster RC time constant resulting in better frequency reponse.

So there you go!   If you like this, let me know.  Maybe I’ll have to post some VHDL code or something.

About these ads

10 comments

  1. [...] This is part 2 of “ADC In an FPGA”.  If you have not read part 1, here it is. [...]


  2. It would seem to me that the constant (1-exp(-T/RC)) really isn’t that important after all, except for noting that it is constant. If we use the wrong constant, what will happen is that the final calculated value will be of by a constant factor. So, basically just make RC a magnitude or two bigger than T, and compensate after the low pass filter: when you’re reading out 20K samples per second or less, the overhead of a multiplication doesn’t matter that much. In my case, the R and C won’t be that precise anyway, so I’ll need to calibrate regardless. Of course I want to choose R and C such that I don’t end up with useless bits in my output, but that’s all.

    So, my question: is it true we can exchange the positions of the RC model and the low pass filter?

    If I understand correctly, we can just determine our desired bit depth N and use an N-bit counter for counting the number of clocks that Dout was high. Then every 2**N clocks, we latch the counter in an output register X and reset it. This way X will always have a value proportional to the input voltage.

    Now only to figure out how I put together the necessary VHDL code for a generic that takes the number of bits N and clock C, and provide me with the output signals for the register X and its latch signal.

    FYI, my concrete goal is to use a Xilinx Spartan 3 FPGA with a 40 MHz SPARC v8 LEON3 SoC on it (see gaisler.com) to control a 3D printer (see reprap.com). The DAC will be needed for reading a thermistor. Precision is more important than speed, but 8 bits of precision at 10 Hz would probably be sufficient. While I might be able to do this purely in software after all, I’d rather use 10 bits of a general purpose I/O register for an ADC.

    -Geert


  3. The RC filter model is more than just a filter. An RC filter is inherently non-linear, and the model adjusts and compensates for that. In a “normal” DAC/ADC there will be a constant current source/sink that charges and discharges a cap. The cap voltage will go up or down a fixed amount based on the constant current and the time it is on. In our ADC, we have an external cap for this but instead of a constant current source we have a constant voltage source and a resistor. This isn’t exactly like a constant current source, since the current varies depending on how close the cap voltage is to the power rails. When the cap is at VCC/2 then it works great, but in the extremes it gets a bit nonlinear.

    For our ADC, we could create an external constant current source but that would be a complex bit of circuitry for something that is supposed to be simple. So instead we deal with our non-constant-current source and model how that non-constant-ness behaves.

    So to answer one of your questions: you cannot swap the positions of the RC model and the low pass filter.

    Because of the RC model is compensating for the non-linearity of our external RC filter, the value of (1-exp(-T/RC)) is important. It is also important that it is basically a constant value that we have some control over. Still on my to-do list is to characterize how the accuracy of this constant (along with the tolerances of the external RC filter) effects the overall ADC performance. So stay tuned for more data on this particular subject.

    You could just count the number of high pulses over 2**N clocks, but you would have several negative side effects: 1. You would get that non-linearity issue that the RC model corrects. 2. There would be potentially unwanted frequency distortion due to the way the analog signal is being sampled. And 3. Your bit depth and sample rate would be somewhat limited. My ADC should be free of those issues while not taking up much more logic.

    I still have a lot of work on this ADC before it is ready for “prime time”, but currently I’m getting about 14 bits of precision with a 100 MHz master clock and a sample clock of around 20 to 40 KHz. Again, there’s lots of work to do but it looks very promising at the moment.

    I should also mention that I’m being super nit-picky about this ADC design. Most of this won’t matter for your application, except the non-linear stuff. 10 Hz sample rate is fairly slow and you can get away with a lot of sloppy (but easy) math with that.


  4. Thanks for your reply, David.

    It seems you are saying in your article that the RC model is given by:

    Ref_New= Ref_Current + (Dout-Ref_Current) * (1-exp(-T/RC))

    Now, let’s substitute expression (1 – exp (-T/RC)), with constant K.
    Also, instead of referring to Ref_New and Ref_Current, lets assume a series of N iterations, with Ref (J) and Dout (J) referring to the value of Ref_Current in iteration J. Also, let’s assume that the start state has Ref (0) = 0 and Dout (0) = 0.

    Now we get:

    Ref (J) = Ref (J – 1) + (Dout (J – 1) – Ref (J – 1)) * K,
    for 1 <= J <= N.

    Or:
    Ref (J) = Ref (J – 1) + Dout (J – 1) * K + Ref (J – 1) * -K,

    or:
    Ref (J) = Ref (J – 1) * (1 – K) + Dout (J – 1).

    If (1 – K) is not a convenient constant (such as a power of two), we can always make it one and add Dout (J – 1) / (1 – K) if Dout (J – 1) is 1 (and nothing if it is 0). The final Ref (N) must be multiplied by (1 – K) to get the correct result. This should be acceptable overhead and allow for easier selection of R and C. Am I missing something?

    -Geert


  5. I think your math is wrong. You can’t go from this:
    Ref (J) = Ref (J – 1) + Dout (J – 1) * K + Ref (J – 1) * -K

    To this:
    Ref (J) = Ref (J – 1) * (1 – K) + Dout (J – 1)

    But even if it were right, it doesn’t make selecting R and C any easier. You still need to select R and C to make your constant a power of 2– regardless of if your constant is K or 1-K.

    One thing that I don’t like about WordPress is that a link within an article doesn’t always show up clearly, making them easy to miss. Check out Part 2 of this series. About half way down is a link called “adc_constants”. I just made it bold to highlight it. That link will take you to a spreadsheet of R and C combinations for a variety of clock frequencies. If it doesn’t have the frequency you want, just yell and I’ll add it.


  6. Hi,

    Could you please tell me what software you are using to produce the scope traces and simulation results?

    Thank you,

    Neo.


  7. I’m using ModelSim for VHDL simulation, which produced all the waveforms in this article.


  8. This article is quite a bit unclear about what you’re doing. You say “In the FPGA, the only real comparator would be a differential input buffer. LVDS, for example.” What do you mean by “differential input buffer”? An Op-Amp? No, those aren’t digital. I’ve never heard of a digital differential buffer. LVDS? That’s a specification, not a circuit component. So can you be a little more clear about what you’re talking about?


    • A “differential input buffer” is an input buffer that takes a differential signal of some kind. There are many standards for differential signals, and LVDS is one of them. An LVDS input buffer is an input buffer that follows the LVDS standard.


  9. I am a university student and as a part of my final year project, I have to implement Sigma Delta ADC on Altera cyclone FPGA device. I searched VHDL code for that in internet but I could not find any code. I only found the theory about that. So I kindly request you to send me a VHDL code for Sigma Delta ADC. Please help me. Thank you. (Sorry about my bad English). My email- ddescw@gmail.com



Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: