Skip to content

Netlist Simulator and Waveform Viewer

joern274 edited this page Jun 8, 2022 · 33 revisions
Attention: 

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 procedure (Overview)

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

Instantiate a simulation controller

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.

Select parts of the current netlist to be simulated

Prepare simulation

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.

Select the clock(s) driving the simulation

Select the simulation engine

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:

Load or create data for the inputs of the selected partial netlisst.

Run the simulation

Analyze the results

Similar to all other plugins, the netlist simulator can be loaded using the following Python snippet:

Setting up the Simulator

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

Running the 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

Retrieving Simulation Results

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.

Generating a Waveform File

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)

Example: Simulating a simple counter

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.

Waveform of a simple counter

Clone this wiki locally