Internal Signals
Categories:
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.