Skip to content

Instantly share code, notes, and snippets.

@i-infra
Last active April 25, 2026 19:15
Show Gist options
  • Select an option

  • Save i-infra/ca4ae0ad7f9a0886412f32a06e0a0cba to your computer and use it in GitHub Desktop.

Select an option

Save i-infra/ca4ae0ad7f9a0886412f32a06e0a0cba to your computer and use it in GitHub Desktop.
Glasgow Firmware Mod: Preserve VIO Rails Across Bitstream Reloads

Glasgow Firmware Mod: Preserve VIO Rails Across Bitstream Reloads

Problem

On revC hardware, loading a new bitstream (e.g. switching applets) unconditionally killed both VIO A and VIO B voltage rails. This made it impossible to configure a device on one port with one applet and then load a second applet to use it — the voltage was gone before the second applet even started.

Root cause

firmware/fpga.c fpga_reset() explicitly called iobuf_set_voltage(IO_BUF_ALL, 0) and then waited 250 ms for the rails to discharge before asserting CRESET_N. The original rationale was that an unconfigured iCE40 has internal weak pull-ups on all I/O pins, and on revC a high logic level on the level shifter OE pin (driven by the FPGA) enables the level shifter as an output — which could drive external circuitry unexpectedly during reconfiguration.

However, the LDO enable pins (ENVA = IOD[0], ENVB = IOD[6]) live on the FX2 microcontroller, not the FPGA. The FPGA reset does not affect FX2 port D state. The voltage discharge was an intentional but conservative safety measure, not a hard electrical requirement, and is unnecessary if the connected device can tolerate the ~1.2 ms window during which the FPGA is unconfigured and outputs go HIGH through 33o resistors.

Fix

Removed the iobuf_set_voltage(IO_BUF_ALL, 0) call and the 250 ms discharge delay from the revC case in fpga_reset() in firmware/fpga.c.

-    case GLASGOW_REV_C0:
-    case GLASGOW_REV_C1:
-    case GLASGOW_REV_C2:
-    case GLASGOW_REV_C3: {
-      // Disable voltage output.
-      // This is necessary because iCE40 FPGAs have pull-ups enabled by default (when unconfigured
-      // and on unused pins), and on revC, a high logic level on the OE pin configures the respective
-      // level shifter as an output.
-      __xdata uint16_t millivolts = 0;
-      iobuf_set_voltage(IO_BUF_ALL, &millivolts);
-
-      // We don't have feedback from the Vio output to know when it has actually discharged.
-      // The device itself has 6 µF of capacitance and a load of 1 kΩ(min), for a t_RC = 6 ms.
-      // A reasonable starting point is 3×t_RC = 18 ms. However, external circuitry powered by
-      // the device can and likely will add some bulk capacitance. 250 ms of delay would be safe
-      // in the worst case of 5 V, 40 uF, no added load. It is also not long enough to become
-      // an annoyance.
-      delay_ms(250);
-
-      // Reset the FPGA now that it's safe to do so.
+    case GLASGOW_REV_C0:
+    case GLASGOW_REV_C1:
+    case GLASGOW_REV_C2:
+    case GLASGOW_REV_C3: {
+      // Reset the FPGA.

How to reproduce / rebuild the firmware

Prerequisites

The Docker-based build in firmware/docker.sh requires network access to pull the Debian image. As an alternative, build natively on macOS using Homebrew:

brew install sdcc make

You also need the libfx2 submodule (used for the fx2tool loader):

git submodule update --init vendor/libfx2

Build

Use gmake (Homebrew GNU make 4.x) rather than the system make (3.81), which is too old for the firmware Makefile:

# Build libfx2 first (only needed once, or after submodule update)
gmake -C vendor/libfx2/firmware/library all MODELS=medium

# Build the Glasgow firmware
gmake -C firmware all
# Output: firmware/build/glasgow.ihex

Load into FX2 RAM (non-persistent, for testing)

The Glasgow venv Python (3.13) has libusb1; use it directly with fx2tool:

PYTHONPATH=vendor/libfx2/software \
    software/.venv/bin/python3.13 -m fx2.fx2tool \
    -d 20b7:9db1 load firmware/build/glasgow.ihex

The firmware is loaded into RAM only — it is lost on power cycle. No EEPROM is written.

Flash to EEPROM (persistent)

First deploy the built firmware into the software tree, then flash it:

# Normalize and copy to the location glasgow flash expects
PYTHONPATH=vendor/libfx2/software python3 firmware/normalize.py \
    firmware/glasgow.ihex software/glasgow/hardware/firmware.ihex

# Flash to device EEPROM
glasgow flash --firmware

Verify the fix

# Set port voltages (persists in DAC registers on FX2, not reset by bitstream load)
glasgow voltage A 3.3
glasgow voltage B 1.8

# Run a first applet with explicit voltages
glasgow run experimental-calibrate-clock -V B=1.8,A=3.3 --ref-pin B0 --ref-freq 8388608 --count 5

# Run a second applet WITHOUT -V — voltages are now preserved across the bitstream reload
glasgow run experimental-calibrate-clock --ref-pin B0 --ref-freq 8388608 --count 5 --ext-pin A2 --ext-freq 10e6

Previously the second invocation would log cannot configure pulls for port X: Vio is off and start with dead rails. With this fix the rails stay live.

@i-infra
Copy link
Copy Markdown
Author

i-infra commented Apr 25, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment