Hochschule Kempten      
Fakultät Elektrotechnik      
Publications       Fachgebiet Elektronik, Prof. Vollrath      

NodeEEBench with Digilent BASYS3 FPGA board

INL, DNL ramp test with error correction lookup table implementation

Jörg Vollrath, University of Applied Science Kempten, Germany, Joerg.vollrath@hs-kempten.de
March, 2024



Overview


Introduction

Summary




Introduction



INL, DNL errors for DACs and ADCs can be measured using a ramp test.
For DACs all codes are applied and the output voltage is measured.

The output voltage can be noisy, which can be eliminated by averaging.

For the BASYS3 board only a maximum of 4k memory is available per oscilloscope acquisition.
A 16-Bit R2R DAC with an output range from 0 V to 3.3 V is created with this board.
The XADC has a range from 0 V to 1 V with a 16 Bit number, with 10/12 valid bits.

A start, stop and step value is needed to program the AWG code range.
Then 4k of data is acquired.
A loop with different AWG ranges and merging test results give higher resolution but takes more test time.

Implementation


Starting point is to do the acquisition and processing once manually and do a step by step improvement and generalization.
A function 'generateRamp()' to start data acquisition is created.

T and O command


Start, stop and step code are taken from the user input GUI.
Repetition is 832, since ther are 320 AWG samples generated with 100MHz CLK (T = 10ns) and the OSC acquisition is done with 8.32 µs.

  var stepCode = Math.trunc(parseInt(document.getElementById("codesStep").value)/2);  // number of code values
  if (stepCode <= 0) { stepCode = 1; }
  var startCode = parseInt(document.getElementById("codesStart").value);  // number of code values
  var endCode = Math.trunc(parseInt(document.getElementById("codesEnd").value) / 2) ;  // number of code values
  endCode = Math.trunc((endCode - startCode)/stepCode) * stepCode + startCode; // Ramp codes down = up
  // Make triangle waveform
  // Repeat 832 because DAC 100MHz, ADC 8.32us
  cmdText = "T" + decToHex(startCode,4) + decToHex(endCode,4)
                 + decToHex(stepCode,4) + decToHex(4*832,8);         
  // sendCmd "T"
  document.getElementById("cmd").innerHTML = cmdText;
  sendCmd();

This code is used with step = 16, start code = 0 and end code = 19850.
It gives 4 values for each code for averaging.
Data analysis shows for low codes a minimum voltage and for high codes a maximum limit.
Parameters were changed to step = 16, start code = 0 and end code = 19488.

Data processing


The function 'ramp' is doing the data processing.

DNL and INL calculation and display


Only one OSC channel is selected and INL, DNL should be plotted in the right color.
  1. Copy AWG code and OSC channel into object array (dataOb)
  2. Sort array for AWG code
  3. Calculate average OSC (avgOSC) for each AWG code (codeAWG) into new object array
  4. Calculate minimum, maximum and range for OSC and AWG
  5. Calculate number of AWG codes (bins) and lsb = (maxOSC - minOSC)/(bins-1)
  6. Calculate ideal OSC codes idealOSC = minOSC + (codeAWG - minAWG) * rangeOSC/rangeAWG
  7. Calculate INL: inl = (codeOSC - idealOSC)/lsb
  8. Calculate DNL: dnl = (codeOSC[i] - codeOSC[i-1] - lsb)/lsb
  9. Display INL, DNL

The INL, DNL display is successfull.
During testing range limitations were seen. For a lot of minimum codes a fixed output value is produced.
Also if the AWG code range is too high, a lot of maximum OSC values can be observed.
This gives INL error at the beginning and at the end of the curve.
Repetitive measurements showed a change of up to 200 OSC codes for the same AWG code as noise.
Therefore averaging can be important.
Implementation, debugging and test took 6 h.
HTML code 50 lines
JavaScript 180 lines.

Lookup table calculation


  1. The object array (oscObjAvg) is sorted ascending by OSC code (codeOSC)
  2. Get the maximum step size
  3. Scale the maximum step size
  4. With this step size search for best AWG codes in array
  5. Fill up the rest of the array with maximum (Changes scaling??)
    Update scaling?
  // Make a lookup table with this data

	// sort oscObjAvg with OSC codes
    oscObjAvg.sort(function(a,b) { // sort for codeAWG
         if ( a.codeOSC < b.codeOSC )
             return -1;
         if ( a.codeOSC > b.codeOSC )
             return 1;
         return 0;
    });
  
  var maxStep = 0;	
  if (nrCodes > 2) {  
    // get maximum step size
    maxStep = oscObjAvg[1].codeOSC - oscObjAvg[0].codeOSC;	
    var minOSC = oscObjAvg[0].codeOSC;
    var maxOSC = oscObjAvg[oscObjAvg.length - 1].codeOSC;
    for (var j = 2; j < nrCodes; j++) { 	   // Getr maximum step size
      var stepOSCX = oscObjAvg[j].codeOSC - oscObjAvg[j-1].codeOSC;	
      if (stepOSCX > maxStep) maxStep = stepOSCX;
    }
  }	
