GTKWave with Python

"gtkwave-python"

Visualizing Digital Signals: Using GTKWave with Python

A practical, code-first guide to generating waveform (VCD) files in Python and viewing, styling, and automating them in GTKWave.


What is GTKWave?

GTKWave is a free, open-source waveform viewer used heavily in digital hardware design and verification. It reads standard trace formats like VCD (Value Change Dump), FST, and LXT2, and displays how digital (and analog) signals change over simulated time — essentially an oscilloscope for your simulation data.

It's most commonly paired with HDL simulators (Verilog/VHDL via Icarus Verilog, Verilator, GHDL, ModelSim, etc.), but you don't need an HDL simulator at all. Because VCD is just a well-documented plain-text format, Python can generate VCD files directly — which makes GTKWave useful for visualizing any time-based, discrete-event data: FSM states, protocol traces, sensor logs, algorithm internals, or actual digital logic simulated purely in Python.

What we'll cover:
  1. Installing GTKWave and the Python tooling
  2. Generating a VCD file from Python (manually and with the pyvcd library)
  3. A worked example: simulating a simple digital counter and viewing it
  4. Loading, arranging, and saving views in GTKWave (.gtkw save files)
  5. Automating GTKWave from Python (TCL scripting + subprocess)
  6. Tips, gotchas, and where to go next

1. Installing GTKWave and Python Tools

Install GTKWave

Ubuntu / Debian:

sudo apt update
sudo apt install gtkwave

macOS (Homebrew):

brew install --cask gtkwave

Windows: download the installer from the GTKWave SourceForge page, or install it as part of a toolchain like OSS CAD Suite.

Install the Python VCD library

We'll use pyvcd, a lightweight pure-Python library purpose-built for writing VCD files (no HDL simulator required).

pip install pyvcd

2. Writing a VCD File by Hand (Understanding the Format)

Before relying on a library, it helps to see what a VCD file actually looks like — it's just structured plain text:

$date
   2026-08-15
$end
$version
   Python hand-written example
$end
$timescale 1ns $end
$scope module top $end
$var wire 1 ! clk $end
$var wire 1 " reset $end
$var wire 8 # counter $end
$upscope $end
$enddefinitions $end

#0
0!
1"
b00000000 #
#5
1!
#10
0!
0"
#15
1!
b00000001 #
#20
0!
#25
1!
b00000010 #
#30
0!

