RHDL includes a comprehensive debugging infrastructure for HDL simulations, including signal probing, waveform capture, breakpoints, and an interactive terminal GUI.
The SignalProbe class records signal transitions over time for waveform analysis.
# Create a probe attached to a wire
wire = RHDL::HDL::Wire.new("data_bus", width: 8)
probe = RHDL::HDL::SignalProbe.new(wire, name: "bus_probe")
# Signal changes are automatically recorded
wire.set(0x42)
wire.set(0xFF)
# Access recorded history
probe.history.each do |time, value|
puts "At #{time}: 0x#{value.to_s(16)}"
end
# Get current value
puts probe.current_value
# Generate ASCII waveform
puts probe.to_waveform(width: 60)
# Enable/disable recording
probe.disable!
probe.enable!
probe.clear!The WaveformCapture class manages multiple probes and provides export functionality.
capture = RHDL::HDL::WaveformCapture.new
# Add probes for signals
capture.add_probe(clock_wire, name: "clk")
capture.add_probe(data_wire, name: "data")
capture.add_probe(addr_wire, name: "addr")
# Control recording
capture.start_recording
# ... run simulation ...
capture.stop_recording
# Display text-based waveforms
puts capture.display(width: 80)
# Export to VCD format (for GTKWave)
vcd_content = capture.to_vcd(timescale: "1ns")
File.write("simulation.vcd", vcd_content)
# Clear all recorded data
capture.clear_allBreakpoints pause simulation when a condition is met.
sim = RHDL::HDL::DebugSimulator.new
# Break on a custom condition
bp = sim.add_breakpoint { |sim| sim.current_cycle >= 100 }
# Check breakpoint properties
puts bp.id # Unique identifier
puts bp.hit_count # Number of times triggered
puts bp.enabled # Is it enabled?
# Control breakpoint
bp.disable!
bp.enable!
bp.reset! # Reset hit count
# Remove breakpoint
sim.remove_breakpoint(bp.id)
sim.clear_breakpointsWatchpoints are specialized breakpoints that trigger on signal changes.
# Break when signal changes
sim.watch(wire, type: :change)
# Break when signal equals a value
sim.watch(wire, type: :equals, value: 0x42)
# Break when signal doesn't equal a value
sim.watch(wire, type: :not_equals, value: 0)
# Break on rising edge (0 -> 1)
sim.watch(clock_wire, type: :rising_edge)
# Break on falling edge (1 -> 0)
sim.watch(clock_wire, type: :falling_edge)
# Break when signal is greater/less than value
sim.watch(counter_wire, type: :greater, value: 100)
sim.watch(counter_wire, type: :less, value: 10)
# Watchpoint with callback
sim.watch(wire, type: :equals, value: 0xFF) do |simulator|
puts "Signal reached maximum value!"
endThe DebugSimulator extends the base simulator with debugging features.
sim = RHDL::HDL::DebugSimulator.new
# Add components
clock = RHDL::HDL::Clock.new("clk")
counter = RHDL::HDL::Counter.new("cnt", width: 8)
sim.add_clock(clock)
sim.add_component(counter)
# Connect components
RHDL::HDL::SimComponent.connect(clock, counter.inputs[:clk])
# Add probes (automatically creates SignalProbe)
sim.probe(counter, :q) # Probe output 'q'
sim.probe(clock) # Probe the clock wire directly
# Step control
sim.step_cycle # Execute one full clock cycle
sim.step_half_cycle # Execute half a clock cycle
# Run control
sim.run(100) # Run 100 cycles
sim.pause # Pause simulation
sim.resume # Resume simulation
# Step mode
sim.enable_step_mode # Enable step-by-step execution
sim.disable_step_mode # Disable step mode
# Callbacks
sim.on_break = -> (sim, breakpoint) do
puts "Breakpoint hit: #{breakpoint.id}"
sim.pause
end
sim.on_step = -> (sim) do
puts "Stepped to cycle #{sim.current_cycle}"
end
# Inspection
puts sim.signal_state # Hash of all signal values
puts sim.dump_state # Formatted state dump
puts sim.get_signal("counter.q") # Get specific signal value
# Reset
sim.resetThe SimulatorTUI provides an interactive terminal interface for simulation.
require 'rhdl'
# Create simulator with components
sim = RHDL::HDL::DebugSimulator.new
# ... add components ...
# Create and configure TUI
tui = RHDL::HDL::SimulatorTUI.new(sim)
tui.add_component(counter) # Add component signals to display
tui.add_component(alu)
# Run the TUI
tui.run┌─────────────── Signals ──────────────┐┌────────────── Waveform ──────────────┐
│ counter.clk HIGH ││ clk │▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄▀▄│
│ counter.rst LOW ││ q │════╳════╳════╳════╳════╳════│
│ counter.q 0x2A (42) ││ zero │▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄│
│ counter.zero LOW ││ └──────────────────────────────┘
│ ││ │
└──────────────────────────────────────┘└──────────────────────────────────────┘
┌─────────────── Console ──────────────┐┌────────── Breakpoints ───────────────┐
│ 10:23:45 Simulation started ││ ● #1 counter.q changes (hits: 42) │
│ 10:23:46 Stepped to cycle 1 ││ ○ #2 counter.q == 100 (hits: 0) │
│ 10:23:47 Watch triggered: counter.q ││ │
└──────────────────────────────────────┘└──────────────────────────────────────┘
▶ RUNNING │ T:42 C:42 h:Help q:Quit Space:Step
| Key | Action |
|---|---|
Space |
Step one cycle |
n |
Step half cycle |
r |
Run simulation |
s |
Stop/pause simulation |
c |
Continue until breakpoint |
R |
Reset simulation |
w |
Add watchpoint (opens command mode) |
b |
Add breakpoint (opens command mode) |
j / ↓ |
Scroll signals down |
k / ↑ |
Scroll signals up |
: |
Enter command mode |
h / ? |
Show help |
q |
Quit |
Press : to enter command mode. Available commands:
| Command | Description |
|---|---|
run [n] |
Run n cycles (default: 100) |
step |
Single step |
watch <signal> [type] |
Add watchpoint |
break [cycle] |
Add breakpoint |
delete <id> |
Delete breakpoint by ID |
clear [what] |
Clear breaks/waves/log |
set <signal> <value> |
Set signal value |
print <signal> |
Print signal value |
list |
List all signals |
export <file> |
Export VCD waveform |
help |
Show help |
quit |
Exit TUI |
When adding a watchpoint, specify the type:
change- Break when signal changes (default)equals- Break when signal equals specified valuerising_edge- Break on 0→1 transitionfalling_edge- Break on 1→0 transition
Example: :watch counter.q equals 100
When setting or printing values, multiple formats are supported:
- Decimal:
42 - Hexadecimal:
0x2A - Binary:
0b101010 - Octal:
0o52
VCD (Value Change Dump) is a standard format for waveform data that can be viewed in tools like GTKWave.
# From WaveformCapture
vcd = capture.to_vcd(timescale: "1ns")
File.write("waveform.vcd", vcd)
# From DebugSimulator
sim.run(100)
File.write("simulation.vcd", sim.waveform.to_vcd)- Install GTKWave:
apt-get install gtkwaveorbrew install gtkwave - Open the VCD file:
gtkwave simulation.vcd - Add signals from the signal tree to the waveform viewer
require 'rhdl'
# Setup simulation
sim = RHDL::HDL::DebugSimulator.new
clock = RHDL::HDL::Clock.new("clk")
counter = RHDL::HDL::Counter.new("cnt", width: 8)
sim.add_clock(clock)
sim.add_component(counter)
RHDL::HDL::SimComponent.connect(clock, counter.inputs[:clk])
counter.set_input(:rst, 0)
counter.set_input(:en, 1)
counter.set_input(:up, 1)
counter.set_input(:load, 0)
# Add probes
sim.probe(counter, :q)
sim.probe(counter, :zero)
# Add watchpoint
sim.watch(counter.outputs[:q], type: :equals, value: 50) do |s|
puts "Halfway there!"
end
# Add breakpoint at cycle 100
sim.add_breakpoint { |s| s.current_cycle >= 100 }
# Setup break callback
sim.on_break = -> (s, bp) do
puts "Breakpoint hit at cycle #{s.current_cycle}"
puts s.dump_state
s.pause
end
# Run simulation
sim.run(200)
# Export results
File.write("counter_sim.vcd", sim.waveform.to_vcd)
puts "Simulation complete. Final count: #{counter.get_output(:q)}"