2.2 The Command Line Interface 2.4 Configuration and Checkpointing
Simics User's Guide  /  2 Feature Overview  / 

2.3 Simics Scripting Environment

The Command Line Interface in Simics provides scripting capabilities that can be used to write parameterized configurations and scripts that control or inspect simulated sessions. For even more advanced scripting, the Python language can be used.

This chapter describes how to write simple scripts in the Simics command line interface (CLI), using control flow commands, variables and script branches. It also explains how the configuration system can be accessed from scripts, and how Python can be used for more advanced script programming.

All commands can be executed either by typing them at the prompt in the Simics console, or by writing them to a file, e.g., example.simics, and executing the command run-script example.simics, or for Python examples run-script example.py.

Scripts should normally not start the simulation. If a script starts the simulation, that is a synchronous (blocking) call. Control is not returned to CLI until the simulation is stopped. If the script does not stop itself, the simulation may be stopped by using the GUI controls or by entering Ctrl-C in CLI.

2.3.1 Script Support in CLI

2.3.1.1 Why Use CLI Scripting Instead of Python

There are a number of situations where it may make sense to use the scripting support in CLI due to its advantages over Python. Still, if the script starts to grow in size and complexity, or if more complete and detail control of the simulation is needed, then Python is the obvious choice.

2.3.1.2 Variables

The Simics command line has support for string, integer, floating point, list and Boolean variables. Variables are always prefixed with the $ character. A variable has to be created by assigning a value to it, before it can be used.

simics> $foo = "some text"
simics> $foo
"some text"
simics> echo $not_used_before
No CLI variable "not_used_before"

The defined expression can be used to test if a variable has been defined. Note that this command takes the name of the variable only, i.e. without the $.

simics> $foo = 4711
simics> if defined foo { echo "foo is defined"}
foo is defined

List variables can be indexed, something that is useful in loops for example.

simics> $foo = []
simics> $foo[0] = 10
simics> $foo[1] = 20
simics> echo $foo[0] + $foo[1]
30
simics> $foo
[10, 20]
simics> $foo += ["abc"]
[10, 20, "abc"]
simics> list-length $foo
3

CLI also has support for local variables, described later in this chapter.

2.3.1.3 Command Return Values

The return value of a command is printed on the console, unless it is used as argument to some other command. Parenthesis () are used to group a command with arguments together, allowing the return value to be used as argument. The return value can also be used as namespace in another command. Variables can be used in the same way.

simics> $address = 0x7ff00000
simics> set $address 20
simics> echo "The Value at address " + $address + " is " + (get $address)
The Value at address 2146435072 is 20

simics> $id = 0
simics> ("board.mb.cpu0.core[" + $id + "][" + $id + "]").print-time
processor                   steps  cycles  time [s]
board.mb.cpu0.core[0][0]        0       0     0.000

Although in this particular case it is simpler to write:

simics> board.mb.cpu0.core[$id][$id].print-time
processor                   steps  cycles  time [s]
board.mb.cpu0.core[0][0]        0       0     0.000
simics> $cpu = board.mb.cpu0.core[0][0]
simics> $cpu.print-time
processor                   steps  cycles  time [s]
board.mb.cpu0.core[0][0]        0       0     0.000

Parenthesis can also be used to break a command with its arguments across multiple lines, making it easier to read scripts with expressions and nested command invocations.

2.3.1.4 Control Flow Commands

The script support in CLI has support for common flow control commands such as if, else, while as well as foreach.

simics> $value = 10
simics> if $value > 5 { echo "Larger than five!" }
Larger than five!

The if statement has a return value:

simics> $num_cpus = 2
simics> (if $num_cpus > 1 { "multi" } else { "single" }) + "-pro"
multi-pro