// make ideal curve lookup table
// maximum step = 0.5..1.5 LSB
  var stepScale = parseFloat(document.getElementById("stepScale").value);  // number of code values
    maxStep = stepScale * maxStep;
  if (maxStep == 0 ) { alert("maxStep = 0"); }
  else {  // maxStep not 0
   var maxCodes = (maxOSC - minOSC) / maxStep;
// Look for suitable values going through sorted array
   var lookupList = [];
   lookupList[0] = oscObjAvg[0].codeAWG;
   var nextVal = oscObjAvg[0].codeOSC + maxStep;
   var indexL = 1;
   lookupList[indexL] = oscObjAvg[1].codeAWG;
   var errorL = Math.abs(oscObjAvg[1].codeOSC - nextVal);
   for (var i = 2; i < nrCodes; i++) {   // get lookup list 
     var errorN = oscObjAvg[i].codeOSC - nextVal;
     if (errorN > 0.5 * maxStep) { // next value
	    indexL = indexL +1;
		nextVal = nextVal + maxStep;
		errorN = oscObjAvg[i].codeOSC - nextVal;
		errorL = Math.abs(errorN);
        lookupList[indexL] = oscObjAvg[i].codeAWG;
     }	 
	 if (Math.abs(errorN) < errorL) {   // absolute error better
        lookupList[indexL] = oscObjAvg[i].codeAWG;
		errorL = Math.abs(errorN);
	 }
   }				
   var rangeN = indexL;   
   for (var i = indexL + 1; i < 8 * 1024; i++) {   // fill up rest until 8k 
      lookupList[i] = oscObjAvg[nrCodes-1].codeAWG;
   }
   for (var i = 0; i < 4 * 1024; i++) {   // copy lower 4k to upper 4k: 8k 
      lookupList[i + 4*1024] = lookupList[i];
   }

   var lText = "Min: " + oscObjAvg[0].codeOSC + " Max: " + oscObjAvg[nrCodes-1].codeOSC 
              + " Step: " + maxStep + " New Codes: " + rangeN 
			  + " New Code Range: " + (rangeN * 16) + "<br>\n";
   lText += "Lookup Table: <br>\n";
   var xText = lText;  
   for (var i = 0; i < 16 * 4 * 8; i++) {   // fill up rest 
      for (var j = 0; j < 16; j++) {   // fill up rest 
         lText += lookupList[i * 16 + j] + ",";
		 xText += lookupList[i * 16 + j] + ",";
      }
	  lText = lText + "<br>\n";
	  xText = xText +"\n";
   }
   xText = xText.substring(0, xText.length-2);
   document.getElementById("bestL").innerHTML = lText;
   document.getElementById("lookupX").value = xText;
   // calculate new INL, DNL and redo with different maxStep if necessary
  
  } // End maxStep not 0
JavaScript 80 lines of code copied from Arduino.
Changed variable names and 256 to 8k buffer.
length of oscObjAvg could be 1 and led to non calculation of maxStep.

Testing:

4k sample FFT has bleeding.
2k sample FFT is good.
Starting AWG does an "X" command resetting lookup.
The range of AWG has to be adjusted according to the number of codes.
The output amplitude will be still 1 V according to lookup codes.

16 Bit R2R DAC
Before:
Ramp Test: start = 0, stop = 19480, step = 16
INL -7.54..20.8 DNL -24.2..1.73 Gives 1.0 321 Codes 5136 Range
Range 19480 is 1V; Range 5136 0.25V
Ramp Test: start = 0, stop = 5100, step = 16
INL -2.31..5.61 DNL -7.73..3.48
Improvement from 24 to 8.

SNDR FFT Investigation:
4096 samples FFT shows bleeding
2k, 1V, SNDR = -8.96 dB - (-36.63 dB) = 27.67 dB
Changing AWG range to offset 120 mV and amplitude to 110 mV.

Since the Stop AWG issues a "X" command a Lookup Table Activate "Q" command is sent after it.

2k, Lookup, 0.25V SNDR = -10.9 dB - (-46.89 dB) = 35.9 dB -> +8.23 dB
Improvement of 8.23 dB of more than 1 Bit.

0.5 h programming
1.5 h testing.

Acquisition of more data


The number of acquisited data should rely on the step size.
At each step 4 data points are acquired (4*832).
    // Make triangle waveform
    // Repeat 832 because DAC 100MHz, ADC 8.32us
    cmdText = "T" + decToHex(startCode,4) + decToHex(endCode,4)
                  + decToHex(stepCode,4) + decToHex((4*832),8);    // 832, 416,208  4 samples    
The loop has 3 parts.
Function 'generateRamp' initializes the measurement from the 'Run' Button.
Function 'generateRampX' calculates the correct step size (stepCode) for the required number of points. It also gets the number of runs (runs).
Function 'nextRamp' pushes data from OSC3 to oscObj and looks wether acquisition is finished. Acquisition is finished after 16 steps.

Testing:
Revealed problems to switch graphs.
New lookup table does not improve SNDR ratio. Too much noise during acquisition of lookup table?.

To Do