-
Notifications
You must be signed in to change notification settings - Fork 77
Netlist Simulator and Waveform Viewer
Note: netlist simulator is currently experiencing a major makeover. The code has not been merged into master yet but can be obtained in branch [v4.0.0](https://github.com/emsec/hal/tree/v4.0.0)
The simulator framework enables the reverse engineer to simulate selected parts of the netlist in a cycle-accurate fashion and analyze the results in a graphical viewer. The simulator framework is made out of several plugins, like controller plugin to provide common functionality and API, Verilator simulator plugin and waveform viewer plugin. These plugins are not build automatically. It is recommended to force the build of all available plugins by issuing the flag -DBUILD_ALL_PLUGINS=1
flag when calling cmake
.
The simulation can be controlled from waveform viewer GUI or via Python script or Python command line. In all cases the following steps are necessary
- Instantiate a simulation controller
- Select parts of the current netlist to be simulated
- Prepare simulation
- Select the clock(s) driving the simulation
- Select the simulation engine
- Load or create data for the inputs of the selected partial netlisst.
- Run the simulation
- Analyze the results
Python:
from hal_plugins import netlist_simulator_controller
pl_sim = hal_py.plugin_manager.get_plugin_instance("netlist_simulator_controller")
GUI: Create a new controller by (left-)click on '+' button, the leftmost button in waveform viewer button toolbar.
The sequence of the following steps is not important, they can be done in any order. However, GUI will only allow to do the actual simulation if all steps below have been completed.
While the menu offers three different engines the user should always select the external Verilator engine for most accurate simulation. The build-in HAL-simulator has known issues and is kept only for testing and comparison with simulation results from early HAL days. The dummy engine is for debugging purpose only. Python:
from hal_plugins import netlist_simulator_controller
pl_sim = hal_py.plugin_manager.get_plugin_instance("netlist_simulator_controller")
GUI:
Similar to all other plugins, the netlist simulator can be loaded using the following Python snippet:
Before the simulation starts, a simulator instance needs to be created by calling 'create_simulator' on the plugin instance as follows:
sim = pl_sim.create_simulator()
Now the user must specify the subset of the netlist he wants to simulate. For this purpose, a list of gates must be specified and passed to the simulator instance sim
such that it can automatically determine input and output nets of the subgraph. This can be done using the add_gates
function as shown below.
gate_list = [g1, g2, g3, g4] # collect gates to simulate in a list
sim.add_gates(gate_list) # add the gates to the simulation instance
As stated before, the simulator will automatically detect all inputs and outputs to the simulated subgraph. Using get_input_nets
and get_output_nets
, the user can retrieve those nets and may use them later on to, e.g., set input values during simulation.
sim.get_input_nets() # get all input nets
sim.get_output_nets() # get all output nets
Next, the clock signal needs to be specified. This can be done using either add_clock_frequency
or add_clock_period
, which take the net connected to the clock and a clock frequency or period as input. Hence, the function assignes a specific clock frequency to the given net and thus allows for different clock domains.
sim.add_clock_frequency(clk, 1000000) # assign a clock frequency of 1 MHz to input net 'clk'
To avoid getting stuck in infinite loops, it may be helpful to set a iteration timeout that stops the simulation after a pre-defined number of iterations. The timeout is given in number of iterations and can be controlled using set_iteration_timeout
and get_iteration_timeout
. It defaults to 10000000
iterations and assigning a value of 0
disables the timeout.
sim.set_iteration_timeout(1000) # set the timeout to 1000 iterations
In some cases, sequential gates may come with an initial value that is assigned to their internal state during device startup. For example, this is the case for flip-flops within FPGA netlists, which may retrieve their initial state from the netlist or rather bitstream file. To accommodate this during simulation, these initial values can be loaded into the internal state of such sequential elements using load_initial_values_from_netlist
. Further, load_initial_values
may be used to specify an initial value to load into all sequential elements before running the simulation.
sim.load_initial_values_from_netlist() # load initial values into sequential gates if available
sim.load_initial_values(hal_py.BooleanFunction.Value.ZERO) # load logical 0 into all FFs before running simulation
To finally run the simulation, logical values should be assigned to all of the input nets of the simulated subgraph. The function set_input
provides exactly this functionality and takes a net as well as a value as input. It may also be used during simulation to change input values between simulation cycles. The simulation itself is started using simulate
and proceeds for the specified number of picoseconds. An example is given below.
sim.set_input(in1, hal_py.BooleanFunction.Value.ONE) # assign a logical 1 to input net in1
sim.set_input(in2, hal_py.BooleanFunction.Value.ZERO) # assign a logical 0 to input net in2
sim.simulate(3000) # simulate for 3000 ps
sim.set_input(in1, hal_py.BooleanFunction.Value.ZERO) # change the value of in1 to a logical 0
sim.simulate(2000) # simulate for another 2000 ps
To get the results of the simulation, the function get_simulation_state
may be called to retrieve the entire simulation result. It returns a simulation state containing detailed information of the simulation including all incurred events. By calling get_net_value
on that instance, the user can query the value of each net included in the subgraph at every point in time during simulation.
state = sim.get_simulation_state() # retrieve the simulation state
val = state.get_net_value(net, 1000) # get the value of net 'net' after 1000 ps
Furthermore, the simulation state holds a list of all events incurred during simulation that can be obtained using get_events
. This list may also be expanded by the user by creating and adding a new event using add_event
. The current simulation state of the simulator instance may be overwritten using set_simulation_state
. More on these operations can be found in the official Python API documentation.
To assist the simulation process, HAL can generate waveform files using the VCD file format. For this purpose, the function generate_vcd
can be called to write the current simulation instance to a waveform file. The user may specify a start and an end time (in ps) to avoid the generation of unnecessarily large traces. The VCD file can then be viewed using popular open-source tools like gtkwave. An example looks like this:
sim.generate_vcd("trace.vcd", 0, 300000)
In our tests we provide a netlist for a simple counter. The following code simulates the netlist:
from hal_plugins import netlist_simulator
pl_sim = hal_py.plugin_manager.get_plugin_instance("netlist_simulator")
sim = pl_sim.create_simulator()
sim.add_gates(netlist.get_gates())
sim.load_initial_values(hal_py.BooleanFunction.Value.ZERO)
clock_enable_B = netlist.get_net_by_id(8)
clock = netlist.get_net_by_id(9)
reset = netlist.get_net_by_id(7)
output_0 = netlist.get_net_by_id(6)
output_1 = netlist.get_net_by_id(5)
output_2 = netlist.get_net_by_id(4)
output_3 = netlist.get_net_by_id(3)
sim.add_clock_period(clock, 10000)
sim.set_input(clock_enable_B, hal_py.BooleanFunction.Value.ONE)
sim.set_input(reset, hal_py.BooleanFunction.Value.ZERO)
sim.simulate(40 * 1000)
sim.set_input(clock_enable_B, hal_py.BooleanFunction.Value.ZERO)
sim.simulate(110 * 1000)
sim.set_input(reset, hal_py.BooleanFunction.Value.ONE)
sim.simulate(20 * 1000)
sim.set_input(reset, hal_py.BooleanFunction.Value.ZERO)
sim.simulate(70 * 1000)
sim.set_input(clock_enable_B, hal_py.BooleanFunction.Value.ONE)
sim.simulate(23 * 1000)
sim.set_input(reset, hal_py.BooleanFunction.Value.ONE)
sim.simulate(20 * 1000)
sim.simulate(20 * 1000)
sim.simulate(5 * 1000)
sim.generate_vcd("counter.vcd", 0, 300000)
The generated vcd can be viewed using gtkwave.