Multi-line if-else statements must have } else { on the same line.

It is also possible to have else followed by another if statement.

simics> $b = 0
simics> if $b == 1 {
.......     echo 10
....... } else if $b == 0 {
.......     echo 20
....... } else {
.......     echo 30
....... }
20

Loops can be written with the while command.

simics> $loop = 3
simics> while $loop {
.......     echo $loop
.......     $loop -= 1
....... }
3
2
1

They can also be written using the foreach list iteration command. The range commands in the example returns a list of integers from 0 up to, but not including, 3.

simics> foreach $loop in (range 3) {
.......     echo $loop
....... }
0
1
2

Here is another example that shows how foreach can be used. The get-object-list commands return a list of all objects that implement the processor_internal interface in Simics:

simics> foreach $cpu in (list-objects -all processor_internal) {
.......     echo "Cycles on " + ($cpu->name) + ": " + ($cpu.print-time -c)
....... }
Cycles on board.mb.cpu0.core[0][0]: 0

Lists can also be written directly, for example:

simics> foreach $loop in [1, 2, 3] {
.......     echo $loop
....... }
1
2
3

Within command blocks, it can be useful to have variables that are local to the scope and thus do not collide with the names of global variables. By adding local before the first assignment of a variable, the variable is made local.

simics> $global = 10
simics> if TRUE {
.......     local $global = 20
.......     echo $global
....... }
20
simics> echo $global
10

2.3.1.5 Integer Conversion

In some cases it is useful to interpret an integer as a signed value of a specific bit size, for example when reading four bytes from memory that should be interpreted as a signed 32 bit integer. The signed, signed8, ..., signed64 commands can be used to perform the conversion.

simics> board.mb.phys_mem.set 0x7fe00000 0xffffffff 4
simics> board.mb.phys_mem.get 0x7fe00000 4
4294967295 (LE)
simics> signed32 (board.mb.phys_mem.get 0x7fe00000 4)
-1

Other useful and related commands are atoi, bits, int-to-*-float, bin, dec, hex, and oct.

2.3.1.6 Accessing Configuration Attributes

Simics configuration attributes that are of string, integer, floating point, Boolean, nil, and list types can be accessed directly from CLI using the -> operator.

simics> echo "Current project: " + (sim->project)
Current project: C:\Users\joe\Desktop\project

An object referenced with this operator returns the object's name as a string.

A nil attribute value is represented by NIL in CLI.

To access attributes that use other data types than the ones listed above, you need to use Python:

simics> @conf.myobject.attr.dictionary_attribute = { 1 : "abc" }

See chapter 2.3.4.3.1 for more information about accessing attributes from Python.

2.3.1.7 Error Handling in Scripts

When a Simics command encounters an error, an error message is typically printed, and the script execution is interrupted. In some cases the script itself want to handle the error, in order to try some alternative approach, or to present the error message with more context. The try-except statement can be used for this purpose.

simics> try {
    load-module my-own-components
} except {
    echo "Simics failed to import my-own components. Perhaps you forgot to "
    echo "install the latest modules from the development team? See the "
    echo "project web-site for more info.\n"

    interrupt-script "Cannot continue"
}

Without the try-except statement, the example above would print an error message like Error loading module and the script execution would be interrupted with an error.

The error message from the failing command can be obtained inside the except block by calling the get-error-message CLI command. The get-error-line command returns the line of the error in the script file and get-error-file the file name. The get-error-command returns the command name if the error occurred within a command.

2.3.2 Script Branches

2.3.2.1 Introduction to Script Branches

Script branches allow the user to write sequences of CLI commands that can be postponed, waiting for things to happen in the simulator, without breaking the sequential flow of commands. This is typically used to avoid breaking a script into many small sections, each run as a callback using Python.

A simple example of a script branch:

simics> script-branch "script branch description (optional)" {
    echo "This is a script branch test - going to sleep."
    board.mb.cpu0.core[0][0].wait-for-step 10
    echo "Processor registers after 10 steps:"
    board.mb.cpu0.core[0][0].print-processor-registers
}

The example above will execute the first echo command at once, and then go to sleep waiting until the first 10 instructions (steps) have run. When the step counter for the processor has reached 10, the branch will wake up and run the next two commands, echo and print-processor-registers.

A big difference between script branches and the main script is that the main script (also called main branch) may be interrupted by the user pressing the stop button or typing Ctrl-C. The script branches are unaffected by such actions and can exist in the background, coexisting with any interactive command line use.

2.3.2.2 How Script Branches Work

When a script branch is started (using script-branch), it begins executing immediately, and runs until a wait-for-, command is issued. Execution is then resumed in the main script; i.e., there is never any concurrent activity. When some activity occurs that a script branch is waiting for, the branch continues executing once the currently simulated instruction is ready.

Since only one branch can be active at once, any callback to Python from Simics will execute in the currently active branch, i.e., if a branch installs a callback, it is most likely that it will be called when the main branch is active.

2.3.2.3 Script Branch Commands

The following is a list of the commands related to script branches.

2.3.2.4 Variables in Script Branches

Variable references in CLI are evaluated when accessed. This is important to remember when writing script branches, since some commands are executed when the branch has been suspended, and variables may have been changed. To make sure that CLI variables in script branches are untouched by other scripts, they should be made local.

The following example

simics> script-branch "sample script branch" {
    $foo = 20
    board.mb.cpu0.core[0][0].wait-for-step 10
    echo "foo is " + $foo
}
$foo = 15
run

will produce the output foo is 15 while the following script will print foo is 20.

simics> script-branch "sample script branch" {
    local $foo = 20
    board.mb.cpu0.core[0][0].wait-for-step 10
    echo "foo is " + $foo
}
$foo = 15
run

2.3.2.5 Script Branches and Multi-Threaded Simulation

Large system configurations can be split up over several cells where each cell runs in its own host thread to speed up simulation. Care must be taken when writing scripts in such sessions. If the simulation is stopped or paused, for example by a breakpoint, other cells than the one where the breakpoint occurred may be ahead or behind in simulated time. The difference in time between cells is limited by the minimum latency setting. The time where cells are stopped may also differ between runs of the same setup. To ensure deterministic behavior, a script stopping a simulation as result of some event should only access the cell where the event occurred. If the script needs to access the full configuration, i.e. also objects in other cells, then all cells have to be synchronized in time. The wait-for-global-time and wait-for-global-sync commands can be used to run until a point where all cells have synchronized.

2.3.2.6 Canceling Script Branches

It is possible to cancel a suspended script branch by interrupting it using the interrupt-script-branch command. Each branch has an ID associated that can be found using list-script-branches, and that is returned by the script-branch command.

simics> $id = (script-branch "sample script branch" {
    bp.wait-for-breakpoint $bp
})

...

simics> interrupt-script-branch $id
Command 'wait-for-breakpoint' interrupted.
Script branch 1 interrupted.

2.3.2.7 Script Branch Restrictions

There are a few things related to script branches that are discouraged or not allowed:

2.3.3 Targets and parameters

This section describes the Simics target parameter framework. For the older script declaration system, see chapter 5.1. The target parameter framework introduces a few concepts:

The advantages compared with the script declaration system are as follows:

2.3.3.1 Semantics

Parameters are write-once, hence the first value provided for a parameter is what gets used. I.e. values provided by the user when running a script will take precedence over default values provided in the script. Scripts can also provide new default values for parameters declared by scripts that they include, and those defaults will take precedence over the defaults in the declaring script. Scripts can also set defaults from their script code, to facilitate the provision of default values that require calculations, potentially using other parameters.

The whole parameter tree is exposed to the user running a script, and via the write-once semantics, the user can specify/override arguments for all parameters. Arguments can be provided on the command line, but the more powerful method is to provide them via preset files.

The write-once semantics implies that the "earliest setting wins". Arguments can come from

  1. User specification at command line.
  2. User specification via preset files.
  3. (Default) values specified in parameter declarations.
  4. (Default) values specified in script code.

and the first argument for a specific parameter sticks, e.g. user input overrides values in scripts (hence they are only default values). Note that this also implies that script code cannot unilaterally set arguments, not even input to another script that it calls, it can only provide default values, which can always be overridden by an earlier setting, such as user input.

2.3.3.2 File types

The parameter declarations and the presets are specified in the standard YAML format. Usage of a standard format means that one can easily process the files outside of Simics, although to resolve file paths using %script% or %simics% knowledge of the underlying Simics installation is required.

We have two types of files: scripts and presets.

2.3.3.2.1 Script Files

Script files consist of

See section 2.3.3.3 for an example of a script.

2.3.3.2.2 Preset Files

Preset files

See section 2.3.3.4 for an example of a preset.

The write-once semantics implies that the arguments specified in a preset overrides any arguments specified in included presets. This facilitates easily creating more specialized presets where only a few arguments are changed.

Presets can also specify the script where the corresponding parameter declarations are given, using the target key. Such a preset is a simple way to describe a Simics configuration, and can be used to start Simics, meaning that the specified script is run with the arguments from the preset. The script corresponding to the preset can also be specified implicitly by including other presets using the import.

For more details about the syntax, see the Target parameters reference.

2.3.3.3 Example target

Here is a small example that illustrates how to write a target with parameters:

%YAML 1.2
---
# Declaration section
params:
  # A simple parameter
  sigma:
    type: str
    required: true
    default: sigmoid
    description: >-
      A potentially multi-line text
      describing this parameter.
  # List parameters can have typed elements
  resources:
    type: list[list[int]]
    default: []
  # A complex parameter
  epsilon:
    mu:
       type: str
    nu:
       type: int
  tau:
    # Import parameters from other script
    import: inner.yml
    # beta parameter is set by this script, not required
    provides:
    - beta
    # new default value of imported parameter
    defaults:
      gamma: true
# Type of code
code-type: simics
# Code inlined in the YAML section
cmd: |
  echo `params["sigma"]`
  echo (params.get "epsilon:mu")
  echo `params.get("epsilon:nu")`
  params.setdefault "tau:beta" (params.get "sigma")
  run-script script = "inner.yml" namespace = "tau"
...

And the imported file:

%YAML 1.2
---  
params:
  alpha:
    type: int
  beta:
    type: str
    required: true
  gamma:
    type: bool 
cmd: |
  # Use dict syntax to read parameter
  print(f"beta={params['beta']}")
...

As can be seen from the example, the target script code can be inlined in the YAML section. One can also place the code in a separate file which is referred from the parameter section using the script tag. A third option is to place the code below the parameter section, but then the file as a whole will typically no longer be valid YAML.

The example also illustrates how to import parameters from other scripts. The result is that the parameter tree defined in the imported script becomes a sub-tree in the current script, in this case with the root node tau. To refer to parameters further down in the tree, the : character is used as separator. Note that importing a parameter tree from another script does not mean that the imported script has to be run, but all scripts that are run must have been imported. When running an imported script, the root node of the imported sub-tree must be provided.

Notice that in the first file we have to declare the code type to be Simics CLI, using the code-type key, since Python is the default. The code type defaults to Simics CLI if the file (or the file specified by the script key) has extension .simics or .include and defaults to Python if the extension is .py or .yml.

Finally, as can be seen, the parameters are accessed via a global object params, both in CLI and in Python. In Python the object acts like a regular Python dictionary and in CLI it is a Simics object with various commands.

Notice how we illustrate different ways to read parameters: one can use the CLI command params.get, or params.get via inline Python, or Python dictionary notation. The latter will throw an exception if the parameter is not assigned.

For more details about the syntax, available types etc, see the Target parameters reference.

2.3.3.4 Example presets

Here is an example preset for to the example target. The target is assumed to be a file named example.target.yml:

%YAML 1.2
---
args:
  tau:
    beta: test
  epsilon:
    mu: foo
  import: include.preset.yml
...

As can be seen, argument values are specified under the args using the parameter names as keys. Also note that presets can include other presets.

Here is the the included preset, include.preset.yml:

%YAML 1.2
---
args:
  sigma: foo
  tau:
    beta: bar

target: example.target.yml
...

Notice that the first preset overrides the argument beta in the included preset. This illustrates how presets can be stacked on top of each other, only overriding what is necessary. This facilitates easily creating variants of target configurations for runs with different parameters.

Also note that the second preset specifies the target that it is based on. The result is that the preset, and all presets that import it, can be run directly.

2.3.3.5 Target definition

As mentioned at the top of this section, a target is a file with name that matches targets/*/*.target.yml, located in the Simics project or in a package available in the project.

The intention is that the top level scripts, that should be exposed to the user via the load-target command, are turned into targets by naming them accordingly.

Targets can be listed from the Simics CLI or from the shell as mentioned in 2.3.3.6. The idea is to make it easy to find the main entry points to a hardware model without looking for a particular script name in the package or project.

2.3.3.6 Run-time usage

There are two main CLI commands:

Calling load-target with a target name has the same effect as calling run-script on the file that defines the target.

Target names, script file names can also be provided directly to Simics on the command line, in order to run them. Presets for a target can be added using the --preset command line flag.

As mentioned above, the parameters are accessed via a global params object, which has a number of CLI commands as well. These can be discovered using tab completion or by running help on the object.

2.3.3.7 Using parameter references

Consider the situation where there are two scripts, perhaps hardware and software setup, that are connected by a main target script. Often the software script has parameters that should have the same value as some parameter in the hardware script, and the main script has to make sure this happens.

Here are examples of sub-scripts, alpha.yml and beta.yml.

%YAML 1.2
---
# This is alpha.yml
params:
  foo:
    type: str
    default: alpha
cmd: print(params['foo'])
...
%YAML 1.2
---
# This is beta.yml
params:
  foo:
    type: str
    required: true
  bar:
    type: int
    required: true
cmd: print(params['foo'])
...

Without parameter references, the main script can look like this:

%YAML 1.2
---
params:
  bar:
    type: int
    required: true
  alpha:
    import: "%script%/alpha.yml"
  beta:
    import: "%script%/beta.yml"
code-type: simics
cmd: |
  run-script "%script%/alpha.yml" namespace = alpha
  params.setdefault "beta:foo" (params.get "alpha:foo")
  params.setdefault "beta:bar" (params.get "bar")
  run-script "%script%/beta.yml" namespace = beta
...

Parameter references facilitate avoiding the explicit copying of parameter values, so that the main script can be expressed like this:

%YAML 1.2
---
params:
  bar:
    type: int
    required: true
  alpha:
    import: "%script%/alpha.yml"
  beta:
    import: "%script%/beta.yml"
    defaults:
      foo: ^alpha:foo
      bar: ^bar
code-type: simics
cmd: |
  run-script "%script%/alpha.yml" namespace = alpha
  run-script "%script%/beta.yml" namespace = beta
...

2.3.4 Scripting Using Python

The Intel® Simics® simulator provides support for the script language Python (http://www.python.org). By using Python the user can extend the simulator, and control it in greater detail. Python code can use functions from the simulator's API.

2.3.4.1 Python in the Intel® Simics® Simulator

The Intel® Simics® simulator always contains a Python environment. The simulator's command line interface (CLI) and all commands are implemented in Python. To find the code executed by a command, one can search the Python - .py - files of the corresponding module for the calls to the new_command, new_info_command, and new_status_command functions.

To execute Python code directly from the simulator's CLI, Python code that is to be executed should be prefixed with the @ character:

simics> @print("This is a Python line")
This is a Python line
simics>

For code spanning more than one line, the prompt will change to ....... and more code can be inserted until an empty line is entered. The full code block will then be executed (note that whitespace indentation is significant in Python):

simics> @if SIM_number_processors() > 1:
.......     print("Wow, an MP system!")
....... else:
.......     print("Only single pro :-(")
.......
Wow, an MP system!
simics>

Entering more than one line is useful for defining Python functions. It is also possible to execute Python code from a file, which is done with the run-script command.

If the Python code is an expression that should return a value to the CLI, the python command can be used, or the expression can be back-quoted. The following example selects a file with Python commands to execute depending on the number of processors in the system:

simics> run-script `"abc-%d.py" % SIM_number_processors()`

If the system has 2 processors, the file abc-2.py will be executed.

One can switch the simulator's CLI to Python mode with the python-mode command. In Python mode, indicated by

simics>>>

prompt, all input is interpreted as Python code, not as simulator's CLI commands. In Python mode, Python code lines should not be prefixed with the @ character, since that is only used in CLI mode to interpret input as Python code. Example:

simics> python-mode
Entering Python mode. Use cli_mode() or Ctrl-D to return to CLI.
simics>>>

Now Python code can be entered directly:

simics>>> print(f"Simics base package version: {SIM_version_base()}")
Simics base package version: Simics ...
None
simics>>>

As it was pointed above, to exit Python mode, one can use the cli_mode function or simply enter Ctrl-D:

simics>>> cli_mode()  # we run cli_mode here. Alternatively, enter Ctrl-D
Command line is now in CLI mode.
None
simics>

2.3.4.2 Accessing CLI Variables from Python

CLI variables can be accessed from Python via the simenv namespace, for example:

simics> $cpu = "processor"
simics> @simenv.cpu = simenv.cpu.capitalize()
simics> $cpu
Processor

As we could see in the example, simenv is imported into global namespace by default, but if it is needed elsewhere, it can be imported from the cli module.

2.3.4.3 Accessing the Configuration from Python

2.3.4.3.1 Configuration Objects

All configuration objects are visible as objects in Python. The global Python module conf holds the top level namespace, which contains all top level objects. The objects contain all their subobjects as attributes.

The configuration objects also expose their attributes as Python attributes. The attributes can be accessed via the attr attribute, or, in a legacy way, directly as Python object attributes. Thus the attributes can be both read and written as Python attributes. The following example prints the size attribute from the board.mb.rom_image object of the image class:

simics> @conf.board.mb.rom_image.attr.size
262144

Here is a legacy way to access the same attribute:

simics> @conf.board.mb.rom_image.size
262144

We recommend to access Simics objects' attributes via the attr attribute, except for a few commonly used attributes like name and classname.

If an object contains a subobject and an attribute with the same name, the subobject takes precedence and hides the attribute.

To try the previous example in an arbitrary configuration, run list-objects -all image to find available objects of the image class to use instead of the board.mb.rom_image object.

Any '-' (dash) character in the object name, or in an attribute name, is replaced by '_' (underscore). This substitution is performed because Python always treats the dash character as the minus operator. To avoid confusion the recommendation is to always use underscore.

Indexed attributes can be accessed using [] indexing in Python. It is also possible to index other list attributes this way, but it might be inefficient since the full list is converted to a Python list before the element is extracted. Here are some examples of indexed attributes access (a pcie-bus object, and a memory-space object):

simics> @conf.board.mb.nb.pci_bus.attr.pci_devices[0]
[29, 1, <the ich10_usb_uhci 'board.mb.sb.uhci[1]'>, 1]

simics> @conf.board.mb.phys_mem.attr.memory[0x100:0x10f]
(89, 236, 0, 240, 61, 0, 0, 158, 83, 255, 0, 240, 144, 37, 0)

simics> @conf.board.mb.phys_mem.attr.memory[0x10000:0x10003] = (100, 101, 102)

If the attribute contains a list, dictionary or data, then an access returns a reference instead of the actual value. This is similar to how container objects such as lists and dictionaries work in Python and allows constructs such as:

simics> @conf.board.mb.phys_mem.attr.map[0][0] = 0x1000

The example modifies the attribute at position [0][0]. To get the copy of the attribute value, the following can be used:

simics> @memory_map = conf.board.mb.phys_mem.attr.map.copy()

Note that there is a difference in how references to Simics attributes work compared to ordinary Python objects: if the attribute access returns a list, dictionary or tuple, then a reference to the full attribute is used and not only to the referenced container objects. The reason is that internally in Simics, the attribute is treated as a single value.

Consider a list of lists, such as a = [[1, 2, 3], [4, 5, 6]]. If this was a Python list, then the following applies:

b = a[0]       # b is a reference to the [1, 2, 3] list.
a[0][1] = 9    # b will now change to [1, 9, 3].
a[0] = [7, 8]  # b still references the [1, 9, 3] list, only a will change.

If a instead is a Simics attribute:

b = a[0]       # b is a reference to the first list in a, i.e. [1, 2, 3].
a[0][1] = 9    # b will now change to [1, 9, 3].
a[0] = [7, 8]  # b is still a reference to the first list in a, i.e [7, 8].

As we see, only the last line of the examples differs. The most common situation where this difference is visible is when doing list duplication. In Python a list can be duplicated in whole or part by using slicing to produce a shallow copy. In Simics, that would simply produce a reference to the same list if any of the items in the list is a container object. In this case the .copy() method has to be used.

2.3.4.3.2 Interfaces

From Python, the iface attribute of a configuration object can be used to access the interfaces it exports. Use obj.iface.name.method to accesses the method function in the name interface of the obj object. Example:

simics> @conf.board.mb.cpu0.core[0][0].iface
<interfaces of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].iface.processor_info
<processor_info_interface_t interface of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].iface.processor_info.get_program_counter
<built-in method logical_address_t (*)([conf_object_t *]) of interface method object at 0x5cb2070>
simics> @hex(conf.board.mb.cpu0.core[0][0].iface.processor_info.get_program_counter())
'0xfff0'

When called from Python, the first conf_object_t * argument for interface methods should not be used: it is passed automatically.

The last command corresponds to the following C code (with no error-checking):

conf_object_t *obj = SIM_get_object("board.mb.cpu0.core[0][0]");
processor_info_interface_t *iface = 
        SIM_get_interface(obj, PROCESSOR_INFO_INTERFACE);
logical_address_t pc = iface->get_program_counter(obj);
printf("0x%llx", pc);

2.3.4.3.3 Ports and Banks

Port objects in Simics represent ports and banks. For scripting access to objects they work exactly as a sub-object. By convention they are placed in the port and bank namespaces under the object.

Use obj.port.portname.iface.interfacename.method or obj.bank.bankname.iface.interfacename.method to access interface methods in a port object.

simics> @conf.board.mb.sb.com[0].port
<the namespace 'board.mb.sb.com[0].port'>
simics> @conf.board.mb.sb.com[0].port.HRESET
<the NS16550.HRESET 'board.mb.sb.com[0].port.HRESET'>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface
<interfaces of board.mb.sb.com[0].port.HRESET>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface.signal
<signal_interface_t interface of board.mb.sb.com[0].port.HRESET>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface.signal.signal_raise
<built-in method void (*)([conf_object_t *NOTNULL]) of interface method object at 0x7f90f32f5310>
simics> @conf.board.mb.sb.com[0].port.HRESET.iface.signal.signal_raise()

The last command corresponds to the following C code (with no error-checking):

conf_object_t *obj = SIM_get_object("board.mb.sb.com[0].port.HRESET");
signal_interface_t *iface = 
        SIM_get_interface(obj, SIGNAL_INTERFACE);
iface->signal_raise(obj);

2.3.4.3.4 Port Interfaces

Simics also has a legacy mechanism for providing named entry points to objects, called port interfaces. This mechanism is still used by some models. Named port interfaces are referenced from other objects using a list of [object, portname] instead of just a single object reference. Port interfaces are accessed from Python in a similar way to interfaces.

Use obj.ports.portname.interfacename.method to access the interfacename interface in port portname of the object obj. Example:

simics> @conf.board.mb.cpu0.core[0][0].ports
<ports of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET
<interfaces of port RESET of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET.signal
<signal_interface_t interface of board.mb.cpu0.core[0][0]>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET.signal.signal_raise
<built-in method void (*)([conf_object_t * NOTNULL]) of interface method object at 0x5cb22d0>
simics> @conf.board.mb.cpu0.core[0][0].ports.RESET.signal.signal_raise()

The last command corresponds to the following C code:

conf_object_t *obj = SIM_get_object("board.mb.cpu0.core[0][0]");
signal_interface_t *iface = 
        SIM_get_port_interface(obj, SIGNAL_INTERFACE, "RESET");
iface->signal_raise(obj);

2.3.4.4 Accessing Command Line Commands from Python

At times, it can be useful to access command line commands from a Python script file. This can be done in two ways which are described below.

The first way to access command-line commands from Python is to use the run_command(cli_string) function, which takes a string which is then evaluated by the command line front-end. For example, write run_command("list-objects") to execute the list-objects command. Any return value from the command is returned to Python. There is also the quiet_run_command function, which captures any output produced by the command and returns a tuple with the command return value as first entry, and the text output as the second. More details about both functions can be found in the CLI Classes and Functions in the API Reference Manual.

The second way to access command line commands from Python is to use wrapper functions from two special namespaces. The first namespace is global_cmds (provided by the cli module). It allows to run commands which are not tied to any Simics object. For example, the list-objects command can be executed as global_cmds.list_objects(). The commands which are tied to Simics objects (namespace commands) can be executed via the cli_cmds namespaces which can be accessed through a Python object representing a Simics objects. For example, the sim.info command can be executed as conf.sim.cli_cmds.info().

Please note that Simics command-line commands are defined to only be executed in the Simics API execution context known as Global Context. If a command is executed while not in Global Context then Simics will stop with an error message. Though, this doesn't happen often in practice; usually only when CLI commands are be called directly from user callbacks installed for haps or notifiers, or from device models. For more information about Simics execution contexts and how to get to Global Context, please see section "API Execution Contexts" in the Simics API reference manual.

2.3.4.5 The Script Branch API

Script branches can be manipulated from Python using the script branch API. A script branch can be created using sb_create, passing a Python function that defines the script branch.

From the script branch Python function, one can use sb_wait to suspend the script branch. From somewhere else, the branch can be woken up again by calling sb_signal_waiting. Thus one can make the script branch wait on arbitrary conditions. Example:

def script_branch(data):
    print("Branch start")
    wait_id = cli.sb_get_wait_id()
    data.append(wait_id)
    cli.sb_wait("wait", wait_id)
    print("Branch done")

data = []
print("Create branch")
cli.sb_create(lambda: script_branch(data))
wait_id = data[0]
time.sleep(1)
print("Signal branch")
cli.sb_signal_waiting(wait_id)

One can also access the existing script wait functionality using the ability to run any CLI command, described in 2.3.4.4. As an example:

# Sample script branch stopping simulation after 5 cycles
# executed on the clock object.
def script_branch():
    clock.cli_cmds.wait_for_cycle(cycle = 5, _relative = True)
    cli.global_cmds.stop()

cli.sb_create(script_branch)

2.3.4.6 The Simics API

The Simics API is a set of functions that provide access to Simics functionality from loadable modules (i.e., devices and extensions), and Python scripts. All functions in the Simics API have a name that starts with "SIM_". They are described in details in the API Reference Manual.

By using the api-help and api-search commands you can get the declarations for API functions and data types. api-help identifier will print the declaration of identifier. api-search identifier lists all declarations where identifier appears.

Note that while api-help topic does the same thing as help api:topic, the help-search command will not search through the API declarations.

The Simics API functions are available in the simics Python module. This module is imported into the Python environment in the frontend when Simics starts. However, for user-written .py files, the module must be imported explicitly:

from simics import *

Errors in API functions are reported back to the caller using frontend exceptions. The exception is thrown together with a string that describes the problem more in detail. Examples of exceptions are SimExc_General, SimExc_Memory, SimExc_Index, and SimExc_IOError. In DML and C/C++, these exceptions have to be tested for using SIM_clear_exception or SIM_get_pending_exception. In Python, such exceptions result in regular Python exceptions.

For the Python environment, Simics defines an exception subclass for each of its defined exceptions in the simics module. These are raised to indicate exceptions inside the API functions. When errors occur in the interface between Python and the underlying C API function, the standard Python exceptions are used; e.g., if the C API function requires an int argument, and the Python function is called with a tuple, a Python TypeError exception is raised.

2.3.4.7 Haps

A hap is an event or occurrence in Simics with some specific semantic meaning, either related to the target or to the internals of the simulator.

Examples of simulation haps are:

There are also haps which are related to the simulator, e.g., (re)starting the simulation or stopping it and returning to prompt.

In the Simics documentation, the word event is used exclusively for events that occur at a specific point in simulated time, and hap for those that happen in response to other specific conditions (like a state change in the simulator or in the simulated machine).

A callback can be invoked for all occurrences of the hap, or for a specified range. This range can be a register number, an address, or an exception number, depending on the hap.

A complete reference of the haps available in Simics can be found in the Haps in the Simics Reference Manual.

2.3.4.7.1 Example of Python Callback on a Hap

This example uses functions from the Simics API to install a callback on the hap that occurs when a control register is written. It is intended to be part of a .simics script, that extends an QSP-x86 machine setup. The SIM_hap_add_callback_index() function sets the index of the control register to listen to, in this case the %ia32_feature_control register in an x86-intel64-turbo processor.

@ia32_feature_control_reg_no = conf.board.mb.cpu0.core[0][0].iface.int_register.get_number("ia32_feature_control")

# print the new value when %ia32_feature_control is changed
@def ctrl_write_ia32_feature_control(user_arg, cpu, reg, val):
    print("[%s] Write to %%ia32_feature_control: 0x%x" % (cpu.name, val))

# install the callback
@SIM_hap_add_callback_index("Core_Control_Register_Write",
                            ctrl_write_ia32_feature_control, None,ia32_feature_control_reg_no)

In CLI, the same example would look like:

simics> script-branch {
    local $cpu = (pselect)
    while TRUE {
        $cpu.bp-wait-for-control-register -w ia32_feature_control
        echo "[" + $cpu + "] Write to %ia32_feature_control: "+ ((hex ($cpu.read-reg ia32_feature_control)))
    }
}

2.3.4.7.2 Example of Python Callback on Core_Log_Message_Extended Hap

This example shows how to add a callback to the Core_Log_Message_Extended. This allows for better control when handling log messages. This example writes all log messages to a file that is associated with the cpu object.

The Core_Log_Message_Extended hap will only be triggered for messages with log level less than or equal to the log level setting of the object.

from simics import *

class file_log:

    def __init__(self, fname, obj, level):
        # setup logging
        try:
            self.f = open(fname, 'w')
        except Exception as msg:
            raise Exception("Failed to open file %s, %s" % (fname, msg))
        self.obj = obj
        self.level = level

        # install the callback
        SIM_hap_add_callback_obj(
            "Core_Log_Message_Extended", obj, 0, self.log_callback, None)

    def log_callback(self, not_used, obj, log_type, message, level, group):
        type_str = conf.sim.log_types[log_type]
        if level <= self.level:
            self.f.write("[%s %s] %s,  level=%d, group=%d\n" % (
                    obj.name, type_str, message, level, group))

file_log('log_cpu.out', conf.board.mb.cpu0.core[0][0], 1)

2.3.5 Simics as Python module

Simics is built and exposed as a Python extension module (https://docs.python.org/3/extending/extending.html). This means that the Python module simics can be imported into any Python, and hence Simics can be easily integrated into a Python-based software project in a similar way as any other Python package.

As examples of how to use this, we exhibit two ways to boot to an UEFI shell using the QSP-x86 package and a user provided Python. The examples use a Linux host, but the steps are analogous on Windows. The examples assume that the necessary Simics packages have been installed and a Simics project created.

The first example shows how to run a Python script booting QSP using a host Python 3.10. The environment variable PYTHONPATH must be set to make Python find the Simics Python module and the site-packages directory that is included in Simics-Base.

Precise paths will naturally not be like in the example when run elsewhere.

$ ./simics  -v
Simics Base             1000    7.0.0     (7006) 2023-12-14
Quick-Start Platform    2096    7.0.0     (7006) 2023-12-14

$ cat qsp.py 
import simics
import conf
import cli
# Turn off all log messages
cli.global_cmds.log_level(level=0)
# Make Simics quit after boot
conf.sim.batch_mode = True
simics.SIM_load_target("qsp-x86/uefi-shell", "", [], [])
conf.bp.console_string.cli_cmds.run_until(
     object=conf.qsp.serconsole.con, string="Shell> ")
print("QSP boot done")

$ PYTHONPATH=../install/simics-7.0.0/linux64 python3.10 qsp.py
QSP boot done
$ 

Note that if Simics is not run from the created project, the environment variable SIMICS_INIT_PROJECT must be set to the project path.

The previous example is limited in that it uses the site-packages directory, containing Python modules needed by Simics, distributed in the Simics-Base package. These modules are built for Python 3.10.

The standard way to integrate external Python modules into a Python software project is to install them using pip. Simics and its dependent Python modules can be installed in that way by pointing to the Simics-Base installation, in this example via the BASE environment variable.

The second example shows how to do that using Python 3.11 and then boot QSP like in the previous example. In addition to the assumptions in the first example, here we also assume that pip packages can be downloaded and installed. The latter may require various libraries to be installed on the host, as well as header files for the host Python 3.11 being used. Note that we have split up the required packages into different parts. Installing the packages in gui-requirements.txt is only needed if you want to use the Simics GUI. Those packages require more libraries on the host, such as libgtk.

$ python3.11 -m venv venv
$ source venv/bin/activate
$ python -m pip install wheel
$ python -m pip install $BASE/linux64
$ python -m pip install --requirement gui-requirements.txt
$ python qsp.py
QSP boot done
$ 

Note that there is nothing inherent in this example that prohibits using Python 3.12 or any other newer Python version. However, difficulties may occur when installing the required Python packages if no pre-built wheels are available.

2.3.5.1 Launching the simulator from your virtual environment

Above we set up a virtual environment which includes the Simics-Base distribution package and its dependencies. A distribution package in Python can include several Python packages and modules. Above we saw the use of the simics package, but it also includes a Python module to launch the simulator called runsimics.

If you have set up and activated your virtual environment as above, then you can launch the simulator from it.

python -m runsimics --project your-project

This will function the same as the simics launcher. You can pass extra command line arguments to the simulator etc. As runsimics does not go through the trampoline launch scripts in your project you have to specify the project manually if you want it.

2.2 The Command Line Interface 2.4 Configuration and Checkpointing