Each signal gets a short identifier (!, ", #), timestamps are prefixed with #, and value changes are logged only when they happen — that's what makes VCD compact. This is exactly the structure pyvcd generates for you programmatically.

3. Generating a VCD File with Python (pyvcd)

Let's simulate something concrete: a clock, an active-low reset, and a free-running 4-bit counter that increments on every rising clock edge.

import vcd

with open('counter.vcd', 'w') as f:
    writer = vcd.VCDWriter(f, timescale='1 ns', date='2026-08-15')

    # Register signals
    clk = writer.register_var('top', 'clk', 'wire', size=1)
    reset = writer.register_var('top', 'reset_n', 'wire', size=1)
    counter = writer.register_var('top', 'counter', 'wire', size=4)

    count_val = 0
    time = 0

    # Hold reset low for the first 10ns
    writer.change(reset, time, 0)
    writer.change(clk, time, 0)
    writer.change(counter, time, 0)

    time = 10
    writer.change(reset, time, 1)  # release reset

    # Run 20 clock cycles, period = 10ns
    for cycle in range(20):
        # Rising edge
        time += 5
        writer.change(clk, time, 1)
        count_val = (count_val + 1) % 16
        writer.change(counter, time, count_val)

        # Falling edge
        time += 5
        writer.change(clk, time, 0)

    writer.close()

print("counter.vcd written successfully.")

Run it:

python3 generate_counter_vcd.py

Then open the result directly in GTKWave:

gtkwave counter.vcd
Tip: pyvcd also supports real/analog values, vectors, string ("event") types, and hierarchical scopes, so you can model buses, FSM state names, or even floating-point sensor data — not just single bits.

4. Viewing the Waveform in GTKWave

Once GTKWave opens:

  1. In the SST pane (top-left), click on top to expand the module scope.
  2. Select clk, reset_n, and counter from the signal list.
  3. Click Insert (or drag them) into the waveform pane on the right.
  4. Right-click counterData Format → choose Unsigned Decimal or Hex to read bus values easily instead of raw binary.
  5. Use the zoom controls (or scroll wheel) to fit the full trace, and the toolbar's Zoom Fit button for a quick reset.

5. Saving and Reusing Your View (.gtkw files)

After arranging signals, colors, and zoom level the way you like, save the session:

File → Write Save File (or Ctrl+S) — this produces a .gtkw file, which is itself a TCL-like script GTKWave replays to restore your exact layout.

gtkwave counter.vcd counter.gtkw

This means you can commit .gtkw files alongside your Python scripts so teammates (or future you) get the same signal layout every time, without manually re-adding signals.

6. Automating GTKWave from Python

GTKWave has a built-in TCL console and supports being driven via TCL scripts and its VCD "shared memory" interface. For most Python workflows, the simplest and most robust approach is to generate a TCL init script from Python and pass it to GTKWave on launch.

Example: auto-load signals via a generated TCL script

tcl_script = """
gtkwave::/File/Open_Trace {counter.vcd}
gtkwave::addSignalsFromList {top.clk top.reset_n top.counter}
gtkwave::/Edit/Set_Trace_Max_Time
gtkwave::/Time/Zoom/Zoom_Full
"""

with open('load_view.tcl', 'w') as f:
    f.write(tcl_script)

Then launch GTKWave with the script:

gtkwave -T load_view.tcl counter.vcd

Launching GTKWave from a Python script

import subprocess

subprocess.run(['gtkwave', '-T', 'load_view.tcl', 'counter.vcd'])

This is handy for a one-command workflow: run a Python script that (1) simulates your logic, (2) writes the VCD, (3) writes a matching TCL layout script, and (4) pops open GTKWave already configured — no manual clicking required.

Full pipeline example:
import vcd
import subprocess

def simulate_and_write_vcd(path='counter.vcd'):
    with open(path, 'w') as f:
        writer = vcd.VCDWriter(f, timescale='1 ns', date='2026-08-15')
        clk = writer.register_var('top', 'clk', 'wire', size=1)
        counter = writer.register_var('top', 'counter', 'wire', size=4)

        val, t = 0, 0
        for cycle in range(16):
            t += 5
            writer.change(clk, t, 1)
            val = (val + 1) % 16
            writer.change(counter, t, val)
            t += 5
            writer.change(clk, t, 0)
        writer.close()

def write_tcl_layout(path='load_view.tcl'):
    with open(path, 'w') as f:
        f.write("""
gtkwave::addSignalsFromList {top.clk top.counter}
gtkwave::/Time/Zoom/Zoom_Full
""")

def open_gtkwave(vcd_path='counter.vcd', tcl_path='load_view.tcl'):
    subprocess.run(['gtkwave', '-T', tcl_path, vcd_path])

if __name__ == '__main__':
    simulate_and_write_vcd()
    write_tcl_layout()
    open_gtkwave()

7. Practical Tips

  • Use FST for large traces. VCD is human-readable but bulky. For long simulations, convert with vcd2fst (bundled with GTKWave) for much faster loading.
  • Group related signals in GTKWave with Edit → Insert Blank/Comment to visually separate clocks, buses, and control signals.
  • Use markers (press M then click a signal edge) to measure timing between two events.
  • Save your .gtkw file into version control alongside the Python script that generates the VCD, so the view is reproducible.
  • If you only need static, ready-made images for documentation rather than an interactive viewer, GTKWave can also export views to PostScript/PDF via File → Print to File.

8. Where to Go Next

If you're working with actual HDL (Verilog/VHDL) rather than pure-Python models, tools like cocotb let you write testbenches in Python that drive an HDL simulator (Icarus Verilog, Verilator, GHDL) and automatically dump VCD — GTKWave then visualizes the results exactly as shown above, no manual VCD writing required.


Written for a Blogger post — feel free to copy this HTML directly into Blogger's "HTML view" editor.

Post a Comment

0 Comments