Internal Signals

Internal Signal Example

Internal signals refer to those not exposed at the module’s IO ports, but which play roles in control, data transfer, or state tracking within the module. Typically, when picker converts RTL to DUT, only IO ports are automatically exposed, and internal signals are not exported by default.

However, when more detailed verification of internal module logic is needed, or when debugging known issues, verification engineers often need access to these internal signals. In addition to traditional tools like Verilator and VCS, picker also provides an internal signal extraction mechanism as an auxiliary tool.

Motivation

Take the up-counter as an example:

module UpperCounter (
    input wire clk,           
    input wire reset,         
    output reg [3:0] count   
);
    wire upper;

    assign upper = (count == 4'b1111);

    always @(posedge clk) begin
        if (reset) begin
            count = 4'b0000;
        end else if (!upper) begin
            count = count + 1;
        end
    end
endmodule

The IO signals of the module are those defined directly in the module declaration, such as:

module UpperCounter (
    input wire clk,           
    input wire reset,         
    output reg [3:0] count   
);

Here, clk, reset, and count are IO signals and can be directly accessed. The following wire upper; is an internal signal, whose value is determined by both module inputs and internal logic. While the counter logic in this example is simple, larger hardware modules often present the following challenges:

  • When the module output does not match expectations, the problematic code range is large, making it hard to quickly narrow down the issue.
  • Complex internal logic makes understanding and analysis difficult; internal signals can serve as key markers to clarify the module’s operation.

For these issues, accessing and analyzing internal signals is very effective. Traditionally, tools like Verilator and VCS are used to observe internal signals. To further lower the verification barrier, picker provides three internal signal access methods: DPI direct export, VPI dynamic access, and direct memory read/write.

DPI Direct Export

DPI (Direct Programming Interface) is an interface for interaction between Verilog and other languages. In picker’s default implementation, DPI is provided for the IO ports of the DUT. When running picker, if the --internal option is added, DPI can also be provided for internal signals. Picker will use a predefined internal signal file to extract both internal signals and IO ports from the RTL when converting to DUT.

Writing the Signal File

The signal file specifies to picker which internal signals to extract, listing the module and the internal signals to extract.

Example internal.yaml:

UpperCounter:
  - "wire upper"

The first line is the module name (e.g., UpperCounter). From the second line, list the internal signals to extract in the format “type signal_name”. For example, if upper is a wire, write "wire upper". (In theory, as long as the signal name matches the variable name in the Verilog code, it can be matched. The type can be arbitrary, but it’s recommended to use Verilog-supported types like wire, reg, logic, etc.)

The ability to extract internal signals depends on the simulator. For example, Verilator cannot extract signals starting with an underscore _.

Note: For multi-bit internal signals, you need to explicitly specify the width, so the actual format is “type [width] signal_name”.

UpperCounter:
  - "wire upper"
  - "reg [3:0] another_multiples" # This signal does not exist in this example, just to illustrate the yaml format

Export Parameter

After writing the signal file, you need to specify it explicitly when running picker using the --internal option:

--internal=[internal_signal_file]

Full command example:

picker export --autobuild=true upper_counter.sv -w upper_counter.fst --sname UpperCounter \
--tdir picker_out_upper_counter/ --lang python -e --sim verilator --internal=internal.yaml

You can find the signals.json file generated by picker for the DUT:

{
    "UpperCounter_upper": {
        "High": -1,
        "Low": 0,
        "Pin": "wire",
        "_": true
    },
    "clk": {
        "High": -1,
        "Low": 0,
        "Pin": "input",
        "_": true
    },
    "count": {
        "High": 3,
        "Low": 0,
        "Pin": "output",
        "_": true
    },
    "reset": {
        "High": -1,
        "Low": 0,
        "Pin": "input",
        "_": true
    }
}

This file shows the signal interfaces generated by picker. The first signal, UpperCounter_upper, is the internal signal we wanted to extract. The part before the first underscore is the module name defined in internal.yaml, and the rest is the internal signal name.

Signal Access

After extraction, accessing internal signals is no different from accessing IO signals—they are all XData objects on the DUT and can be accessed as dut.signal_name.

from UpperCounter import *

def test():
    dut = DUTUpperCounter()
    print(dut.UpperCounter_upper.value)

VPI Dynamic Access

VPI (Verilog Procedural Interface) is a standard interface in Verilog that allows external programs (like C) to interact with the Verilog simulator during simulation. With VPI, users can access, read, and modify signals, variables, and module instances in the Verilog simulation, as well as register callbacks to control and extend the simulation process. VPI is often used for custom system tasks, advanced verification, dynamic signal access, and waveform processing. VPI is part of the IEEE 1364 standard.

Export Parameter

picker export --help
...
--vpi Enable VPI, for flexible internal signal access default is OFF

Enable VPI support with the --vpi parameter, for example:

picker export upper_counter.sv --sname UpperCounter --tdir picker_out_upper_counter/ --lang python --vpi

Signal Access

After enabling --vpi, you can use the DUT interface dut.GetInternalSignalList(use_vpi=True) to list all accessible internal signals, and dut.GetInternalSignal(name, use_vpi=True) to dynamically construct XData for data access.

from UpperCounter import *

def test():
    dut = DUTUpperCounter()
    # List all internal signals
    # Or use dut.VPIInternalSignalList()
    dut.GetInternalSignalList(use_vpi=True)
    # Dynamically construct XData
    internal_upper = dut.GetInternalSignal("UpperCounter.upper", use_vpi=True)
    # Read access
    print(internal_upper.value)
    # Write access (writing is possible, but the value will be overwritten after dut.step; not recommended for non-reg types)
    internal_upper.value = 0x1

Direct Memory Read/Write

Both DPI and VPI-based internal signal access have some performance overhead. For maximum performance, picker implements direct internal signal access for Verilator/GSIM simulators.

Export Parameter

picker export --help
...
--rw,--access-mode ENUM:value in {dpi->0,mem_direct->1} OR {0,1}

Enable direct memory read/write for Verilator by using --rw 1, for example:

picker export upper_counter.sv --sname UpperCounter --tdir picker_out_upper_counter/ --lang python --rw 1

Signal Access

After enabling direct memory read/write, you can use dut.GetInternalSignalList(use_vpi=False) to list all internal signals, and dut.GetInternalSignal(name, use_vpi=False) to dynamically construct XData for signal read/write.

from UpperCounter import *

def test():
    dut = DUTUpperCounter()
    # List all internal signals
    dut.GetInternalSignalList(use_vpi=False)
    # Dynamically construct XData
    internal_upper = dut.GetInternalSignal("UpperCounter_top.UpperCounter.upper", use_vpi=False)
    # Read access
    print(internal_upper.value)
    # Write access (writing is possible, but the value will be overwritten after dut.step; not recommended for non-reg types)
    internal_upper.value = 0x1

Comparison of Internal Signal Access Methods

Each internal signal access method provided by picker has its own advantages and disadvantages. Choose according to your needs.

Method Name Enable Option Advantages Disadvantages Access Interface Supported Simulators Suitable Scenarios
DPI Direct Export –internal=cfg.yaml Fast Must specify signals in advance
Read-only
Recompile after changes
Same as normal pins verilator, VCS Few signals, no write needed
VPI Dynamic Access –vpi Flexible, all signals
No need to specify in advance
Slow GetInternalSignalList
GetInternalSignal
verilator, VCS Small designs or not speed-critical
Direct Mem R/W –rw 1 Fast
Flexible
No need to specify in advance
Some signals may be optimized away GetInternalSignalList
GetInternalSignal
verilator, GSIM Large designs, e.g., Xiangshan CPU

*Note: These methods are independent and can be used together.

Last modified May 7, 2025: fix(multilang): sync en with cn (963533f)