Closed-loop voltage-mode control on a Xilinx Spartan-7 FPGA using 23-bit fixed-point arithmetic
A fully verified, hardware-validated digital PID controller for a 100 kHz synchronous buck converter, designed from first-principles small-signal modeling through FPGA deployment.
- Overview
- Architecture
- Key Features
- Repository Structure
- Design Specifications
- Fixed-Point Arithmetic
- Simulation & Verification
- Synthesis & Deployment
- Hardware Results
- Documentation
- License
This project implements a digital voltage-mode PID controller for a synchronous buck DC-DC converter on a Xilinx Spartan-7 (XC7S25) FPGA. The design follows a rigorous three-stage methodology:
-
Small-Signal Modeling — Derive the control-to-output (
$G_{vd}$ ), audio susceptibility ($G_{vg}$ ), and output impedance ($Z_o$ ) transfer functions analytically. - Frequency-Domain Tuning — Compute PID gains in MATLAB to satisfy a 10 kHz crossover bandwidth and 60° phase margin.
- FPGA Hardware Implementation — Discretize via Forward Euler, quantize to Q3.10 fixed-point, and deploy a fully parallel PID architecture with anti-windup saturation.
The controller tracks a 2.2 V ↔ 3.3 V reference transient every 10 ms, achieving < 2 ms settling time and ≈ 50 mV ripple — validated on real hardware with oscilloscope captures.
The closed-loop system combines the PID compensator, digital PWM modulator, buck converter plant, and ADC feedback path:
┌─────────┐ ┌───────┐ ┌─────────┐
Scaled Ref ──►(+)──►(e)──►│ G_c(z) │──► │ F_m │──► │ G_vd(s) │──┬──► V_out
(V_ref*H) ▲ - │ PID │ │ DPWM │ │ Plant │ │
│ └─────────┘ └───────┘ └─────────┘ │
│ │
│ ┌──────────┐ │
└─────────────────│ H (Sensor)│◄───────────────────────┘
└──────────┘
All three control paths (P, I, D) execute concurrently in a single 10 ns clock cycle:
The architecture uses registered error input, parallel multiplier paths for
| Feature | Detail |
|---|---|
| Parallel PID Execution | P, I, D branches compute simultaneously — no sequential bottleneck |
| 23-bit Fixed-Point Precision | Q4.19 intermediate math eliminates floating-point overhead |
| Dual-Stage Saturation | Independent integral anti-windup + output clamping prevents overflow |
| Cycle-Accurate Testbench | Self-checking shadow model validates every PID output against RTL |
| Ultra-Low Resource Usage | < 2% LUTs, 3 DSP48 slices — 98% of FPGA capacity available |
| Deterministic Latency | Single-cycle control path (10 ns) at 100 MHz system clock |
| Hardware Validated | Oscilloscope-confirmed transient tracking on physical buck converter |
Controller Design/
│
├── rtl/ # Synthesizable RTL source
│ ├── top.v # Top-level integration module
│ ├── buck_control_pid.v # Parallel PID controller core (Q4.19)
│ ├── clock_gen.v # Clock divider (25 MHz SPI, 100 kHz switching)
│ ├── pwm_gen.v # Trailing-edge digital PWM (1000-count period)
│ └── ref_gen.v # Reference voltage step generator (2.2V ↔ 3.3V)
│
├── tb/ # Simulation testbenches
│ └── tb_main.v # Advanced self-checking testbench
│
├── matlab/ # Design scripts
│ └── calculate_pid.m # PID gain computation & Bode/Step verification
│
├── constraints/ # FPGA pin mapping
│ └── constraint_PID.xdc # Xilinx XDC for Spartan-7 board
│
├── docs/ # Technical documentation
│ ├── main.tex # Full LaTeX project report (656 lines)
│ ├── ESDP Project Report.pdf # Compiled laboratory report
│ └── ESDP_Controller_Assignment.pdf # Original assignment specification
│
├── media/ # Hardware captures & diagrams
│ ├── buck and fpga circuit connection.jpeg
│ ├── pwm and output voltage oscilloscope.jpeg
│ ├── control_block.png
│ ├── pid_hardware.png
│ └── oscilloscope outputs video.mp4
│
├── .gitignore # Vivado / LaTeX / MATLAB exclusions
├── LICENSE # MIT License
└── README.md # This file
| Parameter | Symbol | Value |
|---|---|---|
| Input Voltage | 5 V | |
| Inductor | 5.6 µH | |
| Output Capacitor | 140 µF | |
| Switching Frequency | 100 kHz (DPWM) | |
| Load Resistance | 50 Ω | |
| Inductor DCR | 10 mΩ | |
| Capacitor ESR | 15 mΩ | |
| Sensing Gain | 1/11 V/V | |
| System Clock | 100 MHz |
| Metric | Target | Achieved |
|---|---|---|
| Crossover Frequency | ≤ |
10 kHz |
| Phase Margin | ≥ 45° | 60° |
| Settling Time (1.1V step) | — | < 2 ms |
| Output Ripple | — | ≈ 50 mV |
| Step Accuracy (ΔV) | 1.1 V | 1.110 V |
| Gain | Value | Discretized ( |
|---|---|---|
| 1.670 |
|
|
| 23,047 |
|
|
| 2.40 × 10⁻⁵ |
|
The controller avoids floating-point entirely. All computation uses two's complement fixed-point with carefully chosen Q-formats:
Signal Flow: Q1.9 ──► × Q3.10 ──► Q4.19 ──► Saturate ──► Truncate ──► Q1.11
(error) (gains) (products) (output to DPWM)
| Stage | Format | Bit Width | Range | Resolution |
|---|---|---|---|---|
| ADC Error Input | Q1.9 | 10-bit signed | [-1, +1) | 1.95 × 10⁻³ |
| PID Gains | Q3.10 | 13-bit signed | [-4, +4) | 9.77 × 10⁻⁴ |
| Multiplier Outputs | Q4.19 | 23-bit signed | [-16, +16) | 1.91 × 10⁻⁶ |
| DPWM Control Word | Q1.11 | 12-bit signed | [-1, +1) | 4.88 × 10⁻⁴ |
Quantization error for all gains is < 0.2%, negligible relative to component tolerances.
kpd = 13'b001_1010101110; // 1.6699 ≈ 1.670 (Kp)
kid = 13'b000_0011101100; // 0.2305 ≈ 0.230 (Ki·Ts)
kdd = 13'b010_0110011010; // 2.4004 ≈ 2.400 (Kd/Ts)- Icarus Verilog (
iverilog+vvp) - Or Xilinx Vivado (for synthesis + simulation)
# Compile all RTL + testbench
iverilog -o sim.vvp rtl/top.v rtl/buck_control_pid.v rtl/clock_gen.v rtl/pwm_gen.v rtl/ref_gen.v tb/tb_main.v
# Execute simulation
vvp sim.vvp============================================================
STARTING ADVANCED FAULT DETECTION TESTBENCH
============================================================
>>> TEST STAGE 1: Steady State (Zero Error)
PASS at 114995000 ns: PWM Duty ok (Exp: 0.9%, Meas: 0.9%)
>>> TEST STAGE 2: Positive Error Step (ADC < REF)
PASS at 334995000 ns: PWM Duty ok (Exp: 98.7%, Meas: 98.7%)
>>> TEST STAGE 3: Extreme Positive Error (Saturation Bounds Test)
PASS at 654995000 ns: PWM Duty ok (Exp: 0.9%, Meas: 0.9%)
>>> TEST STAGE 4: Negative Error Step (ADC > REF)
PASS at 874995000 ns: PWM Duty ok (Exp: 0.9%, Meas: 0.9%)
>>> TEST STAGE 5: Hardcoded Outputs Check
>>> TEST STAGE 5A: Full ADC Range Sweep testing
>>> TEST STAGE 5B: High-frequency Impulse Spike test
>>> TEST STAGE 5C: Randomized High-Speed Jitter Stress Test
>>> TEST STAGE 6: Completing final settling recovery verification...
PASS at 2544995000 ns: PWM Duty ok (Exp: 43.2%, Meas: 43.2%)
============================================================
[SUCCESS] ALL ADVANCED FAULT CHECKS PASSED !
Zero faults or anomalies detected in the design.
============================================================
The testbench (tb/tb_main.v) implements six categories of automated checks:
| Check | Description |
|---|---|
| Clock Frequency Validation | Asserts exact periods: 10 µs (100 kHz switching), 40 ns (25 MHz SPI) |
| X/Z State Detection | Flags any undefined logic on control voltage, PWM, or DAC outputs |
| Cycle-Accurate PID Shadow Model | Mirrors the exact Q4.19 fixed-point math and compares output every switching cycle |
| PWM Duty Cycle Accuracy | Measures actual high-time ratio and validates against expected duty from control voltage |
| Saturation Recovery | Injects extreme error values and confirms anti-windup clamp behavior |
| Randomized Jitter Stress | 100 pseudo-random ADC inputs at random intervals to test pipeline robustness |
- Create a new Vivado project targeting XC7S25 (Spartan-7).
- Add all files from
rtl/as design sources. - Set
top.vas the top module. - Add
constraints/constraint_PID.xdcas the constraints file. - Run Synthesis → Implementation → Generate Bitstream.
| Resource | Used | Available | Utilization |
|---|---|---|---|
| LUTs | ≈ 180 | 14,600 | 1.2% |
| Flip-Flops | ≈ 120 | 29,200 | 0.4% |
| DSP48 Slices | 3 | 80 | 3.8% |
| I/O Pins | 49 | 150 | 32.7% |
The three DSP48 slices map directly to the three fixed-point multiplications (
Spartan-7 FPGA control card connected to the synchronous buck converter power stage via SPI ADC/DAC and gate-drive PWM signals.
Oscilloscope capture showing regulated output voltage (top), PID control effort (middle), and DPWM gate signal (bottom) during 2.2V ↔ 3.3V reference transients at 20 ms/div.
Key Observations:
- ✅ Stable tracking — Output follows 2.2V ↔ 3.3V reference steps with < 2 ms settling
- ✅ Step accuracy — Measured ΔV = 1.110V matches the designed 1.1V step
- ✅ Low ripple — Peak-to-peak switching ripple ≈ 50 mV (within 1% regulation band)
- ✅ No oscillation — LC resonance (Q ≈ 6.86 at 5.68 kHz) fully damped by phase-lead action
| Document | Description |
|---|---|
docs/main.tex |
Full LaTeX project report — 650+ lines covering small-signal modeling, frequency-domain design, Q-format derivation, hardware architecture, and experimental validation |
docs/ESDP Project Report.pdf |
Compiled PDF report |
docs/ESDP_Controller_Assignment.pdf |
Original laboratory assignment specification |
matlab/calculate_pid.m |
MATLAB script: transfer function derivation, PID gain computation, Bode/step response verification |
The 100 MHz master oscillator is divided into two derived clock domains by clock_gen.v:
| Clock | Frequency | Period | Purpose |
|---|---|---|---|
clk |
100 MHz | 10 ns | System clock, DPWM counter |
clk_25 |
25 MHz | 40 ns | SPI interface for ADC/DAC |
clk_sw |
100 kHz | 10 µs | PID execution & PWM switching |
The 50 Hz reference stepping (toggling setpoint every 10 ms) is handled internally by ref_gen.v using a counter clocked by clk_sw.
- HDL: Verilog (IEEE 1364-2005)
- FPGA: Xilinx Spartan-7 XC7S25
- Synthesis: Vivado 2021.2+
- Simulation: Icarus Verilog / Vivado Simulator
- Analysis: MATLAB R2023+
- Documentation: LaTeX (pdflatex)
This project is released under the MIT License.
Designed and implemented as part of EE69006 — Electronic Systems Design and Prototyping
Department of Electrical Engineering, IIT Kharagpur
