Nmigen PLL ICE40HX8K/HX1K

I’ve been playing with a couple of ICE40 boards. These are some quick notes on using the PLL with the HX8K and HX1K. There’s nothing particularly innovative here, but I wanted to get some notes down.

I used pll.py from this repository: https://github.com/kbob/nmigen-examples/tree/master/nmigen_lib

The examples in this repository are for the ICEBreaker platform, which uses ICE40UP5K. The code doesn’t work with the HX1K and HX8K as laid out on the ICEStick and ICE40HX8KBEVN (these are the boards supplied by Lattice).

In particular pll.py is hardcoded to use B_PLL40_PAD. It appears that the oscillator on the above platforms can not be connected to this type of PLL. As such, if you try and use pll.py unmodified with these platforms you’ll get the following error (as I understand it from nextpnr):

ERROR: PLL ‘U$$0.U$$0’ PACKAGEPIN SB_IO ‘clk12_0__io$sb_io’ is not connected to any PLL BEL

Instead I use SB_PLL40_CORE. This also means you need to use i_REFERENCECLK instead of i_PACKAGEPIN. So the following changes are required:

pll = Instance(“SB_PLL40_CORE”, becomes pll = Instance(“SB_PLL40_PAD”,

and

i_PACKAGEPIN=self.clk_pin, becomes i_REFERENCECLK=self.clk_pin,

In pll.py. A tarball containing a working example for the Icestick is below (if you want to use the HX8K, just change the platform).

Aside from the PLL stuff, I had an issue programming the ICE40HX8KBEVN. As I understand it “platform.build(Top(), do_program=True)” should program this board. iceprog gets called, and appears to succeed but the ICE40 doesn’t seem to get programmed correctly (possibly it needs to be erased first?). I need to run iceprog manually. Programming the Icestick doesn’t have this issue.

Tarball: here

Various OP177 die images

I bought some OP177G in SOIC8 on eBay. The strange thing about them was that while I bought 10 on the same piece of cut tape, the packages were different… I figured there was a high probability that they were fake.

I decapped them using an Olfa cutter (box cutter) blade. All things considered this decapping process works surprisingly well. With some effort, I think you can do better (@marcan42 has used this method with reasonable results which is what gave me the idea in the first place).

They were imaged under the same inspection microscope (Amscope) that I use for soldering. While the images are not great, the feature sizes are huge so it doesn’t matter so much.

I also purchased some OP177s from Digikey… turns out that while the packages look kind of fake, the dies appear to be identical!

ebay packages back
ebay packages front
Digikey package back
Digikey package front

More pictures follow:

Notes on the Keithley 2002 ADC

The Keithley 2002 is an 8.5 digit multimeter. This puts it among the highest precision multimeters available. I’ve been curious about the ADCs used in these high end multimeters and have been looking over the Keithley 2002 ADC schematic. These are my notes.

The schematic I used is from TiN. Originally this was hosted here but appears to currently be offline. A local copy of the schematic is here.

As far as I can understand the ADC is a dual slope implementation (UPDATE: it’s actually multislope, but the slow slope is always connected, see below). The slopes are driven by current sources of ~450uA (which are derived from a 7V reference). They are switched through a SD5400, and when not in use flow to some kind of current sink.

I’ve mostly looked at the current sources, and switching. As part of this, I’ve been playing with an LTSPICE simulation (download):

The Keithley 2002 uses a 7V LTZ1000 reference (generally regarded as the best voltage reference you can buy). This enters the ADC board as a differential signal (REFHI and REFLO). REFHI should be 7V and REFLO ~GND. The REFHI is used to feed a non-inverting opamp (U801). The opamp has gain of 1.666. A tap is taken off the feedback network of U801 at ~2.333V. This is used as the reference for U816. This opamp drives a JFET (Q805) such that 2.333V flows through it. Creating a current source. Setting the voltage through Q805 seems to set the current at ~-450uA.

Rather than being ground referenced, the feedback network of U801 is connected to the virtual ground of U802. U802 is referenced to REFLO. U802 is an inverting opamp. Again with a gain of 1.666. Again a tap is taken off the feedback network at ~-2.333V. This is used as the reference for a opamp driving a current source created by Q806 (similar to the above). Current though Q806 is ~450uA.

The above therefore creates positive and negative current sources of +/- 450uA. These are the run down slopes of this dual slope implementation.

There’s another current source, Q807. This goes directly to the integrator. I don’t understand it’s purpose…

UPDATE: Kleinstein on the EEVBlog forums pointed out that there is a single slower slope which is always connected, delivering ~5uA. I assume this is what U815/Q807 are. So this is in fact a multislope ADC, but the slow slope is always connected.

The references are switched through an SD5400. These are MOSFET switches. The Vgs threshold is ~0.1V. It is driven by TTL logic level signals (0,5V). This being the case, if the voltage coming from the current sources goes below -0.1V or above 5V it will not switch correctly. Because the current is so low (450uA), a load of >200Ohms would be required to generate a voltage higher than 0.1V. I assume that the integrator presents as a load lower than this.

When not in uses, both the input and the references switch to a separate path, I’ve not looked at this path in detail. But kind of imagine this is used as a sink, which keeps the current flowing through the references constant…

Multislope ADC Bring up (Dual slope)

I’ve been playing with a multislope ADC design. Multislope ADC are often used in high end multimeters, and as I have a mild obsession with 8.5 digit multimeters, I wanted to try making a multislope ADC. The current design, such as it is was developed with significant input from EEVBlog users (see this thread). This post documents initial bring up and tests of the first revision of the PCB. Links to Kicad files, and data used in plots can be found at the end of this post.

Components used in this test

All resistors are standard, cheap 1/4 Watt metal film resistors. The large slopes are 47K. Small slopes 4.7M. The input resistor (R200) is also 47K. The design uses a 2 opamp integrator. Notionally this allows you to combine an opamp with good low frequency response with one with good high frequency response. A voltage divider sits between the two opamps. R9 is 47Ohm, R8 1K. On the output amplifier R3 is 1K and R7 is 470Ohm to give a gain of ~0.5. This allows the Arduino Mega which I’m using to control the amplifier to read almost the full positive range of the integrator output (12V). The integrator capacitor is an NP0 10nF, Murata GRM3195C1E103JA01D. The switches are all DG419s. This includes the part marked DG417 on the schematic, which resets the integrator. All the DG419s are Vishay DG419Bs MSOP8s, except for the integrator which uses an Analog ADG1419BRMZ (I ran out of Vishay DG419s). Update: I attempted to rebuild this circuit using only ADG1419BRMZ, it did not work well. My guess is that there is too much charge injection… charge injection is not listed in the datasheets… I’m using a LTZ1000 reference to supply the slopes. This sits on an 3458A A9 PCB. I’m using a A9 clone from here. All opamps are socketed. In this test I used a NE5534 on the output. An OPA177 and AD711 for the integrator, an AD711 on the input, and an LT1013 to buffer/invert the reference voltage.

Build Issues

There were a few errors in the schematic and layout. These include:

  • The banana plug holes are too small for the sockets.
  • The output opamp connected incorrectly (power swapped)
  • The input opamp was connected incorrectly (I forget the exact issue).
  • The reference voltage buffer/inverter was wired incorrectly.
  • The reference PCB covers the banana plugs when installed.

These should be resolved in the schematic, but I’ve not fixed the layout yet. In the build you’ll also notice that I’ve hacked around with other parts of the PCB too. This was when I was cutting traces to try and figure out where leakage current was coming from and charging the integration capacitor. In the end this seemed to be coming from flux I couldn’t clean from under the DG419 switches. I removed, cleaned and replaced these, being more conservative with my usage of flux when re-soldering them. This removed most of the leakage current.

Initial tests

When I first brought up the design there was a lot of charge accumulating on the integrator after reset without any of the slopes or the input connected. After about 3 seconds the integrator would rise to the rail voltage (12v). After some investigation, this seemed to be caused by excess flux remaining under the DG419s. After cleaning this out, it takes ~60 seconds for the integrator to charge to the rail voltage. The following plot shows the output as recorded by an Arduino analog input:

I’ve written code to drive the ADC board in a basic dual slope configuration. This works for bother the large and small slopes. In the tests below however I’m using the small slopes only. Arduino code is provided in the notes at the end of this post.

The tests use a DP832 to supply rail voltages (+/- 12 and 5V). A 33220A function generator is used to generate the input (this is probably not particularly low noise/accurate).

The following plot shows a histogram of ADC counts when the input is zero:

And here at 10V:

There’s something weird going on, as in some cases the histogram is bimodal. For example here at 1V:

Integration of positive voltages is also about 3 times quicker than negative voltages. It’s not clear to me why this is. Positive and negative voltages use different reference resistors, however these don’t seem to be significantly different and I need to investigate further.

The positive and negative responses however seem to be quite linear. The following plot shows input voltage versus ADC count. You can see the slope difference between positive and negative voltages quite clearly:

Before moving forward, I’d like to better understand why I’m seeing this difference between positive and negative voltages. I should then be in a position to combine large and small slopes to create a multislope ADC.

Update: I cleaned the board in an ultrasonic cleaner (distiled water, 60 degrees for ~15min, with a small amount of washing up liquid. Then agitated in IPA for ~30min). This seems to have cleared up the slope issue above. Here’s a revised graph:

Notes

PCB Back:

EEV Blog Thread

Data for plots

Kicad files

Hacky code used in this post:

#include <SPI.h>  
  
// name      header   arduino
// SW_RST    2        4
// SW_INPUT2 6        3
// SW_INPUT1 8        2
// SW_REF2B  10       A7
// SW_REF2A  12       A6
// SW_REF1B  14       A5
// SW_REF1A  16       A4
// CMP_OUT   18       A3

#define SW_RST    5
#define SW_INPUT2 3
#define SW_INPUT1 2
#define SW_REF2B  A7
#define SW_REF2A  A6
//#define SW_REF1B  A5 - original
#define SW_REF1B  A1
#define SW_REF1A  A4
//#define CMP_OUT   A3 - original
#define CMP_OUT   A2


void input_off() {
  digitalWrite(SW_INPUT2,HIGH);
  delayMicroseconds(100);
  digitalWrite(SW_INPUT1,LOW);
}

void input_on() {
  digitalWrite(SW_INPUT1,HIGH);
  delayMicroseconds(100);
  digitalWrite(SW_INPUT2,LOW);
}

void references_off() {
  digitalWrite(SW_REF1A,HIGH);
  digitalWrite(SW_REF1B,HIGH);
  digitalWrite(SW_REF2A,HIGH);
  digitalWrite(SW_REF2B,HIGH);
}

int read_cmp() {
  int r = analogRead(CMP_OUT);
  return r;
}

void dump_cmp() {
  int r = read_cmp();
  Serial.print(r);
  Serial.print(" ");
}

void ref_on(int p) {
  digitalWrite(p,LOW);
}

void ref_off(int p) {
  digitalWrite(p,HIGH);
}

void reset_integrator() {
  digitalWrite(SW_RST,HIGH);
  delayMicroseconds(10001);
//  delay(3000);
  digitalWrite(SW_RST,LOW);
}

void setup() {  

  // put your setup code here, to run once:
  Serial.begin(115200);

  pinMode(SW_RST   ,OUTPUT); 
  pinMode(SW_INPUT2,OUTPUT); 
  pinMode(SW_INPUT1,OUTPUT); 
  pinMode(SW_REF2B ,OUTPUT); 
  pinMode(SW_REF2A ,OUTPUT); 
  pinMode(SW_REF1B ,OUTPUT); 
  pinMode(SW_REF1A ,OUTPUT); 
  pinMode(CMP_OUT  ,INPUT ); 
  
  input_off();
  references_off();
  reset_integrator();

 //     ref_on(SW_REF2B); 
  Serial.println("input complete");
  for(;Serial.available() == 0;) {
    dump_cmp();
    Serial.println();
    delay(500);
  }
  Serial.println("running...");
}

void loop() {

  // Serial.println("start");
  input_off();
  delayMicroseconds(1000);
 // Serial.println("inputoff");
  references_off();
 // Serial.println("refoff");
  delayMicroseconds(1000);
  reset_integrator();
 // Serial.println("int reset");
  delayMicroseconds(1000);
  int p = read_cmp();
  int c=0;
  for(;p != 0;) {
    reset_integrator();
    p = read_cmp();
    //Serial.print(p);
    //Serial.print(" ");
    c++;
    if(c>10) break;
  }
  //Serial.println();
  //dump_cmp();

  input_on();
  delayMicroseconds(500);
  input_off();


  int pol = read_cmp();

  // input negative
  if(pol > 0) {
    Serial.print("-");

    unsigned int count=0;
    for(;;) {
      ref_on(SW_REF2B); 
      delayMicroseconds(5);
      ref_off(SW_REF2B);
      int c = read_cmp();
      if(c == 0) break;
      count++;
      if(count > 64000) {Serial.print("B"); count=0;}
    }
    Serial.println(count);
  }

  // input positive
  if(pol >= 0) {
    Serial.print("+");
    unsigned int count=0;
    for(;;) {
      ref_on(SW_REF2A);
      delayMicroseconds(5);
      ref_off(SW_REF2A);
      int c = read_cmp();
      if(c > 0) break;
      count++;
      if(count > 64000) {Serial.print("B"); count=0;}
    }
    Serial.println(count);
  }

}