Co-Simulation of Digital and Analog Circuits: Two Approaches to the Icarus Verilog and NGSpice Bridge
Modern electronic systems require the integration of digital and analog components, making mixed-signal simulation critically important. To address this challenge, we implemented a bridge between Icarus Verilog and NGSpice by developing two architectures. Each has fundamental limitations, but they already enable solving real-world tasks ranging from SAR ADCs to sigma-delta modulators.
Basics of Mixed-Signal Simulation: Events vs. Continuity
Digital circuits are modeled using events: the simulator only activates on signal changes (clock edges, flip-flop toggles). This is how Icarus Verilog and other digital simulators work— the system "sleeps" between events, ensuring high performance. Analog circuits require continuous modeling: SPICE engines (NGSpice, Spectre) break time into small steps, solving differential equations for each interval. This approach is accurate but resource-intensive, especially with sharp edges or feedback loops.
The key challenge in co-simulation is reconciling these fundamentally different paradigms. The digital simulator deals with discrete events, while the analog one needs a smooth time flow. When combining a SAR ADC (where an analog comparator interacts with digital logic), a dilemma arises: how to pass data between engines without losing critical events?
Bridge Architecture: Common Principles
Both implementations use the same core technologies:
- VPI (Verilog Procedural Interface) for interacting with Icarus
- The dynamic library libngspice.so
- A streaming mechanism with semaphores
- A separate NGSpice execution thread
The central element is the system task $spice_sync(), called on clock signal edges. It performs a full exchange cycle: passing digital values to SPICE via alter commands, querying analog voltages, and updating real variables in Verilog. This allows displaying analog signals in GTKWave on the same timeline as digital ones.
always @(posedge clk_fast) begin
$spice_sync();
if (tb.Vcmp > vref_th) begin
// Reaction to event
...
end
end
The expose_analog_N mechanism automatically synchronizes analog variables, making them accessible to digital logic. Wrapper objects on both sides of the bridge handle edges and delays.
Classic Approach: Synchronous Polling
In this implementation, Verilog initiates the exchange on every $spice_sync() call. The simulator records the current time, blocks on a semaphore, hands control to NGSpice, which simulates up to that point and returns the data.
Advantages:
- Full determinism
- Predictable performance
- Simple implementation (about 2000 lines of code)
Critical Limitations:
- Complete invisibility of analog events between clock edges. For example, if a comparator switches at 5.2 ns and the next clock is at 10.0 ns, digital logic won't detect the change for 4.8 ns, making feedback loops in SAR ADCs or DC-DC converters impossible.
- Forced oversampling: to capture fast events, you need to artificially increase the clock frequency (e.g., to 500 MHz), which hurts performance.
This approach works only for tasks where analog events align with clock edges or with a high-frequency clock for synchronization.
Alternative Approach: Lookahead Analysis
To overcome the classic method's limitations, we implemented a lookahead mechanism. NGSpice gets permission to "run ahead" by a set interval, tracking analog events via configurable sensors. Example rule in mixed_bridge.cfg:
analog_event_0 = threshold outp rising 1.2 | time_var=tb.analog_event_time id_var=tb.analog_event_id
When the condition triggers, SPICE pauses and initiates an event in Verilog via the cbAfterDelay callback.
Advantages:
- Ability to detect asynchronous analog events
- Configurable flexibility (thresholds, durations, energy conditions)
- Reduced load with infrequent clocks
Fundamental Drawbacks:
- Missed events between SPICE steps due to discrete time integration
- Causality violations:
altercommands (digital signal changes) apply only in the next simulation phase, which is critical for DACs - Limit of one event per analysis window
- Non-determinism from heuristics in choosing lookahead interval length
- Implementation complexity (3500+ lines of code + config parser)
Key Differences Compared
The main trade-offs between architectures show up in five aspects:
- Event Detection: The classic method completely misses events between clocks, the alternative partially captures them but risks misses
- Causality: In the classic approach, DAC→analog linkage is correct; in the alternative, timing mismatches are possible
- Performance: Synchronous method offers stable speed; lookahead can slow dramatically with short intervals
- Determinism: Only the classic implementation guarantees reproducible results
- Integration Complexity: The alternative requires fine-tuning heuristics and analyzing circuit timing
Key Takeaways
- No Perfect Solution: Both architectures have inherent limitations due to fundamental differences in modeling paradigms
- Choice Depends on the Task: Alternative method suits sigma-delta modulators with rare pulses; classic with oversampling fits SAR ADCs with strict timing
- Real-World Applicability: The tool is already used for verifying digital logic in analog environments and pulse counting
- Visualization: Both support combined signal display in GTKWave and gnuplot on a unified timeline
- Outlook: Integrating an event queue and using
cbNextSimTimecould mitigate limitations
Practical Implementation and Usage Example
Consider using the bridge for a sigma-delta modulator. The analog part generates short pulses that a digital counter must tally. In the classic approach, just call $spice_sync() at a frequency exceeding pulse duration. Example Verilog code:
module DSMCounter(
input i_clk,
input i_rst,
input i_Qbar,
output reg[31:0] o_cntQbar,
output reg o_ready
);
parameter CNT_CLK = 22;
reg [31:0] cnt;
reg [31:0] cntQbar;
always @(posedge i_clk) begin
if(i_rst) begin
cnt <= 0;
cntQbar <= 0;
o_cntQbar <= 0;
o_ready <= 0;
end else begin
o_ready <= 0;
if(i_Qbar == 1) begin
cntQbar <= cntQbar + 1;
end
if(cnt == CNT_CLK-1) begin
o_cntQbar <= cntQbar;
o_ready <= 1;
cnt <= 0;
cntQbar <= 0;
end else begin
cnt <= cnt + 1;
end
end
end
endmodule
In the alternative implementation, you can lower the sync frequency using pulse detection rules via analog_event. However, this requires careful lookahead parameter tuning to avoid misses.
For running, you need the shared NGSpice build (--with-ngshared). Bridge config is set via mixed_bridge.cfg, defining sync points and event detection rules. Both implementations are open-source but need testing for your specific task.
Future directions include implementing an event queue instead of a single flag, integrating maxstep into the bridge config, and using cbNextSimTime for precise time control. A full solution requires adding an event API to NGSpice.
— Editorial Team
No comments yet.