Back to Home

Co-simulation of Icarus Verilog and NGSpice: comparison of architectures

The article analyzes two bridge architectures for co-simulation of Icarus Verilog and NGSpice. The fundamental limitations of each approach and recommendations for application in real projects are considered. The tool is already used for verification of mixed circuits.

How does the bridge between Icarus Verilog and NGSpice work for co-simulation?
Advertisement 728x90

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?

Google AdInline article slot

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.

Google AdInline article slot

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:

Google AdInline article slot
  • 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: alter commands (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 cbNextSimTime could 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

Advertisement 728x90

Read Next