Simics User's Guide

1 Introduction

1.1 Introduction

This document describes the features of the Intel® Simics® simulator, with in-depth explanations on their usage and implementation. A gentler introduction is provided in the Getting Started.

The first chapters cover various aspects of Simics, from its command-line and scripting to the way simulation can be inspected and manipulated. The second part of the manual is dedicated to software debugging, both with Simics stand-alone or when using it as a back-end for other debuggers, such as Intel® System Debugger or GDB.

A simple virtual platform called Quick-Start Platform (QSP) is available to all users. This platform is used in many examples in the documentation and is intended to help users users familiarize themselves with many features of the Intel Simics simulator.

1.2 Simulation Concepts

1.2.1 The Limits of Simulation

Simics is a system-level instruction set simulator. This means that:

In practice, what this means is that there is no code that is too "low-level"—Simics can run, and debug, any kind of software: firmware, hardware drivers, operating systems, user-level applications, whatever. There are some caveats, though:

1.2.2 Non-intrusive Inspection and Debugging

Simics has powerful built-in inspection and debugging facilities. These include:

Because these are implemented in the simulator, no debugging software needs to be on the target at all. As a result, the debugging machinery is completely invisible to the target (and thus to any software running on it).

1.2.3 Simulated Time

One of the most powerful properties of full-system simulation is that time inside the simulation and time in the real world are two completely different things. This brings a number of substantial benefits:

These advantages apply to the entire simulated system, whether it is a single target machine or an entire network.

2 Feature Overview

2.1 Using Simics from the Command Line

This chapter describes command line options accepted by Simics.

Even on Windows hosts, running Simics from the command line is useful. For example, when running automatic test scripts, profiling or benchmarking. Whole test sessions can be automated using the CLI or Python, and executed directly from the Window command shell.

Below is an example on how Simics can be started from the command line using a Simics project.

Simics can also be started by running a similar start script in the [simics]\bin directory ([simics]/bin on Linux). This will run Simics without a project, and is usually not recommended.

This will run the firststeps.simics script, which will start the QSP-x86 machine using its default configuration.

To quit the Simics shell you can type quit at the Simics prompt.

The full set of command line options are documented in the simics section in the Simics Reference Manual.

2.2 The Command Line Interface

The Simics Command Line Interface (CLI) is an advanced text based user interface with built-in help system, context sensitive tab-completion, and scripting support (both built-in and for use with Python). It is provided as part of Simics.

If Simics graphical user interface (GUI) is used, the CLI is accessible via the Simics Control window (Tools → Command Line Window). When running Simics without a GUI, the CLI is accessible directly at the command line/shell where Simics is started.

2.2.1 Invoking Commands

Commands are invoked by typing them at the command line followed by their arguments. The synopsis part of a command documentation explains how to execute a command (you can see many examples in the reference manuals). Here are two examples:

Arguments starting with a hyphen are flags and are always optional. Flags can be more than one character long, so it is not possible to write -xy for -x -y. The order of the flags is not significant and they can appear anywhere in the argument list.

Arguments enclosed within square brackets are optional; in the example above, it is not necessary to specify cpu-name, but address is required. The last argument to command1 is either a size or a name, but not both. Such arguments are called polyvalues and can be of different types. Size and name are called sub-arguments.

If an argument is followed by three dots as the file argument in command2 it indicates that the argument can be repeated one or more times.

The type of the arguments; e.g., whether they are integers or strings, should be evident from their names. For example, size should be an integer and name a string if not documented otherwise.

Integers are written as a sequence of digits beginning with an optional minus character for negative numbers. Hexadecimal numbers can be written by prefixing them with 0x, octal numbers with 0o, and binary numbers with 0b. Integers may contain "_" characters to make them easier to read. They are ignored during parsing. For example:

simics> 170_000
170000
simics> 0xFFFF_C700
4294952704

Strings are written as is or within double quotes if they contain spaces or begin with a non-letter. Within double quotes, a backslash (\) is an escape character, which can be used to include special characters in the string.

The supported escape sequences are the usual C ones: \n for newline, \t for tab, \033 for the octal ASCII character 33 (27 decimal) escape, etc. \ followed by one, two or three octal digits, or \x followed by exactly two hexadecimal digits is the corresponding byte value.

\u followed by exactly four hexadecimal digits is the corresponding Unicode character. CLI strings are in the current version of Simics always stored in their UTF-8 encoding, which means that a single \u character can be represented as several bytes in the CLI string. As this is expected to change in a future version of Simics, do not rely on this functionality.

simics> echo no_quotes_needed
no_quotes_needed
simics> echo "first line\nsecond line"
first line
second line
simics> echo "Two As: \101 \x41, and a micro sign: \u00b5"
Two As: A A, and a micro sign: µ

On a Windows machine, strings used as paths to files can be written in several different ways. If the path does not contain any blank spaces, it can be written without quotes, using backslashes to separate the directories in the path, like C:\temp\file.txt. If the path contains spaces, it has to be written within quote characters, and the directory separators have to be written using double backslashes. This is due to the way that the Simics command line uses backslashes to generate special characters like newline and tab. Such a path would look like "C:\\Users\\joe\\Documents\\file.txt".

Here are some possible invocations of the commands above:

simics> command1 -small board.mb.cpu0.core[0][0] 0x7fff_c000 14 -y
simics> command1 0x7fffc000 foo
simics> command1 -x "Pentium 4" 0x7fff_c000 -8
simics> command2 "/tmp/txt" "../bootdisk" floppy

In the first example cpu-name is passed as the string board.mb.cpu0.core[0][0] and size as the integer 14. In the second invocation cpu-name has been omitted and name is set to the string foo. The third example illustrated the use of a string containing a space. In all command1 examples the address is set to the hexadecimal value 0x7fffc000. command2 takes a list of at least 1 string.

A few commonly used commands have aliases. For example, it is possible to write c for continue and si for step-instruction for example. Command aliases are documented with their corresponding command in the Simics Reference Manual.

Parenthesis can be used to break a command with its arguments across multiple lines. In the console, the prompt will change to ....... for code spanning more than one line. The example shows that use case, as well as how parenthesis are used to encompass expressions.

simics> (echo 10
....... + (20 - 5)
....... + (max 4 7))

2.2.1.1 How are Arguments Resolved?

Simics tries to match the provided arguments in same the order as they appear in the synopsis. If the type of the next argument is identical to what is typed at the command line, the argument will match. If there is a mismatch and the argument is optional, the argument will be skipped and the next argument will be matched, and so on. If a mismatching argument is not optional, the interpreter will fail and explain what it expected. For polyvalues, the argument will match if one of its sub-arguments matches.

There are situations when this method is not sufficient. For example, when two arguments both have the same type and are optional, there is no way to know which argument to match if only one is given. This is resolved by naming the arguments: arg-name=value. For example command1 in the example above can be invoked like this:

simics> command1 size=32 -y address = 0xf000 -small cpu-name=board.mb.cpu0.core[0][0]

Thus there is no ambiguity in what is meant and in fact this is the only way to specify a polyvalue with sub-arguments of the same type. Note also that named arguments can be placed in any order.

2.2.1.2 Referring to Simics Objects

Many Simics commands accept configuration object references as arguments. An object reference is simply a string which contains the fully qualified name of the object. Simics provides a hierarchical namespace for objects. The fully qualified name is similar to a file system path, but with the parts separated by dots. For example, the fully qualified name of cpu0 in the system0 namespace is system0.cpu0.

2.2.1.3 Namespace Commands

Configuration objects (such as devices or CPUs) that define user commands usually place them in a separate namespace. The namespace is the fully qualified name of the object. Interfaces may also define commands, in which case all objects implementing these interfaces will inherit the commands in their own namespace.

Namespace commands are invoked by typing the fully qualified name of the object, followed by a dot and the command name: system.component.object.command, e.g.,

simics> system0.board0.cache0.print-status

All namespace commands are listed in the Simics reference manuals for the class or interface they belong to.

When using large configurations with a hierarchical structure of components and objects it can be inconvenient to type the fully qualified name all the time when invoking namespace commands. You can than use the change-namespace command (alias cn) to set a current namespace just like navigating in a file system with the cd command. From the current namespace you can refer to objects with a relative name, for example:

simics> cn system0.board0
simics:system0.board0> cache0.print-status

Note that the Simics prompt changes to reflect the new position in the hierarchy. You can only change the current namespace to other components. It would have been illegal to do:

simics> cn system0.board0.cache0
system0.board0.cache0 is not a component

Cache0 is not a component, it is an object located in the cache0 slot (see 2.4.4 and 2.4.5 for more information on how objects are named and referenced).

To go "up" one level in the hierarchy you can type cn ..:

simics:system0.board0> cn ..
simics:system0>

You can still refer to other components relative to the root by writing a dot before the fully qualified name, e.g.:

simics:system0> .system1.cpu0.ptime
processor     steps  cycles  time [s]
system1.cpu0  14545   14545     0.000

The command current-namespace is provided to get the current namespace and can be used in scripts to save a location in a CLI variable:

simics:system0> current-namespace
.system0
simics:system0> $location = (current-namespace)
simics:system0> cn ..
simics> cn $location
simics:system0>

See section 2.3 for more information on Simics scripting and CLI variables.

2.2.1.4 Expressions

The CLI allows expressions to be evaluated, for example:

simics> print -x 2*(0x3e + %cr0) + %dx

The precedence order of the operators is as follows (highest first):

$variable access
%register access
[]list indexing
->attribute access
powpower of
~bitwise not
*, /, %multiplication, division, modulo
+, -addition, subtraction
<<, >>left, right shift
&bitwise and
^bitwise xor
``
<, <=, ==, !=, >=, >comparison
notboolean not
andboolean and
orboolean or

Parentheses can be used to override the priorities. Commands which return values can also be used in expressions if they are enclosed within parentheses:

simics> print -x (board.mb.cpu0.core[0][0].read-reg cr0)

Values can be saved in variables for later use. You set a variable by simply giving an assignment command such as $var = 15. You can also store a command in a variable such as $my_read_reg = board.mb.cpu0.core[0][0].read-reg which is different from storing the return value from a command $value_cr0 = (board.mb.cpu0.core[0][0].read-reg cr0).

2.2.1.5 Interrupting Commands

2.2.2 Tab Completion

The command line interface has a tab-completion facility. It works not only on commands but on their arguments as well. The philosophy is that the user should be able to press the tab key when uncertain about what to type, and Simics should fill in the text or list alternatives.

Tab completion on a Windows host does not work when running in a Cygwin terminal, but only from the graphical user interface or from a Windows command line console.

For example com<tab> will expand to the command beginning with com or list all commands with that prefix, if there is not a unique such command. Similarly, disassemble <tab> will display all arguments available for the command. In this case Simics will write:

address =   bytes =   count =   cpu-name =

to indicate that these alternatives for arguments exists. Typing disassemble cp<tab> will expand to disassemble cpu-name = and a further tab will fill in the name of the CPU that is defined (or list all of them).

2.2.3 Help System

The most useful Simics commands are grouped into categories. To list these categories, just type help at the command prompt. The list should look like this:

simics> help
[...]
Type help category to list the commands for a specific category. Here is a list
of command categories:

  Breakpoints    Disks      Inspection       Modules      Probes      Registers
  CLI            Execution  Instrumentation  Networking   Processors
  Components     Files      Logging          Notifiers    Profiling
  Configuration  Help       Matic            Parameters   Python
  Debugging      Image      Memory           Performance  Recording
[...]

Note that since Simics's configuration can change between sessions and even dynamically through the loading of modules, the list of commands and command categories may look different.

Type help category for a list of commands, e.g., help Help will list all commands belonging to that category:

simics> help category:Help
Commands available in the "Help" category:

api-help           get API help
api-search         search API help
copyright          print full Simics copyright information
get-command-args   get list of command arguments
help               help command
help-search        search for text in documentation
license            print Simics license
list-attributes    list all attributes
list-commands      list CLI commands
release-notes      display product release notes
version            display Simics version

Type help command to print the documentation for a specific command.

The help command can do much more than printing command documentation: it gives you access to nearly all Simics documentation about commands, classes, modules, interfaces, API types and functions, haps and more according to the configuration loaded in the simulator. All documentation is also available in the reference manuals.

Here are some more examples of usage of the help command:

simics> help print-time
[... print-time command documentation ...]

simics> help board.mb.cpu0.core[0][0].disassemble
[... <processor_info>.disassemble command documentation ...]

simics> help <processor_info>.disassemble
[... <processor_info>.disassemble command documentation ...]

simics> help board.mb.cpu0.core[0][0]
[... <x86QSP1> class documentation ...]

simics> help x86QSP1
[... <x86QSP1> class documentation ...]

simics> help processor_info
[... <processor_info> interface documentation ...]

simics> help board.mb.cpu0.core[0][0].freq_mhz
[... <x86QSP1>.freq_mhz attribute documentation ...]

simics> help x86QSP1.freq_mhz
[... <x86QSP1>.freq_mhz attribute documentation ...]

simics> help Core_Exception
[... Core_Exception hap documentation ...]

simics> help SIM_get_mem_op_type
[... SIM_get_mem_op_type() function declaration ...]

simics> help x86-intel64-turbo
[... x86-intel64-turbo module documentation ...]

When a name matches several help topics (for example, a command and an attribute, or a module and a class), help will print out the first topic coming in this order: command categories, commands, classes, interfaces, haps, modules, attributes, API functions and symbols. It will also inform you at the end of the documentation output that other topics were matching your search:

simics> load-module NS16450
simics> help NS16450
[... NS16450 class documentation ...]
     
Note that your request also matched other topics:
  module:NS16450

If you type help module:NS16450, the module documentation will be printed instead:

simics> help module:NS16450
[... NS16450 module documentation ...]

You can use specifiers like module: or class: at any time. It will also allow the help command to provide you with better tab-completion, since only items in the selected category of documentation will be proposed. The following specifiers are available: object:, class:, command:, attribute:, interface:, module:, api:, hap: and category:.

By default, help does not propose tab-completion for modules and API symbols, because they tend not to be the most searched for and would clutter the tab-completion propositions unnecessarily. You can get tab-completion for those by specifying module: or api: in front of what you are looking for.

The help-search command can search for keywords in the documentation provided by help. Type help-search keyword to get a list of all documentation topics matching this keyword. Its alias is apropos, named after the UNIX command-line utility which has similar behavior.

simics> help-search step
The text 'step' appears in the documentation
for the following items:
 
Command        <cycle>.cycle-break
Command        <cycle>.cycle-break-absolute
Command        <cycle>.print-time
Command        <cycle>.wait-for-cycle
Command        <cycle>.wait-for-time
[...]
Interface      step_info

simics> apropos step
[... yields the same output ...]

2.2.4 Simics's Search Path

Many Simics commands will look up files based on the current directory. When Simics is launched from the command line, the current directory is the current directory of the shell Simics was launched from. When Simics is launched by double clicking its icon, the current directory is the project directory. This may be impractical when writing scripts or building new configurations, so Simics provides two features to ease directory handling:

Simics's search path can be manipulated using the add-directory, clear-directories and list-directories commands. Simics's search path is also used when looking for image files belonging to checkpoints or new configurations. This is described in section 2.4.2.3.1.

Remember that setting a CLI variable to a path with %simics% or %script% does not in itself evaluate the path marker. This means that the path marker may evaluate to another directory than is anticipated. The following two lines evaluate quite differently:

simics> $just_a_string = "%script%/images/my_image"
simics> $absolute_path = (lookup-file "%script%/images/my_image")

The first CLI variable reads the given text and may evaluate to wherever. The second variable is evaluated locally and reads the absolute path for the my_image file that is located nearby the script.

Although the Simics search path is saved in the sim object in checkpoints, allowing image files that were found through it to be opened again by the checkpoint, it is not available until the object creation phase. Module initialization code should not rely on the Simics path since that code is run before the sim object from the checkpoint has been created.

2.2.5 Using the Pipe Command

The pipe command lets you send the output of a Simics command to a shell command through a pipe:

simics> pipe "help" "grep Tracing"

This will run help (which lists all Simics commands categories) and send its output to the standard input of the grep Tracing process. grep will discard all lines not containing "Tracing" and forward the rest to its standard output, which will be printed on the Simics terminal.

The pipe command can be used to send all the output of a command to a file:

simics> pipe "stepi 1000" "cat > trace.txt"

Or you can use it to view information using the shell command more:

simics> pipe "print-processor-registers -all" more

Note that you have to enclose both the Simics command (the first argument) and the shell command (the second argument) in double quotes if they contain whitespace or other non-letter characters.

2.2.6 Running Shell Commands

The ! Simics command can be used to run command line commands. It will take everything after the ! sign and run it in a command interpreter (the current shell, on Linux, and cmd.exe, on Windows). For example:

2.2.7 Command-line Keyboard Shortcuts

The Simics CLI supports two modes with different keyboard shortcuts: Windows and GNU Readline style. Most shortcuts are the same in both modes but there are some minor differences between the two as listed in the following table. The mode can be set in the preferences, via the GUI or the CLI:

simics> prefs->readline_shortcuts = TRUE
simics> save-preferences

The command line in Simics can be accesses in several different ways. The shortcuts are supposed to be the same everywhere, but some terminals and telnet clients may not forward certain key combinations to Simics. A typical example where keyboard shortcuts do not work properly is the Cygwin terminal on Windows hosts. To run Simics in command-line mode on Windows, a standard Windows command line console is recommended.

2.2.7.1 List of Shortcuts

The following is a list of all keyboard shortcuts supported in Simics, where some are marked as Windows or GNU Readline only.
Move Shortcuts

ActionShortcuts
Move character leftCtrl-B, Left
Move character rightCtrl-F, Right
Move word leftAlt-B, Ctrl-Left
Move word rightAlt-F, Ctrl-Right
Move to start of lineCtrl-A (GNU Readline), Home
Move to end of lineCtrl-E, End

Edit Shortcuts

ActionShortcuts
Enter lineCtrl-J, Ctrl-M, Enter
CopyCtrl-C, Ctrl-Insert
PasteCtrl-Y (GNU Readline), Ctrl-V, Shift-Insert
CutCtrl-X, Shift-Delete
Cut to end of lineCtrl-K
Cut to start of lineCtrl-U
Cut previous wordCtrl-W
Select character leftShift-Left
Select character rightShift-Right
Select word leftCtrl-Shift-Left
Select word rightCtrl-Shift-Right
Select to start of lineShift-Home
Select to end of lineShift-End
Select lineCtrl-A (Windows)
Delete character leftCtrl-H, Backspace
Delete character rightCtrl-D, Delete
Delete word leftCtrl-Backspace, Alt-Backspace, Alt-Delete
Delete word rightAlt-D, Ctrl-Delete
Delete to start of lineCtrl-Home
Delete to end of lineCtrl-End

History Shortcuts

ActionShortcuts
Next in historyCtrl-N, Down
Previous in historyCtrl-P, Up
First in historyAlt-<, Page Up
Last in historyAlt->, Page Down
Reverse searchCtrl-R
Scroll page upShift-Page Up
Scroll page downShift-Page Down

Completion Shortcuts

ActionShortcuts
Auto completeCtrl-I, Tab
Show completionsAlt-?

Transpose Shortcuts

ActionShortcuts
Uppercase wordAlt-U
Lowercase wordAlt-L
Capitalize wordAlt-C
Transpose charactersCtrl-T
Transpose wordsAlt-T, Ctrl-Shift-T

Undo Shortcuts

ActionShortcuts
Cancel multi-line editingCtrl-G
UndoCtrl-_, Ctrl-Z (Windows)
Revert lineAlt-R
Clear screenCtrl-L

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.4 Configuration and Checkpointing

Simics includes a configuration system used to describe the state of the simulated machines.

Simics's configuration system is object-oriented: a simulated machine is represented as a set of objects that interact when the simulated time advances. Typically, processors, memories and devices are modeled as objects. Each object is defined by a number of properties that are called attributes. A processor object, for example, will have an attribute called freq_mhz to define its clock frequency (in MHz).

Simics's configuration system is flexible: it is possible to create and delete objects dynamically, as well as access the attributes of all objects for reading and writing at any time.

Simics's configuration system allows its elements to be saved so that a complete simulated machine state can be written to a set of files. This set of files is called a checkpoint.

This chapter describes the Simics configuration system as well as the different layers built on top of it to handle more specific tasks:

2.4.1 Basics

As mentioned above, Simics's configuration system is object-oriented. A Simics object is instantiated from a Simics class. The core of Simics defines some useful classes, but most of the classes (processors, device models, statistic gathering extensions) are provided by modules that are loaded by the simulator when requested.

For example, the x86-p4 module defines, not surprisingly, the x86-p4 class. Note that a module may define several classes. Since modules advertise the classes they define, Simics can load modules transparently as objects are instantiated.

A class defines attributes that represent both the static and dynamic state of the instantiated objects. The static state includes information that does not change during the simulation (like a version number in a register) while the dynamic state covers the part of the device that are affected by the simulation (registers, internal state, buffers, etc.).

Let us take the example of an x86-p4 processor and have a closer look at how it can be configured using attributes:

As you noticed, attributes may be of various types. A complete description is available in the next section.

2.4.2 Checkpointing

Simics's configuration system can save the complete state of a simulation in a portable way. This functionality is known as checkpointing, and the set of files that represent the elements of the systems are called a checkpoint.

Saving and restoring a checkpoint can be done from the command line with the write-configuration and read-configuration commands.

A checkpoint consists of the following files, collected under a directory:

Below is a portion of a checkpoint file showing an object. Saved objects are always represented as OBJECT object-name TYPE class-name { attributes }. In this case we have an instance of the DEC21143 class (fast Ethernet LAN controller interfacing the PCI bus) named dec0. The attribute pci_bus is used to connect the device to the PCI bus.

OBJECT dec0 TYPE DEC21143 {
        queue: cpu0
        component: eth_adapter_cmp0
        component_slot: "dec"
        object_id: "dec0"
        build_id: 0xbb9
        pci_bus: pci_bus0
        ...
}
OBJECT ... TYPE ... {
...

Objects are saved in the main checkpoint file in no specific order.

2.4.2.1 Compatibility

Simics maintains checkpoint compatibility with older versions, i.e. it is always possible to continue using checkpoints created in a previous version of Simics when upgrading to a new version. Compatibility is always maintained for one major version older than the oldest of the supported API versions. For checkpoints older than that, load the checkpoint with a newer version of Simics and create a new checkpoint.

The opposite is not true. Trying to load a checkpoint created in a newer version of Simics than the local version will typically not work. The same restriction may apply even between minor Simics releases. For example, a checkpoint created with Simics 3.2.2 is not guaranteed to load correctly in the older Simics 3.2.1 release.

2.4.2.2 Attributes

The short example of the dec0 description only uses a few types of attribute values: strings, objects, and hexadecimal integers. The possible attribute types are:

Each attribute belongs to one of the following categories. Note that only attributes of the first two categories are saved in checkpoints.

2.4.2.3 Images

Simics implements a special class called image for objects that potentially need to save a huge amount of state, like memories and disks. An image represents a big amount of raw data using pages and compression to minimize disk usage.

To save space and time, images do not necessarily save their entire state every time a checkpoint is written. They can work in several ways:

It is important to understand that when used in incremental mode, images create dependencies between checkpoints. A checkpoint can only be loaded if all previous checkpoints are intact.

As an example, let us have a look at an assumed disk image:

...
}
OBJECT disk0_image TYPE image {
        ...
	files: (("tango1-fedora5.craff", "ro", 0, 0x4c5abc000, 0),
                ("disk0_image.craff", "ro", 0, 0x4c5abc000, 0))
        ...
}
...

The checkpointed image is based on the file tango1-fedora5.craff, on top of which is added the file disk0_image.craff that contains the difference between the checkpoint and the initial state.

Files like disk0_image.craff are often called diff files because they contain the difference between the new state and the previous state.

2.4.2.3.1 Image Search Path

This section contains more in-depth explanations about image handling that you may skip when reading this guide for the first time.

When successive incremental checkpoints are saved, an image object may become dependent on several diff files present in different directories. To keep track of all files, Simics stores in the checkpoint a checkpoint path list that contains the absolute directory paths where image files may be found. Image file names are then saved as %n%\filename where %n% represents the number of the entry in the checkpoint path, counting from zero.

Simics's checkpoint path is different from Simics's search path (see section 2.2.4), although both will be used when looking for image files, as show below.

Independent checkpoints do not affect the checkpoint path.

To summarize, when loading a checkpoint or a new configuration, Simics looks for images in the following way:

  1. If the filename contains a checkpoint path marker (%n%), the marker is translated using Simics's checkpoint path and the file is looked up in the corresponding path.

    • Windows example;
      If Simics's checkpoint path contains
      C:\checkpoints\c1;C:\checkpoints\c2, the file %1%/image.craff will be translated into C:\checkpoints\c2\image.craff.
    • Linux example;
      If Simics's checkpoint path contains /home/joe/c1:/home/joe/c2, the file %1%/image.craff will be translated into /home/joe/c2/image.craff.

The reason why Simics's search path is involved in the process is that it makes writing new configurations easier. Adding a path to the place where all initial images are located allows you to just specify the image names.

2.4.2.4 Saving and Restoring Persistent Data

As an alternative to checkpointing, Simics allows you to only save the persistent state of a machine, i.e., data that survive when the machine is powered-down. This typically consists of disk images and flash memory or NVRAM contents. A persistent data checkpoint is handled exactly like any other checkpoint and contains the same file set, but only objects containing persistent data are saved. This persistent data checkpoint can be loaded on top of a corresponding configuration later on.

The commands save-persistent-state and load-persistent-state respectively save and load the persistent data in a configuration.

These commands are often used to save the state and reboot a machine after the disk contents have been modified. Remember that the target OS might have cached disk contents in memory. In order to have a clean disk that can be used at boot, you should synchronize the disk, for example by running init 0 on a Linux target system, or shutting down the operating system, before you issue the save-persistent-state command.

Another option is to use the command enable-writable-persistent-state. It saves a persistent state and also switches all saved files to be writable, so that the images are used in read-write mode, as described in section 2.4.2.3. All changes to the images done during the simulation are then automatically written to these files, without the need to explicitly save the state.

This command can also load a state which already exists, created by this command. It is meant to be used in a Simics script and facilitates easy handling of the persistent states, either creating a new state or loading an existing state.

The states created by enable-writable-persistent-state can be loaded using load-persistent-state, if read-write mode is no longer desired. States created by save-persistent-state are generally not usable by enable-writable-persistent-state, since it may not be possible to make them writable.

2.4.2.5 Modifying Checkpoints

Checkpoints are usually created by saving a configuration inside Simics, but it is possible to edit or even create checkpoints yourself. It may even be required to edit file paths in a checkpoint file if it is relocated.

Because a minimal checkpoint only has to include required attributes, creating a checkpoint from scratch works relatively well for small configurations. We suggest you use an existing checkpoint as a starting point if you wish to do that. Note that more advanced layers have been built on top of the configuration system to make the creation of a new machine much easier. Refer to section 2.4.6 for more information.

Modifying checkpoints require some extra care. Adding or removing devices may confuse the operating system, which does not expect devices to appear or disappear while the system is running, and cause it to crash.

Changing the processor frequency may be enough to confuse the operating system. Many operating systems check the CPU frequency at boot time, and base their waiting loops and timing on the value they got. Saving a checkpoint and changing the frequency after boot may affect the simulation and confuse the system. Devices that use processor frequency to trigger events at specific times may also behave strangely when the frequency suddenly changes.

2.4.2.6 Merging Checkpoints

If you want to make an incremental checkpoint independent from all previous checkpoints, for example to distribute it, you can use the small checkpoint-merge program in [simics]\bin from your system command line. It merges the checkpoint with all its ancestors to create a checkpoint that has no dependencies. Specify the checkpoint you want to distribute as the first parameter and the name of the new stand-alone checkpoint as the second. This tool can be used in both Linux and Windows environments.

Note that the merged checkpoint still depends on the base image. This differs from independent checkpoints, which are completely standalone.

2.4.3 Inspecting the Configuration

Object attributes that are of type integer, string or object are directly accessible at the command line with the notation object->attribute:

# reading the EAX register in an x86 processor
simics> cpu0->eax
0
# writing a new value to EAX
simics> cpu0->eax = 10
simics> cpu0->eax
10
simics>

More information about the command line and scripting is available in chapter 2.3.

Finally, objects and attributes (of all types) are also available when scripting Simics directly in Python. Configuration objects are available under the conf namespace:

# reading the EAX register in an x86 processor
simics> @conf.cpu0.eax
0
# writing a new value to EAX
simics> @conf.cpu0.eax = 10
simics> @conf.cpu0.eax
10
simics>

More information about scripting Simics in Python is available in chapter 2.3.

2.4.4 Components

All machines in [simics]\targets\architecture use components to create configurations. A component is typically the smallest hardware unit that can be used when configuring a real machine, and examples include motherboards, PCI cards, hard disks, and backplanes. Components are usually implemented in Simics using several configuration objects and can also contain subcomponents.

Components are intended to reduce the large configuration space provided by Simics's objects and attributes, by only allowing combinations that match real hardware. This greatly simplifies the creation of different systems by catching many misconfigurations.

Components themselves are also configuration objects in Simics. But to avoid confusion, they will always be referred to as components and the objects implementing the actual functionality will be called objects.

2.4.4.1 Component Definitions

The component is the basic building block in the component system. When a component is created, it is in a non-instantiated state. At this stage only the component itself exists, not the configuration objects that will implement the actual functionality. Once a complete configuration has been created, all included components can be instantiated. When this happens, all objects are created and their attributes are set.

Components are connected to each other with connectors. Each connector has a connector type which tells what kind of connector it is and a direction, which can be up, down, or any. A connector is either required or optional. If it is optional it does not need to be connected to anything. Unless a connector is specified as hotpluggable it cannot be connected or disconnected after the component is instantiated. If a connection is hotpluggable it must be optional.

Connectors can be connected to each other in connections. Each connection connects an up connector with a down connector. A connection can also include an any connector. If an any connector is connected to an up connector it works exactly like a down connector and if it is connected to a down connector it works exactly like an up connector. The connections in the system must not form a cycle. You can think of the components and connections in the system as a directed acyclic graph with the components as the vertices and the connections as the edges.

Each connected subgraph in the set of components is called a component hierarchy.

A component A is said to be above a component B if it can be reached through up connectors in one or more steps from component B. Analogously, component A is said to be below a component B if B is above A.

A root is a component without any components above it. A component's roots are the roots which are above it.

A component where the top_level attribute returns true is a top-level component. It is often a motherboard, backplane or system chassis. It must be a root.

A standalone component is a component without any required connectors. A typical example is a hotplug device, such as a PC Card (PCMCIA) or an Ethernet link.

To instantiate a set of components, each component which is not standalone or top-level must have a top-level component as a root.

Components are also namespaces and can be nested in a namespace hierarchy, which is separate from the component hierarchy. The root of the hierarchy is the global namespace, and this is the only namespace which is not a component. Each configuration object (including components) lives in a namespace. The object is a child of the namespace and the namespace is the parent of the object. The other objects in the namespace are siblings of the object.

2.4.4.2 Importing Component Commands

Components in Simics are grouped by machine architecture, or by type, into several modules. Before a component can be used in Simics, the corresponding component module has to be loaded. When the component module is loaded, CLI commands for creating components are added to the front end. The most common modules, that are not architecture specific, are memory-comp, pci-comp, std-comp, console-components. To import all modules that are used by the QSP-Simple machine, issue the following commands:

simics> load-module std-comp
simics> load-module memory-comp
simics> load-module console-components
simics> load-module x58-ich10-comp
simics> load-module x86-nehalem-comp

2.4.4.3 Creating Components

The create-<component> command is used to create non-instantiated components. There is one create command for each component class. The arguments to the create command represent attributes in the component. Standalone components can be created both non-instantiated and instantiated. To create instantiated components, there are new- commands, similar to the create- commands.

The following code creates a non-instantiated 'motherboard_x58_ich10' component , called 'motherboard0'

simics> load-module x58-ich10-comp
simics> create-motherboard-x58-ich10
Created non-instantiated 'motherboard_x58_ich10' component 'motherboard0'

2.4.4.4 Connectors

A connector provides a means for a component to connect to other components. Connectors have a defined direction: up, down, or any. The direction is up if it needs an existing hierarchy to connect to; for example, the PCI-bus connector in a PCI device must connect to a PCI slot. A connector has a down direction if it extends the hierarchy downwards; for example, a PCI slot is a connection downward from a board to a PCI device. There are also non-directed connectors, with direction any. You can only connect an up to a down connector or to an any connector, and similar for down connectors. Connectors with the any direction can not be connected together.

Many connectors have to be connected before the component is instantiated, while others can be empty. A standalone component, as described above, may have all connectors empty.

A hotplug connector supports connect and disconnect after instantiation. Other connectors can only be connected, or left unconnected, when the configuration is created and may not be modified after that point. A multi connector supports connections to several other connectors. Creating multi connectors should be avoided, it is often better to dynamically create non-multi connectors when new connectors are needed.

It is not possible to connect instantiated components with non-instantiated ones. The reason is that the instantiated component expects the other to have all objects already created, and need to access some of them to finish the connection.

The info command of a component lists all connectors and some information about them:

simics> motherboard0.info
Information about motherboard0 [class motherboard_x58_ich10]
============================================================

Slots:
      dimm[0] : motherboard0.dimm[0]
      dimm[1] : motherboard0.dimm[1]
      dimm[2] : motherboard0.dimm[2]
      dimm[3] : motherboard0.dimm[3]
           nb : motherboard0.nb
    reset_bus : motherboard0.reset_bus
           sb : motherboard0.sb
    socket[0] : motherboard0.socket[0]
    socket[1] : motherboard0.socket[1]
    socket[2] : motherboard0.socket[2]
    socket[3] : motherboard0.socket[3]
    socket[4] : motherboard0.socket[4]
    socket[5] : motherboard0.socket[5]
    socket[6] : motherboard0.socket[6]
    socket[7] : motherboard0.socket[7]

Connectors:
      dimm[0] : mem-bus              down
      dimm[1] : mem-bus              down
      dimm[2] : mem-bus              down
      dimm[3] : mem-bus              down
    reset_bus : x86-reset-bus        down
    socket[0] : x86-apic-processor   down
    socket[1] : x86-apic-processor   down
    socket[2] : x86-apic-processor   down
    socket[3] : x86-apic-processor   down
    socket[4] : x86-apic-processor   down
    socket[5] : x86-apic-processor   down
    socket[6] : x86-apic-processor   down
    socket[7] : x86-apic-processor   down

The board has four slots for memory modules, one north bridge, one reset bus, one south bridge, eight sockets. All slots are not listed as hotplug since they have to be inserted when the machine is configured initially.

Since the machine need a cpu, we also add a x86QSP1 processor to our example. A CLI variable is used to hold the name of the processor component.

simics> load-module x86-nehalem-comp
simics> $cpu = (create-processor-x86QSP1 freq_mhz = 2000)
simics> connect motherboard0.socket[0] $cpu.socket

To enable input and output for the simulated machine, the following commands create a serial text console and connect it to the serial[0] connector of the south bridge.

simics> load-module console-components
simics> connect motherboard0.sb.serial[0] (create-txt-console-comp).serial

Since the machine needs some memory to run, we also add a memory module to our example. A CLI variable is used to hold the name of the memory component.

simics> load-module memory-comp
simics> $dimm = (create-simple-memory-module memory_megs = 2048)
simics> connect motherboard0.dimm[0] $dimm.mem_bus

2.4.4.5 Instantiation

When a component hierarchy has been created, it can be instantiated using the instantiate-components command. This command will look for all non-instantiated top-level components and instantiate all components below them. The instantiate-components command can also be given a specific component as argument. Then only that component will be instantiated, including its hierarchy if it is a top-level component.

simics> instantiate-components

If there are unconnected connectors left that may not be empty, the command will return with an error.

When the instantiation is ready, all object and attributes have been created and initialized. In our example, a text console window should have opened. The hardware of the simulated motherboard is now properly configured, but since no software is loaded, it will not show any output on the console if the machine is started.

2.4.4.6 Inspecting Component Configurations

The list-components command prints a list of all components in the system. All connectors are included, and information about existing connections between them.

The info namespace command provides static information about a component, such as the slots and a list of connectors.

The status namespace command provides dynamic information about a component, such as attribute values and a list of all current connections. The output from status in the example:

simics> motherboard0.status
Status of motherboard0 [class motherboard_x58_ich10]
====================================================

Setup:
         Top component : none
          Instantiated : True

Attributes:
                  acpi : True
                  bios : 
       break_on_reboot : False
           mac_address : 20:20:20:20:20:20
              rtc_time : 2008-06-05 23:52:01
             spi_flash : 
          system_clock : False
    system_clock_class : clock

Connections:
               dimm[0] : dimm0:mem_bus
             socket[0] : processor0:socket

2.4.4.7 Accessing Objects from Components

When doing more advanced configuration of a machine, it may be necessary to access configuration objects and their attributes directly. Each object in a component has a slot name that can be used for accessing the object. A list of slot names, and their mappings to actual configuration object names, is available from the output of the component's info command. The next example prints the frequency attribute from the core object.

simics> processor0.core[0][0]->frequency
[20000000, 1]

Accessing objects of non-instantiated components is not possible, since they do not have any associated configuration objects. But it is possible to access the pre_conf_objects of a non-instantiated component from Python. The following example works both for instantiated and non-instantiated components:

simics> @print(conf.processor0.core[0][0].cpuid_stepping)
8

Remember that not all configuration object attributes are available on a pre_conf_object. Only attributes that have been assigned by the component during initialization exists.

2.4.4.8 Available Components

The Target Guide for each architecture lists and describes all components that are available.

2.4.5 Object Name

An object can be identified using more than one name. This section describes the different ways of identifying an object.

All objects have a name that is used when printing log messages, writing checkpoints, in CLI commands, etc. The SIM_object_name function returns this object name. This name will be referred to as the object name in this section, even though an object can have several names for identification.

The object name is the name the object is given when created, or the objects location in the hierarchy.

An object can be given a name when created. The SIM_create_object, SIM_add_configuration, or SIM_set_configuration functions takes the object name as argument. The given name can be a string without dots "foo", a string with dots "cmp0.foo", an empty string "", or None. This section will describe how the given name affects the object name.

An object's hierarchical location is defined by its component and component_slot attributes. The hierarchical location for an object is the name of the component that the object's component attribute points at, and the component's component_slot attribute string, concatenated with a dot. For example, an object that belongs to a component named "cmp0" with the slot name "bar" has the hierarchical location "cmp0.bar".

All objects that reside in a slot in a component have valid component and component_slot attributes. It is the component's responsibility that the attributes are valid. The attributes are set when an object is added to a slot. A name that contains dots is a hierarchical location.

All objects also have an ID. The SIM_object_id function returns the object ID as a string. The object ID is unique, never changes, and will be saved in checkpoints. The object ID will not change even if the object is moved around in the hierarchy or is given a new name.

The object name and object ID are always unique. Creating an object and giving it a name that already exist will generate an error.

If the given name is a hierarchical location, an object will be added to that hierarchical location even if the component and component_slot attributes are not set. Simics will extract the component name and slot name from the given name. This information is then used when looking up the component and adding the object to the slot via the component interface. An object given the name "cmp0.cmp1.foo" belongs to the component "cmp0.cmp1" and has the slot name "foo". Note that the component "cmp1" in this example belongs to the component "cmp0".

An object given a name without dots, e.g., "foo", will get a name that is the hierarchical location of the object if the component and component_slot attributes are valid. Otherwise it will get the name "foo", which means that the object does not reside in any slot in a component. The object will also get an automatically assigned unique object ID, unless legacy_object_id is set. In that case the object name is used as object ID.

An object given a name with dots "cmp0.foo" gets the name "cmp0.foo" or, if it is put in the slot "bar", the name "cmp0.bar", its hierarchical location, and an object ID of the form "obj_XYZ".

An object given an empty name "", or None, will get a hierarchical location as name, if the component and component_slot attributes are valid, otherwise a name of the form "obj_XYZ". The object ID will always be of the form "obj_XYZ", identical to the object name if component and component_slot attributes are invalid.

Given NameName (Slot = None)Name (Slot = bar)IDName After Move
foofoocmp0.barobj_XYZcmp1.smurf
cmp0.foocmp0.foocmp0.barobj_XYZcmp1.smurf
Noneobj_XYZcmp0.barobj_XYZcmp1.smurf

The given name is the name that the user has provided. Name is the name the object gets when created, depending on if component and component_slot are set: Slot = None when they are not set; Slot = bar when they are set and component_slot is bar. ID is the unique object ID. Name After Move is the name the object gets after being moved to cmp1 to a slot named smurf.

2.4.6 Ready-to-run Configurations

Simics includes many customizable ready-to-run configurations. Because checkpoint files are by definition static, these example configurations are not checkpoint-based, but rather built on components and scripts to generate a working simulated machine.

The example configurations are located in separate directories for each system architecture: [simics]\targets\architecture. Each of these directories contains a number of Simics scripts (i.e., files containing Simics commands):

These are the files you want to use to start the standard example machines in this directory.

\<machine\> in the script name is either a Linux machine name, or a some other name that defines the hardware/software combination.  

The example configurations are designed to work with the disk images distributed with Simics. The machines are described in the Target Guide corresponding to each architecture.

Several machines may be defined for a given architecture, and thus the corresponding architecture directory will contain several machine-common.simics scripts.

2.4.6.1 Customizing the Configurations

There are several ways to customize the examples provided with Simics. They are listed below ordered by how simple they are to use.

  1. Scripts
    A simulated machine is defined by several scripts, as described above. By replacing the -common.simics file with a user defined script, the system can be configured more in detail while keeping the machine definition provided by the -system.include file. Similarly the -setup.include can be replaced to configure different software on the same machine.
  2. Components
    Components represents real hardware items such as PCI cards, motherboards, and disks. Using components to configure a machine provides freedom to set up the simulated machine in any way that is supported by the architecture. The -system.include files use components to create their machine definitions. A complete description of components is provided earlier in this chapter.
  3. Objects and Attributes
    A component is implemented by one or more configuration objects in Simics, and each object has several attributes describing it. Configuring machines on the object and attribute level is not supported in Simics, and such configurations may not work in future versions.

Below is another example of a simple configuration based on QSP-x86, that uses parameters to configure two machines slightly differently that both run in the same Simics session.

simics> $freq_mhz = 2000
simics> $host_name = "target0"
simics> run-script "%script%/firststeps.simics"

simics> $freq_mhz = 3000
simics> $host_name = "target1"
simics> run-script "%script%/firststeps.simics"

2.4.6.2 Adding Devices to Existing Configurations

The parameters available for each predefined machine allows the user to do minor modifications. It is also possible to extend the ready-to-run configurations with additional components without creating new machine setups from scratch.

Since the machine setup scripts are located in the read-only master installation of Simics, they should not be modified. User files that add new components should instead be placed in the corresponding [project]\targets\architecture directory.

Here is a short example of how to extend the QSP-x86 to add a SATA disk:

# Add disk2
simics> $disk2 = (create-sata-disk-comp $system.disk2 size = $disk2_size)
simics> connect $system.mb.sb.sata_slot[2] $disk2.sata_slot

simics> instantiate-components

Notice, that script requires you to provide a disk image and a valid disk size. Essentially the script will run another script, firststeps.simics, which will create an instantiated QSP-x86 machine, then a SCSI disk is created and connected to that machine, and finally the new disk is instantiated.

It is possible to add many devices to an instantiated Simics machine in a similar manner. In the case a device must be added to the target machine before instantiation, write a script as described above.

2.5 Moving Data in and out of the Simulation

In order to use Simics, you must have images (also called disk dumps) with the operating system and the applications you plan to run. Depending on the type of machine you are using, these images will correspond to the contents of a disk, a flash memory, a CD-ROM, etc. There are some images provided that work with the example machines located in the targets directory.

Simics images are usually stored in a special format called craff (for Compressed Random Access File Format) to save disk space. Simics also accepts a raw binary dumps as well as VHDX images. The use of a raw dumps as images can sometimes be more practical if you are manipulating images outside Simics. Simics comes with the craff utility to manipulate and convert images in craff format (see section 2.5.2.8).

This chapter will explain the following:

To provide you with a more practical overview, here are the ways you can install and modify the operating system and the applications you wish to run:

2.5.1 File Transfers Using the Simics Agent

Simics Agent is a feature in Simics which can be used to move files and directories in and out of a simulation in a deterministic way, without any network connection or kernel modules. It also provides a deterministic way of executing commands on the target systems and knowing when they finish.

This feature consists of two parts. One part is present inside a Simics simulation session (agent_manager and agent_handler objects) and the other part is the simics-agent application running on the target system.

The simics-agent application must be located on the target system and the user is in charge of starting it. The application as well as the target system must be running for its commands to execute. There is typically just a single Simics Agent in a target system, but there may be more target systems and agents.

The user has to create the agent_manager object with the start-agent-manager Simics command. The object will find running Simics Agents and keep track of them. There can be only one agent_manager in Simics session.

In order to control and issue commands to a Simics Agent, the user must run the <agent_manager>.connect-to-agent command, which returns a unique agent_handle for the Simics Agent. Several handles may be connected to one Simics Agent, which are executing commands concurrently.

All the commands that the agent_handle provides are asynchronous and queued in the handle until they have completed. Handles can be created and used in scripts, script-branches, or interactively on the Simics command-line. The two commands, <agent_handle>.run-until-job and <agent_handle>.wait-for-job, allow the user to wait until a particular or all queued commands have finished.

Exactly when a Simics Agent runs and executes a particular command is defined by the target system, neither Simics nor the user can control it.

A ready-to-use Simics Agent is installed on some virtual platform images. Agents for some architectures are also available on CD (ISO) in the [simics-base]/targets/common/images/ directory. For other use cases, the agent source code comes with Simics Base and can be copied onto and compiled for the target system, usually without, or with only minor, adjustments. The source code is located at [simics-base]/src/misc/simics-agent/.

A Simics Agent runs only when needed, otherwise it sleeps and regularly polls for requests. Agent responsiveness versus performance taxing is a trade-off that is controlled by the polling interval, but affects all users of that Simics Agent.

A checkpoint will contain Simics Agents and the Agent Manager, but not any Agent Handles. Thus, any ongoing transactions or pending commands are lost and must be rerun.

The following sections shows an example where a binary is uploaded to the target system, started, and its output file downloaded to the host. See the Simics Reference Manual or command-line interface help for details on agent_manager and agent_handle classes and the methods they provide.

The examples assume that a target system is booted and a Simics Agent is running on it. All but the first example expects the simulation to be running.

Creating a Simics Agent Manager is done once per simulation session, both for interactive use and for scripts.

simics> start-agent-manager
agent-manager 'agent_manager' is created and enabled.

2.5.1.1 Interactive Example

The agent_manager was started above, but the user needs an agent_handle to issue commands to. The connect command below will return immediately, whether the simulation is running or not, and whether there is any known Simics Agents matching the requested identity or not. Since no name was given, the returned handle will have the default name.

It is recommended, but not required, to issue commands while the simulation is stopped, like in this example, for the sake of determinism and repeatability.

simics> agent_manager.connect-to-agent
matic0 connected to simics_agent0
simics> matic0.upload -executable my_test /bin/
matic0:job 1
simics> matic0.run "/bin/my_test -out /var/my_test.log"
matic0:job 2

Now two commands have been given, but since the simulation is stopped they will not execute yet. The first command will upload the application binary and make it executable (if it isn't already), while the second command will run it. The queued commands can be listed along with their sequence id's.

simics> matic0.list-jobs
	#1	upload my_test /bin/my_test, queued
	#2	run "/bin/my_test -out /var/my_test.log", queued

Note that the <agent_handle>.list-jobs command does not require the simulation to be running (nor even a Simics Agent to exist). Then, run the queued commands and wait for them to complete.

simics> matic0.run-until-job
File my_test copied from host to target /bin/my_test
... [the output of the my_test command, if any] ...
matic0:job 2 finished

The test application produced an output log file that should be downloaded from the simulation to the host. When downloading files the user may want to specify the -overwrite flag. Otherwise the file will not be downloaded on following runs, as the file will persist on the host.

simics> matic0.download -overwrite /var/my_test.log
matic0:job 3
simics> matic0.run-until-job
File /var/my_test.log copied from target to host ./my_test.log
matic0:job 3 finished

2.5.1.2 Example with a Script Branch

This example will perform the same tasks as the previous example, but in a script-branch. The handle will automatically run the queued commands one after another in strict order, and they will complete in due time. Technically, the script-branch will queue all commands up until a <agent_handle>.wait-for-job command and only then wait for them to finish.

simics> script-branch "run my_test" {
    local $matic = (agent_manager.connect-to-agent)
    $matic.upload -executable from = my_test to = /bin/
    $job = ($matic.run -capture "/bin/my_test -out /var/my_test.log")
    $matic.download -overwrite /var/my_test.log
    $matic.wait-for-job  # wait until ALL jobs are done 
    echo ($matic.get-captured-output $job)
    $matic.delete-handle
}

This example prints the output of the my_test application, but requires a wait-for-job command, because without it the application will not have run before the script-branch reaches the echo statement and then deletes the handle.

It is good practice to delete any lingering objects in the end of a script branch. The output of the my_test command is captured and printed, but can alternatively be written to a file instead.

File my_test copied from host to target /bin/my_test
File /var/my_test.log copied from target to host ./my_test.log
... [the output of the my_test command, if any] ...

2.5.1.3 Example from the target shell

Similar to the previous examples, a test log file will be copied from the target system to the host system. Here, the execution is controlled completely from the command-line in the target system shell.

The direction of the upload and download commands are reversed, when giving the commands from the target system, compared to the host system.

~ # simics-agent --executable --download my_test --to /bin/
File my_test copied from host to target /bin/my_test
The Simics target agent has quit.
~ # my_test -out /var/my_test.log
...
~ # simics-agent --overwrite --upload /var/my_test.log
File /var/my_test.log copied from target to host ./my_test.log
The Simics target agent has quit.

Give the --help argument to the simics-agent for more information on the available commands and options.

For these target initiated commands to work, the Agent Manager must be started on the host, but no Simics Agents are required to be running in the target system. Different Simics Agents and using them will not conflict or interfere with each other.

2.5.2 Working with Images

2.5.2.1 Image File Formats

For Simics compatibility, a disk image needs to be either in the Simics-specific craff file format, or be a raw (linear) image.

The raw format is what you get when dumping a disk with a utility such as dd. Keeping the image in raw format is useful if you want to be able to loopback mount the image, or if you want to update the image in-place using the read/write option in Simics. For most other use-cases, compressing the image with the craff utility will be beneficial as the host disk requirements will often be dramatically smaller than for raw images. Image file access is usually not frequent enough during simulation to impact performance. The craff format is recommended as the best all-round image file format for Simics. See section 2.5.2.8 for more information about the craff utility.

2.5.2.2 Saving Changes to an Image

If you modify or create new files on a storage device within Simics, you should remember that by default images are read-only. This means that the alterations made when running Simics are not written to the image, and will last only for the current Simics session. As described in the 2.4.2.3 section, this behavior has many advantages. You may however want to save your changes to the image, in order to re-use them in future simulations.

The first thing you should do is to make sure that all the changes are actually written to the media you are using. In many cases, the simulated operating system caches operations to disks or floppies. A safe way to ensure that all changes are written back is to shutdown the simulated machine.

When all changes have been written to the media in the simulation, you can save the new contents of the image in different ways:

Once you have saved the images, you can do the following:

If you save several persistent states or image diff files that are dependent on each other, it may become cumbersome to take care of all these dependencies and to remember which files are important. You can merge the states or image diff files to create a new independent state:

2.5.2.3 Reducing Memory Usage Due to Images

Although images are divided into pages that are only loaded on request, Simics can run out of host memory if very big images are used, or if the footprint of the software running on the simulated system is bigger than the host memory. To prevent these kind of problems, Simics implements a global image memory limitation controlled by the set-image-memory-limit command.

When Simics is started a default memory-limit is automatically set based on the amount of physical memory available on the host. The default memory-limit does not consider if other applications and users are running on the same host, nor what kind of target system that is simulated in Simics. (For example each target processor will allocate additional non-image memory, so for systems with many processors the default limit could be too high).

When the memory limit is reached, Simics will start swapping out pages to disk very much like an operating system would do. The set-image-memory-limit command lets you specify the maximum amount of memory that can be used, and where swapping should occur.

This memory limitation only applies to images. Although this is unlikely, Simics can run out of memory due to other data structures becoming too large (for example memory profiling information) even though a memory limit has been set.

2.5.2.4 Using Read/Write Images

As mentioned in section 2.4.2.3, images can also work as read-write media, although this is not recommended. It can be useful sometimes when planning to make large changes to an image (like installing an operating system on a disk).

To make an image read-write in your own configurations, simply set the second parameter (the "read-only" flag) of the files attribute in the image object to "rw".

In this example we will use QSP-x86 Firststeps and a raw file named disk_image.raw located in the project directory.

# launch Firststeps using a raw disk-image file
$ ./simics qsp-x86/firststeps machine:software:linux:os_image=disk_image.raw
# read the 'files' attribute
simics> @files = conf.board.disk0.hd_image.files
simics> @files
[['%0%/disk_image.raw', 'ro', 0, 214748364800, 0]]
# provide the absolute path to the file
simics> $img_file = (lookup-file disk_image.raw)
simics> @files[-1][0] = simenv.img_file
# change the second element to make the file read-write
simics> @files[-1][1] = "rw"
# check the result
simics> @conf.board.disk0.hd_image.files
[['[project]</i>/disk_image.raw', 'rw', 0, 214748364800, 0]]

Note that by indexing files with the index -1, the last element of the array is accessed, which is always the one that should be set read-write, in case files is a list of several files.

Simics does not look for files in the Simics search path when the files are used in read-write mode. If you do not provide an absolute path to a read-write file, a new file (in the uncompressed craff format) will be created in the current directory.

If the read/write file already exists, it must be a raw image or an uncompressed craff file. Simics does not support using compressed craff files in read/write mode.

Use this feature with caution. Make sure to take a copy of the original image before running Simics with the image in read-write mode. Remember to synchronize the storage device within the target OS before exiting Simics, for example by shutting down the simulated machine.

2.5.2.5 Editing FAT Images Using Mtools

This is a Linux specific chapter. If you have an image that contains a FAT file system, you can use Mtools (http://mtools.linux.lu) to get read-write access to the image. You must have a raw binary dump of the image for Mtools to work. This can be obtained using the craff utility (see section 2.5.2.8).

A few wrapper scripts around Mtools are included in the Simics distribution in the scripts directory.

If your image is partitioned (a complete disk for example), you may need to give Mtools special parameters like an offset or a partition. Please see the Mtools documentation for more information.

2.5.2.6 Editing Images Using Loopback Mounting

This is a Linux specific chapter. If the host OS supports loopback devices, like, e.g., Linux, you can mount an image on your host machine and get direct read/write access to the files within the image. If you have root permissions this allows you to easily and quickly copy files.

Remember that the image must be a raw binary dump. Disk dumps supplied with Simics are normally in craff format but you can use the craff utility to unpack the disk image to a raw image. The resulting images have the same size as the simulated disk, so you need to have sufficient free space on your host disk to contain the entire simulated disk image.

Do not try to loopback mount an image over NFS. This does not work reliably on all operating systems (Linux, for example). Instead, move the image to a local disk and mount it from there.

On Linux:

mount <disk_dump> mnt_pnt -o loop=/dev/loopn,offset=m

Example:

# mount /disk1/rh6.2-kde-ws /mnt/loop -o loop=/dev/loop0,offset=17063424
# cd /mnt/loop
# ls
bin   dev  home  lost+found  opt   root  tmp  var
boot  etc  lib   mnt         proc  sbin  usr
#

As shown in the example, the disk dump containing a Red Hat 6.2 KDE WS is mounted on the /mnt/loop directory. The file system mounted on / starts on the offset 17063424 on the disk. Linux autodetects the file system type when mounting (ext2 in this example). If you want to access another kind of file system, use the -t fs option to the mount command. Once the file system is mounted, you can copy files in and out of the disk image.

The offset can be calculated by examining the partition table with fdisk (from within Simics). Use mount to find the partition you want to edit or examine (e.g., /dev/hda2 is mounted on /usr which you want to modify). Next, run fdisk on the device handling this partition (such as fdisk /dev/hda). From within fdisk, change the display unit to sectors instead of cylinders with the u command and print the partition table with p. You will now see the start and end sectors of the partitions; you can get the offset by taking the start sector multiplied with the sector size (512).

When you have finished examining or modifying the disk, unmount it and touch the disk image. For example:

cd
umount /mnt/loop
touch /disk1/rh6.2-kde-ws

The modification date of the disk image does not change when you modify the disk via the loopback device. Thus, if you have run Simics on the disk image earlier, the OS might have cached disk pages from the now modified disk image in RAM. This would cause a new Simics session to still use the old disk pages instead of the newly modified pages. Touching the image file should ensure that the OS rereads each page.

2.5.2.7 Constructing a Disk from Multiple Files

In some cases, you may want to populate a simulated disk from multiple files covering different parts of the disk. For example, the partition table and boot sectors could be stored in a different disk image file than the main contents of the disk. If that is the case, you cannot use the <image>.add-diff-file command: you must set manually the disk image files attribute to put each image file at its appropriate location.

Assume you are simulating a PC and want to build a disk from a main file called hda1_partition.img and a master boot record image file called MBR.img. The main partition will start at offset 32256 of the disk, and the MBR (Master Boot Record) covers the first 512 bytes of the disk (typically, you would get the contents of these image files from the real disk as detailed in section 2.5.9). The following command in Simics's start-up script will build the disk from these two files.

simics> load-module std-comp
simics> create-ide-disk-comp disk2 size = 2559836160
simics> @image = get_component_object(conf.disk2, 'hd_image')
simics> @image.files = [["hda1_partition.img", "ro", 32256, 1032151040, 0],
                  ["MBR.img",            "ro",     0,        512, 0]]

Note that the two image files cover non-overlapping and non-contiguous sections of the disk.

2.5.2.8 The Craff Utility

The images distributed with Simics, and in general most of the images created by Simics, are in the craff file format. The craff utility can convert files to and from the craff format, and also merge several craff files into a single file.

In your Simics distribution you will find craff in Windows:[simics]\bin, Linux:[simics]/bin. The examples below assume that craff is present in your shell path.

See also the craff in the Simics Reference Manual for a full description of the craff utility and its parameters.

2.5.2.9 craff-fs

Most large data files in Simics, such as disk images, are stored in the compressed random access file format (CRAFF). In order to manipulate the raw data in these files it is necessary to decompress the file using the craff tool. For disk images these files can be very large, so decompressing them is not always feasible. Therefore, we provide an alternative in the experimental craff-fs tool. With craff-fs you can "mount" the craff file and access it through the file system as if it is a normal raw file. The craff-fs tool is only available on Linux and is provided AS IS.

craff-fs requires libfuse version 2.x.x (tested with 2.9.9) and libvtutils.

The synopsis of craff-fs is as follows:

# ./bin/craff-fs 
craff-fs file mountpt [fuse-opts]

2.5.2.9.1 Example

This examples shows how to "mount" a craff image of a disk image in order to expose it as raw data without decompressing it and then mount the resulting file via loop back in order to access the disk's file system. Mounting craff files as raw files does not require root privileges, but mounting loop back mounting the result usually does. In this example, we use the UEFI agent craff image from Simics package 2096.

The first step is to mount the craff file with craff-fs to be able to access the raw data. Note that the mount point directory must always be created first. As explained further below, there are permission issues that one has to get right, and one way is to use administrator privileges everywhere, so in this example we will do that.

$ mkdir -p craff-mnt
$ sudo ./bin/craff-fs targets/common/images/efi_agent_and_grub_only.craff craff-mnt

The mount point will now be populated with two files: data and info.

Other users (including root or users with administrator privileges) cannot access the information mounted under craff-mnt mount point:

$ ls craff-mnt
ls: cannot access 'craff-mnt': Permission denied

Details about the user, group and file permission cannot be accessed by other users (including root).

$ ls -l
d????????? ? ?           ?               ?            ? craff-mnt/

If other users should access the mount point, "allow_others" should be uncommented from /etc/fuse.conf file. This will enable all users (including root) to list/view the contents.

The data file contains the raw data that makes up the craff file, the info file contains additional information about the craff file in text format:

$ sudo cat craff-mnt/info 
Craff mount: 0.1.0

Craff version: 1
Compression: gzip
Size: 107374182400
Block bits: 13
Sub bits: 4
Directory bits: 9
2.5.2.9.1.1 Use of parted to display the file type and partitions

Since a disk image will have different partitions at different offsets it is not possible to mount the "disk image". It is necessary to mount the individual partitions. There is a tool called parted that can display this information.

$ sudo parted craff-mnt/data
GNU Parted 3.5
Using /disk1/simics-6/craff-mnt/data
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) unit
Unit?  [compact]? b
(parted) print
Model:  (file)
Disk /disk1/jhbaarnh/simics/merge-6/simics-base/craff-mnt/data: 107374182400B
Sector size (logical/physical): 512B/512B
Partition Table: loop
Disk Flags: 

Number  Start  End            Size           File system  Flags
 1      0B     107374182399B  107374182400B  fat32
(parted) q

CraffFS allows mounting as read-write and read-only. File systems successfully tested: ext2, ext3, ext4, NTFS, FAT32.

It can be seen from the example above that the first partition has a fat32 file system and it starts at offset 0. This information will be required to mount that partition via the loop back device.

2.5.2.9.1.2 Mounting the partitions

Mount as read-only:

$ mkdir -p mnt-loop
$ sudo mount -o ro,loop,offset=0 craff-mnt/data mnt-loop
$ ls mnt-loop
EFI  SimicsAgent.efi
$ 

Mount as read-write:

$ mkdir -p mnt-loop
$ sudo mount -o rw,loop,offset=1048576 craff-mnt/data mnt-loop
$ ls mnt-loop
EFI  SimicsAgent.efi
$ 

Mount command used with options (-o) requires administrator privileges. If the craff-fs command is run without administrator privileges, even if mount command is run as root, mounting will fail. There are two options:

  1. Run both craff-fs and mount commands as root.
  2. Run craff-fs as normal user but edit /etc/fuse.conf and uncomment "allow_others". Root (and other users) can mount and access mounted files.
2.5.2.9.1.3 Unmounting the partitions
$ sudo fusermount -u mnt-loop
$ sudo fusermount -u craff-mnt

2.5.2.9.2 Use CRAFF tool to reduce the size of CRAFF files if needed

Craff files will increase in size as files and information is written, but the file will not automatically decrease if same files or others are removed/deleted. To shrink the craff file size after delete, create an zero file (full of zeros) until the end of the partition and use the craff tool. This tool will ignore all zero blocks and will shrink the craff file.

2.5.3 CD-ROMs and Floppies

2.5.3.1 Accessing a Host CD-ROM Drive

This is a Linux specific chapter. Accessing the CD-ROM of the host machine from inside the simulation is supported on Linux hosts. This is done by creating a cdrom-image object using the new-cdrom-image command. First, you should insert the CD in the host machine and figure out which device name it uses.

On a Linux host, this is typically /dev/cdrom, which is a symbolic link to the actual CD-ROM device, e.g., /dev/hdc. Note that you need read/write access to the CD-ROM device for this to work.

When you have the correct device file name, you create a cdrom-image object and insert it into the simulated CD-ROM drive:

simics> new-cdrom-image /dev/cdrom file_cd0
CDROM 'file_cd0' created
simics> cd0.insert file_cd0
Inserting media 'file_cd0' into CDROM drive

Note that you must replace /dev/cdrom with the correct host device name as mentioned above, and cd0 with the correct Simics object name. Use the list-objects command to find the correct object of class ide-cdrom.

The cd0.insert command simulates inserting a new disk into the CD-ROM drive, and there is also a corresponding cd0.eject command that simulates ejecting the disk.

2.5.3.2 Accessing a CD-ROM Image File

A file containing an ISO-9660 image can be used as medium in the simulated CD-ROM. This image file can be created from real CD-ROM disks, or from collections of files on any disk.

On Linux, an image can be created from a set of files with the mkisofs program. For example:

mkisofs -l -L -o image -r dir

On Windows, you can use a third-party product to create ISO-9660 images from files or from CD-ROMs, and a non-exhaustive list is given in figure 1. Note that many programs can read CD-ROMs in either "file" or "raw" mode ("raw" mode is often called "aspi"). If CD-ROMs are read using file mode, the resulting image will not be bootable.

ProductSiteDescription
WinImagehttp://www.winimage.comShareware, only copies images from real CD-ROM
WinISOhttp://www.winiso.comShareware
UltraISOhttp://www.ezbsystems.com/Shareware
MagicISOhttp://www.magiciso.comShareware, can make images from files, CD-ROMs, and DVD-ROMs, and edit ISO images.
mkisofshttp://wiki.osdev.org/MkisofsPart of the cdrtools package, free, need Cygwin
Figure 1. Windows Programs to Create ISO-9660 Images

Once you have an image file, a cdrom-image object can be created, and then inserted into a simulated CD-ROM device in the same way as above:

simics> new-cdrom-image myimage.iso
CDROM 'myimage' created
simics> cd0.insert myimage
Inserting media 'myimage' into CDROM drive

Note that cd0 above refers to the Simics object name of the CD-ROM drive. This may or may not be called cd0. To see which object name to use, try the list-objects command and look for an object of class ide-cdrom.

2.5.3.3 Accessing a Host Floppy Drive

This is a Linux specific chapter. It is possible to access a floppy on the host machine from within Simics if the host is running Linux. For example (assuming the floppy device object is called flp0):

simics> flp0.insert-floppy A /dev/fd0

To boot directly from the floppy on a simulated x86 architecture you need to select the "A" drive to be the boot device:

simics> <motherboard_x58_ich10>.cmos-boot-dev A

2.5.3.4 Accessing a Floppy Image File

Sometimes it can be convenient to have copies of boot floppies as image files. On Windows, to create an image of a floppy you can use, for example, WinImage (see section 2.5.3.2 above).

On Linux, you can use the dd command:

dd if=/dev/fd0 of=floppy.img

It is then possible to use this image file in Simics:

simics> flp0.insert-floppy A floppy.img

To boot directly from the floppy on a simulated x86 architecture you need to select the "A" drive to be the boot device:

simics> <motherboard_x58_ich10>.cmos-boot-dev A

Floppies are also a convenient way to move small amounts of data out of the simulated machine. Write the data to the simulated floppy inside the simulated machine, and then extract it from the image.

If it is formatted as a FAT file system, a floppy image can be manipulated with, for example, Mtools (Linux only, see section 2.5.2.5 for more information).

2.5.4 USB disks

Virtual USB disks can be used to transfer files to and from virtual machines.

A virtual USB disk is created with the new-usb-disk-comp command. The command accepts a file with disk image and creates a USB disk component:

simics> load-module usb-comp
simics> $usb_disk=(new-usb-disk-comp file = /tmp/stick.img name = usb_disk)

Next, you can insert the virtual USB disk into the machine. Exactly how that is done depends on the system that is being simulated. A virtual USB disk should be connected to an empty USB-port connector:

simics> $usb_disk.connect-to $system
Connecting usb_disk.usb_host to board.mb.sb.usb_port[10]

Writes to a virtual USB disk are normally not written directly to an image file, but cached in memory. The <image>.save command can be used to save changes to a new image file. One can find the objects that support the command with the help of the list-objects command:

simics> list-objects -all iface = image
+-------+----------------------------+
| Class |           Object           |
+-------+----------------------------+
...
|<image>|usb_disk.usb_scsi_disk_image|
...
simics> usb_disk.usb_scsi_disk_image.save new_image

2.5.5 Using SimicsFS with FUSE

SimicsFS with FUSE is a new implementation based on FUSE, Filesystem in Userspace, which is supported by most Linux systems[1]. This is a client-server solution, which is more feature complete and with better performance than the old SimicsFS kernel module and device solution.

The Simics extension, SimicsFS with FUSE, will grant a simulated Linux system access to the file system of the host computer from inside the simulation. This allows more storage space for the target system and may make copying files to and from the target system unnecessary as they can be accessed directly from the host file system.

The SimicsFS client, used for SimicsFS with FUSE, is a regular Linux user-space application that allows non-privileged users to mount and unmount file systems. The FUSE kernel module needs to be included in the Linux kernel and the libfuse userspace library[2] in the Linux distribution.

All accesses to the host filesystem will be made with the privileges of the user starting the Simics session.

[1] FUSE is compatible with all Linux kernels since version 2.4.
[2] SimicsFS client is compatible with FUSE 2.6 or later.

Some characteristics/limitations for SimicsFS with FUSE:

Limitations for Windows hosts:

OperationRestriction
ReadRead accesses cannot be denied, any attempts are silently ignored.
ReadUnreadable files may still appear readable, but reading will fail.
Soft-linkWindows filesystems do not support it.
Add/RemoveWrite permission on the directory is ignored.

2.5.5.1 Installing SimicsFS client

The SimicsFS client is installed on some disk images distributed with Simics. Where there is also a /host directory and definitions in /etc/fstab for mounting SimicsFS with the command mount /host by root. Then the host files become accessible by root alone.

If you run your own Linux distribution you have to compile SimicsFS client yourself. The source code is located in the src/misc/simicsfs-client/ directory and in the src/misc/libmagicpipe/ directory of the Base package (pkg 1000). The Linux kernel must be compiled with FUSE support and the file system must include libfuse.

Example definition in the /etc/fstab for mounting SimicsFS with the command mount /host by root:

simicsfs-client /host fuse defaults,noauto 0 0

Replace /host with your mount point. The simicsfs-client executable must be located in a directory included in the PATH environment variable, either by placing it there or adding its directory.

With the user option it is also possible to define entries in /etc/fstab for normal users to mount and unmount SimicsFS. Example showing mount of host user home directory on directory <dir>:

simicsfs-client <dir> fuse defaults,noauto,user,modules=subdir,subdir=/home/<user> 0 0

The <dir> directory must be empty and have write permission for the user. The user mounts with the command mount <dir> and unmounts with the command fusermount -u <dir>.

See the fstab and fuse documentation ("man fstab", "man fuse") for more information about the available mount options.

2.5.5.2 Using SimicsFS client

The SimicsFS client requires the SimicsFS server to be started:

simics> start-simicsfs-server

Example commands showing how to mount SimicsFS with the command simicsfs-client:

Example command showing how to unmount SimicsFS:

Example commands showing how to mount SimicsFS on /host directory by root with definition in /etc/fstab, see section 2.5.5.1:

Example command showing how to mount SimicsFS on <dir> directory by a normal user:

Read-only files on the target system may not be copied to the mounted file system. A workaround is to first touch the file in the mounted file system, which creates the file with write permission, and then copy the file from the target system to the mounted file system. Finally change permission of the destination file to read-only.

When running Simics on Windows host, replace all '\' in the mount path with '/' to prevent interpretation of escape character by the simulated target software. Example:

~:$ simicsfs-client dir -omodules=subdir,subdir=D:/home/<user>

2.5.5.3 SimicsFS Path Substitution

The host directory mounted from a target system is the root directory by default, but that is configurable from the target system. Although, it may not be feasible or desirable to modify the target system configuration when the host system path changes. The path may also be part of a checkpoint and may not be applicable when the checkpoint is restored again. For these cases the host path can be substituted with a replacement path on the host.

To perform a substitution a path rule must be created. These are applied to every operation, with immediate effect. A rule can be general to all clients or specific to just a group of clients. The rule will look for a substring in the requested path and replace that substring with the new destination path. The substring matching is performed from the beginning of the path, but need not match the whole requested path in the mount command on the target.

A client may thus request an existing or non-existing path on the host. This path will be matched against the defined path rules to see if there is a match. Specific rules are checked first, then the general rules. Therefore a specific rule will always override a general one. Otherwise the rules are matched in the order they were added and only the first matching rule will be applied.

EXAMPLES:

One of the main use-cases for this feature is to allow access to a user defined path which is configured at run-time without the need to modify the target configuration. For instance the users may want to access their own home directories, without modifying the generic target image. For this purpose they define a mount-point /mnt/home which is configured to mount /home/user on the host. The host path /home/user may not exist on the host and requires the user to configure a path rule to substitute it for the real home directory of the current user.

simics> $userhome = (env HOME)
simics> <simicsfs-server>.add-path-rule /home/user $userhome
~:$ mount -t simicsfs-client /mnt/home -osubdir,subdir=/home/user

Another major use-case for this feature is to solve the checkpoint problem, where a user has mounted some path on the local machine. This checkpoint is then shared with a colleague, who has similar files stored in another path. The colleague will then need to replace the requested host path with the actual path to the equivalent folder local to this other machine.

simics> <simicsfs-server>.add-path-rule /work/simics/proj /my/path/to/proj

Sometimes you may need to differentiate between different clients. When a client registers itself with the simicsfs server, it provides a bit of details on the target that it runs on. Each of these details can be used to select clients for different groups. These groups can then be given as an extra argument to the path rule, which will then only apply to the clients of that group. Once a client is registered, it is assigned a unique magic number.

simics> <simicsfs-server>.add-client-group a_team magic 0x0103a1245edb31da
simics> <simicsfs-server>.add-path-rule "/" /proj/area_a a_team

The first line will create a group called a_team which will consist of the client with magic number 0x0103a1245edb31da. Since the magic numbers are unique there can only be one client in this group. The second line will add a path rule that replaces the root directory with /proj/area_a, but only for clients that belong to group a_team.

The simicsfs-client is not aware of any changes to the path rules, which means that recently accessed files may appear to still be present even though the path rule has changed to point to another destination. Operations on that file, however, will fail.

2.5.6 Using VirtioFS

The Virtio File System Device is specified in the Virtio specification. The device is used to share files between the host and the guest. There are drivers available for the VirtioFS device for Linux (CONFIG_VIRTIO_FS) , UEFI and Windows.

2.5.6.1 Requirements

2.5.6.2 Example Using VirtioFS with a Linux Target

This section describes how to use VirtioFS with a Linux target. Setting up target software on different target types (UEFI or Windows) is out of scope of this documentation. However, setup of the target platform would be similar.

2.5.6.2.1 Linux Kernel

A Linux target would need to have CONFIG_VIRTIO_FS, CONFIG_VIRTIO_PCI, CONFIG_PCI, CONFIG_PCI_MSI and CONFIG_FUSE_FS enabled in the kernel configuration either as modules or built-in. For modern Linux distributions, these options are usually enabled by default.

2.5.6.2.2 Platform Setup

The following example shows how to use VirtioFS on board that has a free PCIe slot that supports devices implemented using the new PCIe library.

simics> load-module virtio-comp
virtio-comp module loaded
simics> new-virtio-pcie-fs-comp fs0 share = <a-directory-in-the-host> tag_name = fs0
Created instantiated 'virtio_pcie_fs_comp' component 'fs0'
simics> fs0.connect-to board
Connecting fs0.upstream_target to board.mb.nb.pcie_slot[0]

2.5.6.2.3 Interacting with the VirtioFS Device

The following example will show commands executed on the target prepended with [root@board]# and commands executed on the host prepended with [host]#. Comments are prepended with #.

# Mount the VirtioFS device in the target, note that when we created the
# VirtioFS device above, we set the tag_name to fs0.
[root@board]# mount -t virtiofs fs0 /mnt
[root@board]# echo "hello from target" > /mnt/channel.txt
[host]# cat <shared-directory-in-the-host>/channel.txt
hello from target
[host]# echo "hello from host" > <shared-directory-in-the-host>/channel.txt
[root@board]# cat /mnt/channel.txt
hello from host
[root@board]# umount /mnt

2.5.6.2.4 Using the VirtioFS Device as a Root File System

Since the VirtioFS device is recognized by linux as a file system device, it can also be mounted by Linux as a root file system during boot. This can for example be useful to quickly test changes in the root file system without needing to rebuild or relaunch the target.

For this to work, the kernel command line must be modified to include rootfstype=virtiofs root=<tag> where <tag> would be fs0 in the case of the created VirtioFS device above. Additionally, the files in the shared root file system directory on the host must have appropriate permissions. An example of having this set up is having buildroot creating a root file system with correct permissions by using for instance the BR2_TARGET_ROOTFS_TAR config. One would have to extract the content of the outputted tar file. Finally, since The virtioFS daemon does not do any uid/gid translation, the uid/gid of the user running the daemon is what the target will see. Therefore, running the simulator under fakeroot is suggested to get proper uid/gid translation.

2.5.6.2.4.1 The User Provided Linux QSP Target

The user provided Linux target for QSP has some flexibility which makes integration of VirtioFS as a root file system device straight forward. An example of launching the user provided Linux target with VirtioFS as root file system is shown below.

simics> load-target qsp-x86/user-provided-linux machine:software:linux:kernel=<path-to-kernel-image> machine:software:linux:virtio_rootfs=<path-to-root-file-system-directory> machine:software:linux:cmdline="console=ttyS0,115200n8 earlyprintk rootfstype=virtiofs root=myfs rw"

2.5.7 Using TFTP

It is possible to transfer files from the host environment to the target (simulated) machine by using the TFTP feature provided by the service-node. Since TFTP executes in lockstep, with only one packet acknowledged at a time, it is slower than for example FTP, but it reliably transports files between the host and target machines.

It is assumed that a service node has been created and connected to the Ethernet device through an Ethernet link. The following example presents how TFTP is used on a target machine which is running Linux and has the tftp program installed. Furthermore, the target machine in this example is using the IP address 10.10.0.10 and the service node uses 10.10.0.1.

Creating a service node and connecting it to the target machine can be done with the connect-real-network command. This will also set up port forwarding to the real network, even though this is not a requirement for using TFTP:

simics> connect-real-network 10.10.0.10

After booting the target machine into Linux the first step is to bring up the network interface that is connected to the service node. At the target prompt, issue:

joe@computer: ~# ifconfig eth0 10.10.0.10 up

To transfer the file myfile.txt from the host machine, issue:

joe@computer: ~# tftp -l myfile.txt -g 10.10.0.1

The directory that the service node uses to find files downloaded by the target can be changed with the <service-node>.set-tftp-directory command. This also controls where uploaded files are saved. The default is to search the Simics path, starting with the current working directory of the Simics process. The search path can be changed with the add-directory command and can be viewed with the list-directories command.

2.5.8 Using FTP service

The service-node provides a FTP service to support file transfers with the host using FTP clients on a target machine.

Assuming a service-node has been created, and that it is named default_service_node0, the FTP-service will be named default_service_node0.ftp. The service node must be configured with an IP address and connected to a link before the FTP service can be enabled and configured. The FTP root directory, the location on the host system for transferred files, can be set by the set-ftp-directory command. The default root directory is the current directory for the Simics session, usually your project directory.

On a target that runs Clear Linux there is usually a curl command for transferring data using various protocols, it can put a file to the server and retrieve a file from the server.

Here is an example on using the FTP service on a target running Firststeps with a network. Start Simics and run the following commands:

simics> run-script "targets/qsp-x86/firststeps.simics"
simics> default_service_node0.set-ftp-directory dir = "/tmp/"

A Firststeps target was created. The target machine's IP address is 10.10.0.1 and it contains a default_service_node0 with the FTP service. The root directory was set to the /tmp/ directory on the host file system.

Continue the simulation until a prompt is reached.

simics> c

When prompt has been reached, the curl command can be run in the text console.

~ # curl -T /etc/hostname ftp://10.10.0.1/targetfile.txt

The contents of the target file /etc/hostname has now been sent to the host file targetfile.txt in the path set from the set-ftp-directory command. In similar way:

~ # curl ftp://10.10.0.1/targetfile.txt -o hostfile.txt

will retrieve a file, targetfile.txt, from the host and save it as hostfile.txt on the target.

Using other kind of distributions of Linux than Clear Linux there will most likely exist a ftp command in the shell for running a ftp client. An example on how to put a file to the host:

#> ftp 10.10.0.1
Connected to 10.10.0.1.
220 Welcome to the Simics FTP server
Name (10.10.0.1:simics): <enter>
230 User logged in
ftp> put /proc/cpuinfo targetfile.txt
local: /proc/cpuinfo remote: targetfile.txt
200 OK
150 transfer about to start
226 file transfer done
6520 bytes sent in 0.00 secs (12734.4 kB/s)

If running Microsoft Windows as target then Windows Internet Explorer can be used as client to connect to the FTP service by entering ftp://10.10.0.1 as address. This will allow transfer from host to target. To be able to transfer files back to the host you will need to select Page → Open FTP Site in Windows Explorer.

2.5.9 Importing a Real Disk into Simics

It is possible to create an image by copying data from a real disk. If the disk to be copied contains an operating system, you must have at least two operating systems on the machine, since the partition that should be copied should not be in use or mounted.

Before making a copy of a disk, some information about the disk should be gathered:

On Linux, these numbers can be obtained using the fdisk utility. You can choose to make a copy of the whole disk or just a partition of the disk using the dd utility. Example:

dd if=/dev/hdb of=hdb_disk.img

On Windows, you can use the System Information application to find the information under Components/Storage/Disks. You have to select the Advanced setting from the View menu. If you have Cygwin (http://www.cygwin.com) installed, you can use the dd utility to create the image, provided that the correct entries in the /dev file system are created. To access the first hard drive (/dev/hda):

mkdir -p /dev/hda
mount -s -b '\\.\PHYSICALDRIVE0' /dev/hda

You can also mount a specific drive letter:

mkdir -p /dev/fd0
mount -s -b '\\.\A:' /dev/fd0

or

mkdir -p /dev/hda1
mount -s -b '\\.\C:' /dev/hda1

Cygwin's mount program creates persistent mounts (they are stored in the registry), so you will only need to set these things up once. The -b option to mount ensures that no CR/LF conversions are made. See the Cygwin documentation for further details on how to use the mount command.

On Windows hosts without Cygwin, a third-party program can be used to create the disk images. See figure 1 for more details.

To save space, you may want to compress the disk image using the craff utility. See section 2.5.2.8.

The next step is to prepare the target configuration so it can use the new disk. For x86 targets, the dredd machine has a $disk_files parameter that can be set to a list of files to use in the image object of the boot disk, and also $disk_size that specifies the size of that disk.

$disk_size = 1056964608
$disk_files = [["hdb_disk.img", "ro", 0, 1056964608, 0]]

For other machines, that do not have these parameters, attributes in the disk object and its corresponding image objects have to be set instead.

Make sure to set the $disk_size correctly to reflect the size of the disk that has been copied. If only a partition has been copied, the offset where the partition starts, and the size of the partition, should be set in the file list. If the whole disk has been copied, the offset is zero and the size should be the size of the whole disk. Several partitions can be combined to form the complete disk, as described in section 2.5.2.7.

For an x86 machine, the system component will automatically set the BIOS geometry for the C: disk. It can also be set manually:

simics> system_cmp0.cmos-hd C 1023 16 63

2.5.10 Foreign Image Formats Including QEMU and VMware

Similar to using a disk image copied from a real disk as in section 2.5.9, images from other simulators or virtual machines can also be imported into Simics.

As when importing real disks, care has to be taken to map parameters outside of the disk itself to suitable parameters in the Simics virtual machine. Such parameters include disk geometry, type of disk, and simulated hardware. For example, importing a SCSI disk image to an IDE disk image in Simics may cause issues as the disk may lack drivers for IDE, or the software configuration may simply expect the disk to show up with a name that corresponds to the type of disk, leading to failure in locating critical data.

Many simulator and virtual machine file formats can be converted for Simics use through the qemu-img tool included with QEMU. Notably, any QEMU format and several VMware formats can be converted to the raw image format with qemu-img. The qemu-img tool is not included with Simics, but can be downloaded from http://www.qemu.org. See the QEMU documentation for more information about qemu-img.

The resulting raw image file can be used directly in Simics, or converted to craff. See 2.5.2.1 for a description of Simics image file formats.

Connecting simulated machines over a simulated serial connection is done by creating a ser-link component that connects to the serial devices in the machines. The link object can be thought of as modeling a serial cable that is plugged into the connectors on the devices—and just like a real cable, it is a point-to-point connection that connects exactly two devices.

The link object models serial communication at the character level in a simplified way. The bandwidth for the connection is configured in the link object, which means that the serial devices do not need to be explicitly configured by software.

New ser-link components can be added with the new-ser-link command:

simics> load-module ser-link
simics> new-ser-link
Created instantiated 'ser_link' component 'serial_link0'

Serial connectors of other components can then connect to that link. The serial link has two connectors, device0 and device1, representing the two endpoints of the cable. For an QSP-x86 machine, the second UART can be connected to the link the following way:

simics> connect serial_link0.device0 board.mb.sb.serial[2]

2.5.11.1.1 Text and Telnet Consoles

In addition to simulated serial devices, either endpoint of a serial link can be hooked up to a text console. (And just as with serial devices, you have the option of connecting the two endpoints directly without having a link in between—though this is generally much more useful with consoles.)

A text console can open GUI windows on the host computer, and let the user talk to the connected serial device. The text console also has the ability to start a telnet server; the user can then use any telnet program to connect to this server, and talk to the connected serial device.

You create text consoles by instantiating txt_console_comp components, and connecting them to the link or device you want them to talk to, as in the following example:

simics> new-txt-console-comp con visible=TRUE
simics> connect con.serial board.mb.sb.serial[2]

2.5.11.2 Host Serial Console

The text consoles also have the ability to open host serial connections. This is a way of connecting a terminal application through a serial port on the host machine to a serial device object in Simics. The procedure is almost identical in both Linux and Windows. The following examples show how to open host serial connections on a QSP-x86 machine, first in Linux:

simics> board.serconsole.con.host-serial-setup pty = /dev/pts/1
[board.serconsole.con info] Device opened: /dev/pts/1

And the same procedure in Windows:

simics> board.serconsole.con.host-serial-setup pty = COM1
[board.serconsole.con info] Opened: COM1

The host serial connection will operate at the baud rate and other attributes that are set for the physical serial port of the host. In Linux these host settings are edited with stty. In Windows these settings must be edited from a Command Prompt, this is an example:

C:\> mode com1: baud=4800 parity=n data=8 stop=1

It is also possible to open a host serial connection to a virtual serial port. In Linux this is called pseudo-terminal or pseudo-device. If there is no parameter specified to host-serial-setup, any free pts is opened.

However, in Windows a virtual serial port must have been created in advance. There exist several Windows third-party utilities which create virtual serial port pairs for various purposes. In the following example, first such a pair has been created with the port names COM98 and COM99, then a host serial connection can be opened to port 98, and finally the terminal application can connect to port 99 and the two can communicate over the pair:

simics> board.serconsole.con.host-serial-setup pty = COM98
[board.serconsole.con info] Opened: COM98

2.5.11.2.1 Using host serial connections with Windows HyperTerminal

Once the host serial connection has been configured within Simics it is possible to connect to it using a standard terminal program running on your host system. In this section we will use the HyperTerminal application, which comes bundled with some Windows versions, to connect to the simulated system.

First launch HyperTerminal from Start Menu → All Programs → Accessories → Communications → HyperTerminal. The dialog Connection Description will appear, as shown in Figure 2. Name the Connection "Simics" and press OK. In the next dialog, select the COM-port that Simics has opened in the field Connect using. In the next dialog, just accept the default settings and press OK.

Figure 2. Connecting to Simics using the Windows HyperTerminal

Now, resume the simulation in Simics. The output from the simulated serial console will appear in the HyperTerminal window. See figure 3.

Figure 3. HyperTerminal connected to a simulation

2.6 Instrumentation

This section describes the Instrumentation Framework in Simics.

Simics' instrumentation framework provides a mechanism for inspecting various parts of the running simulated system and features a flexible way to dynamically add/remove inspections. The framework is designed with performance in mind, allowing fast and parallel execution with low latency communication.

Unlike most profiling tools, which instrument the target source code or object code, Simics can profile a workload non-intrusively. This allows you to profile without disturbing the execution. Simics will profile arbitrary code, including device drivers, dynamically generated code, and code for which you do not have the source. Also, unlike most profiling tools, Simics collects profiling data exactly, not by sampling the execution and relying on statistics.

What information that can be obtained through the instrumentation framework depends on how well the devices and other objects have been adapted to this new framework. Since the framework is new, only some models support instrumentation through various interfaces:

Conceptually, Simics instrumentation framework consists of four elements:

Figure 4 shows an abstract view of a possible setup. The instrumentation provider sends information through an instrumentation connection to the tool. The instrumentation filter can decide to disable the gathering of information from the provider. At any point within the simulation the tool can produce output for the user on what it has collected.

Figure 4. Abstract view of instrumentation elements

To concretize the abstract example in a more realistic setup, figure 5 shows an "instruction-histogram" tool which counts the instructions being executed and their frequency, producing a histogram of the most commonly used instructions that were executed on this processor (system-wide, that is, both instructions in the operating system, kernel threads, and all user-level processes are counted).

Figure 5. Simple example

If only a specific application's instruction mix is of interest, we can add a filter to this setup. In figure 6, a process-filter has been setup to monitor the 'perl' application only. The process-filter uses the OS-Awareness feature in Simics to get information when any perl process is created, scheduled on a certain cpu, or die. With this knowledge, the process-filter enables and disables the instruction-histogram collection. The output from the instruction-histogram is now only the instruction mix for the perl application(s) that has executed.

Figure 6. Simple example with a filter

Filters are orthogonal to the tools, and the tools are themselves unaware of any existing filters controlling them. From the tool perspective, it is simply someone requesting their connections to be disabled or enabled.

It is possible to have multiple filters controlling one tool as shown in figure 7. This allows sophisticated setups for zooming in a specific details of the instrumentation gathered.

In principle, all tools always have one filter: the user, which can use Simics commands to enable or disable the communication manually, in a script or in a script-branch. By using enable/disable commands in a script branch, the user have the possibility to control when the instrumentation should be active based on virtual-time, breakpoints triggered etc.

As long as all filters enables the tool's connection, it will be active.

Figure 7. Multiple filters, all enables

But as shown in figure 8 when any filter decides to disable the connection it will become disabled and remain so until all filters have enabled it again.

Figure 8. Multiple filters, one disables

How providers and tools are allowed to be connected depends on the tool's requirements of the providers interfaces. That is, some tools can only connect to certain providers.

It is possible to connect:

An even more advanced example is illustrated the in figure 9. In this setup, there are two tools working simultaneously, both are connected to the same SMP cluster of processors and both are using the same filter which tracks the 'perl' application.

Figure 9. Advanced example

2.6.1 Command Set

This chapter describes an overview of the Simics commands that can be used in relation to instrumentation support.

When Simics has loaded a system configuration, providers normally already exist, as part of the device models. Which tools that are available depends on the Simics packages installed. To get an overview of the available instrumentation commands in Simics, use the command: help Instrumentation.

It is also possible to use the list-instrumentation-tools command to see the tools available.

Available tools must first be created with a new-<tool> command, for example new-instruction-histogram.

Once a tool has been created it will have a number of generic instrumentation commands associated with it, as well as tool specific commands. Tools typically supports the following generic commands:

By connecting an Instrumentation tool object to an Instrumentation provider object, information can be extracted from the running system. Connecting can be done either directly when the tool is created with the new-<tool> command, or done afterwards with the <tool>.add-instrumentation command.

The established connections between providers and tools can be listed with the list-instrumentation command.

Figure 10 shows a simple example of commands for creating two different instruction histograms and their output after running some instructions.

simics> new-instruction-histogram -connect-all view = size
[board.mb.cpu0.core[0][0] info] VMP not engaged. Reason: instrumentation enabled.
Created ihist0 (connected to 1 processor)
simics> new-instruction-histogram -connect-all view = mnemonic
Created ihist1 (connected to 1 processor)
simics> list-instrumentation
  1 ihist0 - connected to board.mb.cpu0.core[0][0]:0 view = "size"
  2 ihist1 - connected to board.mb.cpu0.core[0][0]:1 view = "mnemonic"
simics> c 10000
simics> ihist0.histogram
┌─────┬───────┬─────┬───────┬───────────┐
│Row #│  Size │Count│ Count%│Accumulated│
│     │(bytes)│     │       │   Count%  │
├─────┼───────┼─────┼───────┼───────────┤
│    1│      2│ 4480│ 44.80%│     44.80%│
│    2│      1│ 1661│ 16.61%│     61.41%│
│    3│      3│ 1596│ 15.96%│     77.37%│
│    4│      5│ 1074│ 10.74%│     88.11%│
│    5│      4│  560│  5.60%│     93.71%│
│    6│      6│  408│  4.08%│     97.79%│
│    7│      7│  212│  2.12%│     99.91%│
│    8│      8│    8│  0.08%│     99.99%│
│    9│     10│    1│  0.01%│    100.00%│
├─────┼───────┼─────┼───────┼───────────┤
│Sum  │       │10000│100.00%│           │
└─────┴───────┴─────┴───────┴───────────┘
Average size of executed instruction: 2.702500 (21 bits)

simics> ihist1.histogram max = 10
┌─────────┬────────┬─────┬───────┬───────────┐
│  Row #  │mnemonic│Count│ Count%│Accumulated│
│         │        │     │       │   Count%  │
├─────────┼────────┼─────┼───────┼───────────┤
│        1│mov     │ 3025│ 30.25%│     30.25%│
│        2│jmp     │  901│  9.01%│     39.26%│
│        3│add     │  864│  8.64%│     47.90%│
│        4│cmp     │  854│  8.54%│     56.44%│
│        5│out     │  731│  7.31%│     63.75%│
│        6│movsx   │  413│  4.13%│     67.88%│
│        7│jne     │  386│  3.86%│     71.74%│
│        8│inc     │  377│  3.77%│     75.51%│
│        9│je      │  345│  3.45%│     78.96%│
│       10│jae     │  267│  2.67%│     81.63%│
├─────────┼────────┼─────┼───────┼───────────┤
│# 10/42  │        │     │       │           │
│Sum (all)│        │10000│100.00%│           │
│Sum shown│        │ 8163│ 81.63%│           │
└─────────┴────────┴─────┴───────┴───────────┘
Figure 10. Command Example

The framework also supports creating named groups, which can be used in the above commands to, for example, disable a number of connections with a single command. Such groups can be created with the create-instrumentation-group command. The group name can then be used when the connection is established with the new-<tool> and <tool>.add-instrumentation commands.

Each established connection between a provider and tool gets a connection specific sub-object associated with the tool. When multiple tools are connected to a provider, by default, the provider will give instrumentation information to each tool in the order they where connected. That is, each new connection automatically comes last.

Some providers, however, supports re-arranging the order of how the tools are called. This can be useful if one tool makes any kind of change to the simulation that the other tools should see the effect of. For providers which support re-arranging the dispatch order, the instrumentation-order command can be used to see the order and the instrumentation-move command can be used to move a tool connection to an earlier or later stage.

The instrumentation setup is session specific, thus it will not be saved in checkpoints. To re-establish the instrumentation capabilities after loading a checkpoint, objects must be re-created and communication setup again using the same commands that was used before the checkpoint was saved. This can for example be done in a script executed after the checkpoint has been loaded.

2.6.2 Instrumentation Tools

Below are some instrumentation tools listed, that are currently distributed (including source code), together with a short description of the tools purposes.

2.6.3 Instrumentation Filters

The following instrumentation filters are available, including source code:

2.6.4 Exporting Instrumentation Data

Some instrumentation tools supports printing out the gathered data to a comma separated value (.csv) file. This makes it easy to import the raw data with an external spreadsheet program, for further analyses, calculations and visualizing the data as graphs.

Any Simics object that implements the table interface will automatically get the export-table-csv command and it exist on either the tool for the total, the individual connection objects, or both. See figure 11 for an example.

$> ./simics targets/qsp-x86/firststeps.simics num_cores=8
simics> new-instruction-histogram -connect-all
[board.mb.cpu0.core[0][0] info] VMP not engaged. Reason: instrumentation enabled.
Created ihist0 (connected to 8 processors)
simics> run 10 s
simics> ihist0.export-table-csv file = all-cpus-10seconds.csv
simics> ihist0.con0.export-table-csv file = first-cpu-10seconds.csv
simics> quit
$> head all-cpus-10seconds.csv
mnemonic,Count
mov,919169650
add,302311251
cmp,206492934
test,160303832
adc,150848000
jne,144637182
je,133354184
lea,120721026
push,97795399

$> head first-cpu-10seconds.csv
mnemonic,Count
mov,414052451
add,165503526
cmp,100845317
adc,88820557
test,71309728
jne,70008313
lea,56315642
movzx,54618965
je,52189244
Figure 11. Exporting to CSV files

2.6.5 Profiling Example

This chapter describes a sample session when using some of the instrumentation tools. Refer to the command descriptions for details on each individual command.

As a basis for this example, the Coremark www.eembc.org/coremark/ benchmark will be run and analyzed in Simics. The source code is downloadable with an Apache license and very easy to compile and run. In this example Coremark will be run on a x86 QSP system running Linux.

2.6.5.1 Profiling Setup

The Coremark binary was compiled on standard x86 Linux host system and in this example we are using the simics-agent to transfer the binary to the target system. The script in figure 12 shows how the binary is uploaded to the target, enabling instrumentation only for the coremark executable and starting coremark and waiting for it to be finished.

simics> run-script "targets/qsp-x86/firststeps.simics"
simics> script-branch {
    local $con = board.serconsole.con
    local $bin = coremark.exe
    local $params = " 0x0 0x0 0x66 10000 7 1 2000"    # run1 parameters (10000 iterations)
    local $prompt = "# "

    # Wait for a linux prompt to appear and then upload our binary
    # to the target system using simics-agent
    bp.console_string.wait-then-write console = $con $prompt "simics-agent &\n"
    $am = (start-agent-manager)
    $m  = ($am.connect-to-agent)
    $m.change-directory "/root"
    $j = ($m.upload -executable $bin)
    $m.wait-for-job $j

    # Create and connect instrumentation tools
    $ihn = (new-instruction-histogram -connect-all view = x86-normalized)
    $ihs = (new-instruction-histogram -connect-all view = size)
    $bp = (new-x86-branch-profiler -connect-all)
    $mp = (new-memory-profiler -connect-all -read-logical -write-logical -execute-logical)

    # Only let the instrumentation tools monitor our process
    $pattern ="name='" + $bin + "'"
    $pf = (new-process-filter software-component = board.software pattern = $pattern)
    $ihn.add-filter filter = $pf
    $ihs.add-filter filter = $pf
    $bp.add-filter filter = $pf
    $mp.add-filter filter = $pf

    # Start the process, wait for it to finish, and then stop simulation
    $con.input "\n"
    bp.console_string.wait-then-write console = $con $prompt ("./" + $bin + $params + "\n")
    $con.bp-wait-for-console-string $prompt
    stop
}
simics> run
Figure 12. Setup Script

In this setup four instrumentation tools are enabled:

The instrumentation data gathered by these tools will be analyzed a bit in the next chapters. To get a bit more readable big numbers, the following commands splits the numbers with underscores on logical units; digit-grouping 10 3 for decimal values and digit-grouping 16 4 for hexadecimal values.

2.6.5.2 Instruction Profiling

First lets take a look of which instructions that are most commonly executed shown in figure 13.

simics> ihist0.histogram max = 10
┌─────────┬────────────────────────────────┬─────────────┬───────┬───────────┐
│  Row #  │         x86 normalized         │    Count    │ Count%│Accumulated│
│         │                                │             │       │   Count%  │
├─────────┼────────────────────────────────┼─────────────┼───────┼───────────┤
│        1│mov r32,r32                     │  330_467_974│  9.60%│      9.60%│
│        2│jne imm                         │  308_597_223│  8.96%│     18.56%│
│        3│mov r64,r64                     │  204_178_395│  5.93%│     24.49%│
│        4│je imm                          │  190_302_166│  5.53%│     30.01%│
│        5│add r32,r32                     │  142_190_315│  4.13%│     34.14%│
│        6│test r64,r64                    │  140_221_077│  4.07%│     38.21%│
│        7│mov r64,qword ptr [r64]         │  139_282_002│  4.04%│     42.26%│
│        8│and r32,imm                     │  130_455_258│  3.79%│     46.05%│
│        9│cmp r8,imm                      │  123_723_347│  3.59%│     49.64%│
│       10│movsx r32,word ptr [r64+r64*imm]│  123_120_000│  3.58%│     53.22%│
├─────────┼────────────────────────────────┼─────────────┼───────┼───────────┤
│# 10/425 │                                │             │       │           │
│Sum (all)│                                │3_443_579_955│100.00%│           │
│Sum shown│                                │1_832_537_757│ 53.22%│           │
└─────────┴────────────────────────────────┴─────────────┴───────┴───────────┘
Figure 13. Instruction mix

Here the 10 most commonly executed instruction combination is listed. A total of 425 different instruction variants was identified when running the coremark benchmark. With 10,000 iterations in coremark, roughly 3 billion instructions was executed in total. SSE registers are marked as x128 in the disassembly, lets see if any of these where executed in figure 14.

simics> ihist0.histogram sort-on-column = "x86 normalized" instruction-regexp = ".*x128"
 Table reduced from 425 to 29 rows
┌─────┬──────────────────────────────────┬─────┐
│Row #│          x86 normalized          │Count│
├─────┼──────────────────────────────────┼─────┤
│    1│cvtsi2sd(64) x128,r64             │    5│
│    2│divsd x128,[rip+imm]              │    4│
│    3│divsd x128,x128                   │    1│
│    4│movapd x128,x128                  │    1│
│    5│movaps xmmword ptr [r64+imm],x128 │   16│
│    6│movd r64,x128                     │   10│
│    7│movd x128,r32                     │   42│
│    8│movd x128,r64                     │    6│
│    9│movdqa x128,x128                  │   71│
│   10│movdqa x128,xmmword ptr [r64+imm] │    1│
│   11│movdqu x128,xmmword ptr [r64+imm] │   45│
│   12│movdqu x128,xmmword ptr [r64]     │   48│
│   13│movsd [r64+imm],x128              │    1│
│   14│movsd [r64-imm],x128              │    2│
│   15│movsd x128,[r64+imm]              │    1│
│   16│movsd x128,[r64]                  │    2│
│   17│movsd x128,[rip+imm]              │    1│
│   18│pcmpeqb x128,x128                 │  178│
│   19│pcmpeqb x128,xmmword ptr [r64+imm]│    9│
│   20│pcmpeqb x128,xmmword ptr [r64]    │    1│
│   21│pminub x128,[r64+imm]             │    3│
│   22│pmovmskb r32,x128                 │  103│
│   23│por x128,x128                     │   85│
│   24│pshufd x128,x128,imm              │   41│
│   25│punpcklbw x128,x128               │   41│
│   26│punpcklwd x128,x128               │   41│
│   27│pxor x128,x128                    │   68│
│   28│ucomisd x128,[rip+imm]            │    1│
│   29│ucomisd x128,x128                 │    1│
├─────┼──────────────────────────────────┼─────┤
│Sum  │                                  │  829│
└─────┴──────────────────────────────────┴─────┘
Figure 14. SSE instructions

So a few instructions, but they are not used in the body of the benchmark at all, since they are executed so few times. Now lets investigate the instruction sizes of the executed instructions, which is gathered by the other instruction-histogram shown in figure 15.

simics> ihist1.histogram
┌─────┬───────┬─────────────┬───────┬───────────┐
│Row #│  Size │    Count    │ Count%│Accumulated│
│     │(bytes)│             │       │   Count%  │
├─────┼───────┼─────────────┼───────┼───────────┤
│    1│      3│1_552_922_442│ 45.10%│     45.10%│
│    2│      2│  867_469_439│ 25.19%│     70.29%│
│    3│      4│  575_089_349│ 16.70%│     86.99%│
│    4│      5│  253_701_408│  7.37%│     94.35%│
│    5│      6│  145_314_324│  4.22%│     98.57%│
│    6│      1│   34_766_864│  1.01%│     99.58%│
│    7│      7│    9_451_053│  0.27%│     99.86%│
│    8│      8│    4_273_215│  0.12%│     99.98%│
│    9│      9│      550_962│  0.02%│    100.00%│
│   10│     10│       40_428│  0.00%│    100.00%│
│   11│     11│          456│  0.00%│    100.00%│
│   12│     12│           14│  0.00%│    100.00%│
├─────┼───────┼─────────────┼───────┼───────────┤
│Sum  │       │3_443_579_954│100.00%│           │
└─────┴───────┴─────────────┴───────┴───────────┘
Average size of executed instruction: 3.187071 (25 bits)
Figure 15. Instruction sizes

Three byte instructions are by far the most common instruction size when executing this benchmark.

The branch-profiler will report more details on the branch instructions shown in figure 16.

simics> bprof0.histogram max = 15
┌─────────┬───────────┬───────────┬───────────┬───────────┬───────┬───────────┐
│  Row #  │   Branch  │   Taken   │  Non-taken│   Total   │ Total%│Accumulated│
│         │Instruction│           │           │           │       │   Total%  │
├─────────┼───────────┼───────────┼───────────┼───────────┼───────┼───────────┤
│        1│jne        │273_402_780│ 35_194_443│308_597_223│ 47.44%│     47.44%│
│        2│je         │ 10_877_292│179_424_874│190_302_166│ 29.25%│     76.69%│
│        3│jmp        │ 34_532_902│          -│ 34_532_902│  5.31%│     82.00%│
│        4│ja         │  6_862_422│ 15_880_527│ 22_742_949│  3.50%│     85.50%│
│        5│call       │ 20_884_324│          -│ 20_884_324│  3.21%│     88.71%│
│        6│ret        │ 20_884_320│          -│ 20_884_320│  3.21%│     91.92%│
│        7│jbe        │ 14_880_208│  2_120_535│ 17_000_743│  2.61%│     94.53%│
│        8│jg         │  6_640_020│  6_320_100│ 12_960_120│  1.99%│     96.52%│
│        9│jb         │  7_021_647│    880_696│  7_902_343│  1.21%│     97.74%│
│       10│js         │  3_991_228│  2_330_079│  6_321_307│  0.97%│     98.71%│
│       11│jle        │  2_665_772│  2_280_227│  4_945_999│  0.76%│     99.47%│
│       12│jae        │    360_746│  3_080_521│  3_441_267│  0.53%│    100.00%│
│       13│jns        │         29│         10│         39│  0.00%│    100.00%│
│       14│jl         │         14│          4│         18│  0.00%│    100.00%│
│       15│jcxz       │          0│          0│          0│  0.00%│    100.00%│
├─────────┼───────────┼───────────┼───────────┼───────────┼───────┼───────────┤
│# 15/22  │           │           │           │           │       │           │
│Sum (all)│           │403_003_704│247_512_016│650_515_720│100.00%│           │
│Sum shown│           │403_003_704│247_512_016│650_515_720│100.00%│           │
└─────────┴───────────┴───────────┴───────────┴───────────┴───────┴───────────┘
Figure 16. Branch instructions

Here we see the most commonly used branch instruction with information if the branches were taken or not. Unconditional jumps, of course, always jumps. Finally, we can get instruction information from the memory-profiler shown in figure 17.

simics> mprof0.profile view = execute-logical max = 10
┌─────────┬────────────────┬────────────────┬─────────────┬───────┬───────────┐
│  Row #  │     Start      │      Stop      │    Count    │ Count%│Accumulated│
│         │                │                │             │       │   Count%  │
├─────────┼────────────────┼────────────────┼─────────────┼───────┼───────────┤
│        1│     0x0040_1000│     0x0040_1fff│2_454_100_308│ 71.27%│     71.27%│
│        2│     0x0040_2000│     0x0040_2fff│  909_181_060│ 26.40%│     97.67%│
│        3│     0x0040_0000│     0x0040_0fff│   80_070_862│  2.33%│     99.99%│
│        4│0x7ff1_b614_4000│0x7ff1_b614_4fff│       56_044│  0.00%│    100.00%│
│        5│0x7ff1_b65d_c000│0x7ff1_b65d_cfff│       48_656│  0.00%│    100.00%│
│        6│0x7ff1_b65d_e000│0x7ff1_b65d_efff│       29_808│  0.00%│    100.00%│
│        7│0x7ff1_b65e_b000│0x7ff1_b65e_bfff│       22_112│  0.00%│    100.00%│
│        8│0x7ff1_b65d_b000│0x7ff1_b65d_bfff│       15_855│  0.00%│    100.00%│
│        9│0x7ff1_b65e_3000│0x7ff1_b65e_3fff│        8_683│  0.00%│    100.00%│
│       10│0x7ff1_b609_b000│0x7ff1_b609_bfff│        7_780│  0.00%│    100.00%│
├─────────┼────────────────┼────────────────┼─────────────┼───────┼───────────┤
│# 10/90  │                │                │             │       │           │
│Sum (all)│                │                │3_443_579_954│100.00%│           │
│Sum shown│                │                │3_443_541_168│100.00%│           │
└─────────┴────────────────┴────────────────┴─────────────┴───────┴───────────┘
Figure 17. Instruction executed in memory (4K granularity)

Here we can see where we have executed most, using the logical addresses. By default, the range between start and stop is set to 4K, but this can be controlled through a command argument. So the coremark binary executed on a total of 90 4K pages, but 99.9% of its execution was only in three pages. The memory-profiler will count the first byte of an instruction as executed. So if we decrease the granularity to just one byte shown in figure 18.

simics> mprof0.profile view = execute-logical granularity = 1 max = 10
┌──────────┬───────────┬───────────┬─────────────┬───────┬───────────┐
│  Row #   │   Start   │    Stop   │    Count    │ Count%│Accumulated│
│          │           │           │             │       │   Count%  │
├──────────┼───────────┼───────────┼─────────────┼───────┼───────────┤
│         1│0x0040_1333│0x0040_1333│   61_200_000│  1.78%│      1.78%│
│         2│0x0040_1336│0x0040_1336│   61_200_000│  1.78%│      3.55%│
│         3│0x0040_1339│0x0040_1339│   61_200_000│  1.78%│      5.33%│
│         4│0x0040_133c│0x0040_133c│   61_200_000│  1.78%│      7.11%│
│         5│0x0040_133f│0x0040_133f│   61_200_000│  1.78%│      8.89%│
│         6│0x0040_1330│0x0040_1330│   59_160_000│  1.72%│     10.60%│
│         7│0x0040_112a│0x0040_112a│   29_160_000│  0.85%│     11.45%│
│         8│0x0040_112d│0x0040_112d│   29_160_000│  0.85%│     12.30%│
│         9│0x0040_1950│0x0040_1950│   29_160_000│  0.85%│     13.14%│
│        10│0x0040_1953│0x0040_1953│   29_160_000│  0.85%│     13.99%│
├──────────┼───────────┼───────────┼─────────────┼───────┼───────────┤
│# 10/12542│           │           │             │       │           │
│Sum (all) │           │           │3_443_579_954│100.00%│           │
│Sum shown │           │           │  481_800_000│ 13.99%│           │
└──────────┴───────────┴───────────┴─────────────┴───────┴───────────┘
Figure 18. Instruction executed in memory (1 byte granularity)

This produces the top-list of the most executed instruction of the benchmark. A total of 12542 unique instructions (in memory) have been executed.

2.6.5.3 Data Profiling

For the same coremark run, now examine a bit on how many data accesses that happened, using the memory-profile tool again shown in figure 19.

simics> mprof0.profile view = read-logical max = 10
┌─────────┬────────────────┬────────────────┬─────────────┬───────┬───────────┐
│  Row #  │     Start      │      Stop      │    Count    │ Count%│Accumulated│
│         │                │                │             │       │   Count%  │
├─────────┼────────────────┼────────────────┼─────────────┼───────┼───────────┤
│        1│     0x0060_5000│     0x0060_5fff│2_064_749_345│ 76.15%│     76.15%│
│        2│0x7fff_15e5_e000│0x7fff_15e5_efff│  646_513_938│ 23.84%│     99.99%│
│        3│          0x0000│          0x0fff│       79_652│  0.00%│     99.99%│
│        4│0x7ff1_b67f_3000│0x7ff1_b67f_3fff│       48_740│  0.00%│     99.99%│
│        5│0x7fff_15e5_d000│0x7fff_15e5_dfff│       24_501│  0.00%│     99.99%│
│        6│0x7ff1_b67f_6000│0x7ff1_b67f_6fff│       12_934│  0.00%│     99.99%│
│        7│0x7ff1_b67f_0000│0x7ff1_b67f_0fff│       12_375│  0.00%│    100.00%│
│        8│0x7ff1_b63c_6000│0x7ff1_b63c_6fff│       10_768│  0.00%│    100.00%│
│        9│0x7ff1_b67f_5000│0x7ff1_b67f_5fff│        5_678│  0.00%│    100.00%│
│       10│0x7ff1_b63c_4000│0x7ff1_b63c_4fff│        4_988│  0.00%│    100.00%│
├─────────┼────────────────┼────────────────┼─────────────┼───────┼───────────┤
│# 10/88  │                │                │             │       │           │
│Sum (all)│                │                │2_711_569_260│100.00%│           │
│Sum shown│                │                │2_711_462_919│100.00%│           │
└─────────┴────────────────┴────────────────┴─────────────┴───────┴───────────┘
Figure 19. Read profile (4K granularity)

Here we can see that the reads to memory is pretty much only associated to one heap-page, and one stack-page. The memory-profiler here annotates each byte as being read. So if the software performs a 32 bit read, the four bytes will all have an increased counter. The similar for command for write-accesses is shown in figure 20.

simics> mprof0.profile view = write-logical max = 10
┌─────────┬─────────────────────┬─────────────────────┬─────────────┬───────┬───────────┐
│  Row #  │        Start        │         Stop        │    Count    │ Count%│Accumulated│
│         │                     │                     │             │       │   Count%  │
├─────────┼─────────────────────┼─────────────────────┼─────────────┼───────┼───────────┤
│        1│          0x0060_5000│          0x0060_5fff│  595_005_622│ 53.23%│     53.23%│
│        2│     0x7fff_15e5_e000│     0x7fff_15e5_efff│  522_602_567│ 46.75%│     99.99%│
│        3│0xffff_8801_f35a_f000│0xffff_8801_f35a_ffff│       81_720│  0.01%│     99.99%│
│        4│     0x7fff_15e5_d000│     0x7fff_15e5_dfff│       22_052│  0.00%│    100.00%│
│        5│     0x7ff1_b63c_6000│     0x7ff1_b63c_6fff│        5_924│  0.00%│    100.00%│
│        6│     0x7ff1_b602_3000│     0x7ff1_b602_3fff│        4_093│  0.00%│    100.00%│
│        7│     0x7ff1_b67f_3000│     0x7ff1_b67f_3fff│        3_690│  0.00%│    100.00%│
│        8│     0x7ff1_b65d_2000│     0x7ff1_b65d_2fff│        2_893│  0.00%│    100.00%│
│        9│     0x7ff1_b63c_4000│     0x7ff1_b63c_4fff│        2_712│  0.00%│    100.00%│
│       10│     0x7ff1_b67e_e000│     0x7ff1_b67e_efff│        2_512│  0.00%│    100.00%│
├─────────┼─────────────────────┼─────────────────────┼─────────────┼───────┼───────────┤
│# 10/31  │                     │                     │             │       │           │
│Sum (all)│                     │                     │1_117_752_782│100.00%│           │
│Sum shown│                     │                     │1_117_733_785│100.00%│           │
└─────────┴─────────────────────┴─────────────────────┴─────────────┴───────┴───────────┘
Figure 20. Write profile (4K granularity)

Not very surprisingly, the writes goes to the same two pages, but writes are less frequent than reads. And finally say that we are interested in the distribution of the writes on the most commonly read stack-page, this is shown in figure 21.

simics> mprof0.profile view = read-logical start = 0x7fff_15e5_e000 stop = 0x7fff_15e5_efff granularity = 128 sort-on-column = Start
┌─────┬────────────────┬────────────────┬───────────┐
│Row #│     Start      │      Stop      │   Count   │
├─────┼────────────────┼────────────────┼───────────┤
│    1│0x7fff_15e5_e000│0x7fff_15e5_e07f│        700│
│    2│0x7fff_15e5_e080│0x7fff_15e5_e0ff│      9_029│
│    3│0x7fff_15e5_e100│0x7fff_15e5_e17f│     14_585│
│    4│0x7fff_15e5_e180│0x7fff_15e5_e1ff│     17_138│
│    5│0x7fff_15e5_e200│0x7fff_15e5_e27f│     10_315│
│    6│0x7fff_15e5_e280│0x7fff_15e5_e2ff│414_573_166│
│    7│0x7fff_15e5_e300│0x7fff_15e5_e37f│171_369_107│
│    8│0x7fff_15e5_e380│0x7fff_15e5_e3ff│ 56_832_627│
│    9│0x7fff_15e5_e400│0x7fff_15e5_e47f│  1_004_298│
│   10│0x7fff_15e5_e480│0x7fff_15e5_e4ff│  2_681_185│
│   11│0x7fff_15e5_e500│0x7fff_15e5_e57f│        746│
│   12│0x7fff_15e5_e580│0x7fff_15e5_e5ff│        408│
│   13│0x7fff_15e5_e600│0x7fff_15e5_e67f│        240│
│   14│0x7fff_15e5_e680│0x7fff_15e5_e6ff│        220│
│   15│0x7fff_15e5_e700│0x7fff_15e5_e77f│         76│
│   16│0x7fff_15e5_e780│0x7fff_15e5_e7ff│         98│
├─────┼────────────────┼────────────────┼───────────┤
│Sum  │                │                │646_513_938│
└─────┴────────────────┴────────────────┴───────────┘
Figure 21. Distribution of stack reads on a 4K page

In this case we listed the addresses in address-order using the granularity of 128 bytes.

2.6.6 Device Register Coverage

The bank-coverage-tool provides register coverage for DML devices.

Device register coverage can be thought of as code coverage for modeled registers. The tool can aggregate register accesses over several Simics runs by saving intermediate results to a file and summarize the total coverage figures for each device through the command line interface. The tool can also export results to CSV.

The tool makes use of the bank instrumentation framework to collect register accesses and uses the general instrumentation framework and its familiar interfaces, documented in this section, to connect to register banks. Please see the API Reference Manual for more details about the bank instrumentation framework, which the tool also relies on, and how to develop your own tools using this framework.

By connecting the tool to banks of interest, or using the -connect-all flag when instantiating the tool, the tool will track all accesses to connected banks and summarize the number of accessed registers in a device as a percentage to the number of its registers in total. We call this the 'coverage' of a device. For further inquiry, the tool may also present the accrued number of accesses to individual registers in a connected bank. However the register accesses are presented, they may be filtered on read accesses, write accesses, or both. Both commands may be interacted with in this way and the output may be sorted and mutated in a variety of ways.

In order to instantiate the tool, use the new-bank-coverage-tool command and provide any banks you want to connect to the tool. Alternatively, you can use the -connect-all flag to connect it to any supporting banks in the system. Additional banks can be connected using the add-instrumentation command on the tool.

2.6.6.1 Example

When the configuration is loaded, instantiate and connect the bank coverage tool to all banks in the system like so:

simics> new-bank-coverage-tool -connect-all
Created coverage_tool0 (connected to 75 banks)

Once the tool is instantiated we may run the workload, which in this case is booting Linux. At any time we may stop the simulation and use the coverage command to present the coverage so far, which is ordered by the coverage percentage by default:

simics> coverage_tool0.coverage -write max = 10
┌─────────┬─────────────────────────┬──────────────────────┬─────────────┬─────────┐
│  Row #  │          Device         │No. accessed registers│No. registers│Coverage%│
├─────────┼─────────────────────────┼──────────────────────┼─────────────┼─────────┤
│        1│board.mb.sb.spi_flash_obj│                     0│            0│  100.00%│
│        2│board.mb.sb.com[0]       │                     5│            8│   62.50%│
│        3│board.mb.sb.com[2]       │                     5│            8│   62.50%│
│        4│board.mb.sb.com[1]       │                     5│            8│   62.50%│
│        5│board.mb.sb.com[3]       │                     5│            8│   62.50%│
│        6│board.mb.sb.hpet         │                    15│           28│   53.57%│
│        7│board.mb.sb.bridge       │                    17│           38│   44.74%│
│        8│board.mb.sb.uhci[4]      │                    19│           50│   38.00%│
│        9│board.mb.sb.uhci[2]      │                    19│           50│   38.00%│
│       10│board.mb.sb.uhci[0]      │                    19│           50│   38.00%│
├─────────┼─────────────────────────┼──────────────────────┼─────────────┼─────────┤
│# 10/44  │                         │                      │             │         │
│Sum (all)│                         │                   830│         3391│         │
│Sum shown│                         │                   109│          248│         │
└─────────┴─────────────────────────┴──────────────────────┴─────────────┴─────────┘

Note that we're limiting the output by using the max parameter. The output of the bank coverage tool commands may be morphed like this in many ways. It's left as an exercise to the user to explore these possibilities further.

A user may also export the attained coverage to CSV by using the to-file parameter:

simics> coverage_tool0.coverage -write max = 10 to-file = ~/coverage.csv

This will output a CSV file like so:

Device,No. accessed registers,No. registers,Coverage%
board.mb.sb.spi_flash_obj,0,0,1.0
board.mb.sb.com[0],5,8,0.625
board.mb.sb.com[2],5,8,0.625
board.mb.sb.com[1],5,8,0.625
board.mb.sb.com[3],5,8,0.625
board.mb.sb.hpet,15,28,0.5357142857142857
board.mb.sb.bridge,17,38,0.4473684210526316
board.mb.sb.uhci[4],19,50,0.38
board.mb.sb.uhci[2],19,50,0.38
board.mb.sb.uhci[0],19,50,0.38
...

Furthermore, we may inspect the register accesses, of any connected bank, of every device presented in the coverage output by using the access-count command:

simics> coverage_tool0.access-count board.mb.sb.hpet.bank.regs -write sort-on-column = "Count" max = 10
┌─────────┬───────────────┬──────┬────┬──────┐
│  Row #  │      Name     │Offset│Size│Count │
├─────────┼───────────────┼──────┼────┼──────┤
│        1│tim_comp[2]    │ 0x148│   8│529647│
│        2│tim_comp[3]    │ 0x168│   8│ 19140│
│        3│gen_conf       │  0x10│   8│     6│
│        4│fsb_int_rout[2]│ 0x150│   8│     6│
│        5│tim_conf[1]    │ 0x120│   8│     5│
│        6│tim_conf[0]    │ 0x100│   8│     5│
│        7│tim_conf[2]    │ 0x140│   8│     5│
│        8│tim_comp[0]    │ 0x108│   8│     2│
│        9│tim_comp[1]    │ 0x128│   8│     2│
│       10│main_cnt       │  0xf0│   8│     2│
├─────────┼───────────────┼──────┼────┼──────┤
│# 10/15  │               │      │    │      │
│Sum (all)│               │      │    │548825│
│Sum shown│               │      │    │548820│
└─────────┴───────────────┴──────┴────┴──────┘

Note that we're sorting the table based on the number of accesses rather than offset, which is the default.

If the tool were to be used to compile the total coverage figures over a set of runs, such as over several tests, a user may utilize the save command to temporarily save the results to disk:

simics> coverage_tool0.save ~/coverage_0.bin

...and subsequently use the load command to aggregate the results:

simics> coverage_tool0.load ~/coverage_0.bin
simics> coverage_tool0.load ~/coverage_1.bin
simics> coverage_tool0.load ~/coverage_2.bin
...

Once loaded, the coverage and access-count commands will act as if all the accesses had happened during the same run and present the combined results accordingly. In this way, a user may compile the aggregated results of an entire test suite, which should prove useful.

2.7 Probes

This section describes the Probes Framework in Simics.

Simics' Probes framework provides a generic mechanism for measuring counters and other statistics in Simics. The probe measurement points are distributed across the entire simulated system.

Probes can be related to target processors, device models, the host system (where Simics runs), instrumentation tools etc. There are also probes which present sums of other probes in the system on cell or global level.

Currently, most probes have been added for the purpose of measuring and understanding the performance of Simics. Additional probes could focus more on the target hardware and software aspects.

A probe offers a simple way to extract the value, and also contains type information and formatting hints on the probe itself to present the value of the probe, in an uniform and user-readable format.

A probe is identified by a probe-kind, which is a string with a hierarchical namespaces separated with dots (.). For example, cpu.cycles would be found in each processor model in the system and represents how many cycles that has elapsed on the specific processor. The cell.cycles probes are related to the cell objects and this returns the sum of executed cycles of processors which are located under the specific cell. Finally, sim.cycles is not related to any object but form a singleton sum of all the processor's cycles elapsed.

Probes can also be derived from other probes, offering some calculated metric. For example sim.slowdown is calculated as the fraction between sim.time.wallclock and sim.time.virtual, providing an indication how fast the virtual time elapsed compared to the real wallclock time on the system which Simics runs on.

Simics itself is aware of all probes that currently exist in the system, allowing commands to list the probes or read out their values from Simics command line interface.

Probes may either be explicitly implemented in the various Simics objects, or can be implicitly created by the probes framework itself. For example, Simics may detect that a new object is created that implements the cycle interface and then automatically create probes for accessing the new object's cycle information.

2.7.1 Probes Command Set

To look at the available probes, the probe-feature must first be enabled, which can be done with the enable-probes command. When the feature is enabled all probes in the system are detected and/or implicitly created. Also, any further creation or deletion of Simics objects is monitored, keeping the list of known probes in sync with the simulated system.

When the probes framework is enabled, a singleton object called probes is created, which holds all commands to examine probes. For example the probes.list-kinds command gives an overview of the probes that is available in the system, and the amount of objects for each probe-kind. See figure 22 for some examples.

./simics qsp-x86/firststeps machine:hardware:processor:num_cores=2
simics> enable-probes
simics> probes.list-kinds
┌─────────┬───────────────────────────────────┬──────────────────────────┬───┬────────────────────────────────────────┐
│  Row #  │            Probe Kind             │       Display Name       │Num│              Description               │
├─────────┼───────────────────────────────────┼──────────────────────────┼───┼────────────────────────────────────────┤
│        1│cell.cycles                        │Cycles                    │  1│Total number of expired cycles in all   │
│         │                                   │                          │   │processors in a specific cell           │
├─────────┼───────────────────────────────────┼──────────────────────────┼───┼────────────────────────────────────────┤
│        2│cell.esteps                        │Esteps                    │  1│Total number of executed steps          │
│         │                                   │                          │   │(~instructions) on all processors in a  │
│         │                                   │                          │   │specific cell, ignoring any halt steps  │
├─────────┼───────────────────────────────────┼──────────────────────────┼───┼────────────────────────────────────────┤
│        3│cell.event.cycle.histogram         │Cycle Event Histogram     │  1│Histogram of all cycle events triggered │
│         │                                   │                          │   │in a cell.                              │
├─────────┼───────────────────────────────────┼──────────────────────────┼───┼────────────────────────────────────────┤
│        4│cell.event.cycle.intensity         │Cycle Event Intensity     │  1│Number of cycles per event in a cell.   │
├─────────┼───────────────────────────────────┼──────────────────────────┼───┼────────────────────────────────────────┤
...
simics> probes.list-kinds probe-kind = cpu.  categories = instructions -active -probe-type
┌─────┬────────────────┬────────────┬───┬────────┬────────────────────────────────────────────────────────────────────┐
│Row #│   Probe Kind   │Display Name│Num│  Type  │                            Description                             │
├─────┼────────────────┼────────────┼───┼────────┼────────────────────────────────────────────────────────────────────┤
│    1│cpu.esteps      │Esteps      │  2│int     │Number of executed steps (~instructions) on this processor, ignoring│
│     │                │            │   │        │any halt steps                                                      │
├─────┼────────────────┼────────────┼───┼────────┼────────────────────────────────────────────────────────────────────┤
│    2│cpu.load_percent│Sim Load%   │  2│fraction│Calculated load of a processor, that is, instruction per cycles,    │
│     │                │            │   │        │shown as percent: esteps / cycles. 0% is reported if the processor  │
│     │                │            │   │        │is entirely idle and 100% when the processor executes instructions  │
│     │                │            │   │        │for all cycles. The value is also adjusted with the current step-   │
│     │                │            │   │        │rate of a processor, if this exists. Note that if the step-rate is  │
│     │                │            │   │        │dynamically changed during execution, the result will be incorrect  │
│     │                │            │   │        │and 0 / 0 is returned.                                              │
├─────┼────────────────┼────────────┼───┼────────┼────────────────────────────────────────────────────────────────────┤
│    3│cpu.steps       │Steps       │  2│int     │Number of steps (~instructions) consumed by this processor,         │
│     │                │            │   │        │including any halt steps                                            │
├─────┼────────────────┼────────────┼───┼────────┼────────────────────────────────────────────────────────────────────┤
│Sum  │                │            │  6│        │                                                                    │
└─────┴────────────────┴────────────┴───┴────────┴────────────────────────────────────────────────────────────────────┘
simics> probes.list-kinds -objects probe-kind = cpu.steps
┌─────┬──────────┬────────────┬───┬────────────────────────────────────────┬─────────────────────────────────────────┐
│Row #│Probe Kind│Display Name│Num│                Objects                 │               Description               │
├─────┼──────────┼────────────┼───┼────────────────────────────────────────┼─────────────────────────────────────────┤
│    1│cpu.steps │Steps       │  2│board.mb.cpu0.core[0][0], board.mb.cpu0.│Number of steps (~instructions) consumed │
│     │          │            │   │core[1][0]                              │by this processor, including any halt    │
│     │          │            │   │                                        │steps                                    │
├─────┼──────────┼────────────┼───┼────────────────────────────────────────┼─────────────────────────────────────────┤
│Sum  │          │            │  2│                                        │                                         │
└─────┴──────────┴────────────┴───┴────────────────────────────────────────┴─────────────────────────────────────────┘
Figure 22. Example of probe.list-kinds commands

The probes.read allows one or several probes values to be read out and displayed. The command can also be used in in Simics scripts to assign cli-variables. If a single probe is read, its value will be returned, while using probe-kind will return a list of tuples [(probe, value)*] that was read out.

See figure 23 for some examples.

simics> disable-vmp
simics> run-seconds 1
simics> probes.read probe-kind = cpu. -active
┌─────┬────────────────────────────────────────────────────────────┬─────────────┐
│Row #│                           Probe                            │        Value│
├─────┼────────────────────────────────────────────────────────────┼─────────────┤
│    1│board.mb.cpu0.core[0][0]:cpu.counter.x86.exception          │            0│
│    2│board.mb.cpu0.core[0][0]:cpu.counter.x86.exception_intensity│            -│
│    3│board.mb.cpu0.core[0][0]:cpu.counter.x86.interrupt          │           78│
│    4│board.mb.cpu0.core[0][0]:cpu.counter.x86.interrupt_intensity│   6444230.13│
│    5│board.mb.cpu0.core[0][0]:cpu.cycles                         │   2000000000│
│    6│board.mb.cpu0.core[0][0]:cpu.disabled_reason                │             │
│    7│board.mb.cpu0.core[0][0]:cpu.esteps                         │    502649950│
│    8│board.mb.cpu0.core[0][0]:cpu.exec_mode.hypersim_steps       │            0│
│    9│board.mb.cpu0.core[0][0]:cpu.load_percent                   │          25%│
│   10│board.mb.cpu0.core[0][0]:cpu.steps                          │    502649950│
│   11│board.mb.cpu0.core[0][0]:cpu.time.virtual                   │  00:00:01.00│
│   12│board.mb.cpu0.core[0][0]:cpu.time.virtual_ps                │1000000000000│
│   13│board.mb.cpu0.core[0][0]:cpu.vmp.vmexits.histogram          │             │
│   14│board.mb.cpu0.core[0][0]:cpu.vmp.vmexits.total              │            0│
│   15│board.mb.cpu0.core[0][0]:cpu.vmp.vmrets.total               │            0│
│   16│board.mb.cpu0.core[1][0]:cpu.counter.x86.exception          │            0│
│   17│board.mb.cpu0.core[1][0]:cpu.counter.x86.exception_intensity│            -│
│   18│board.mb.cpu0.core[1][0]:cpu.counter.x86.interrupt          │            0│
│   19│board.mb.cpu0.core[1][0]:cpu.counter.x86.interrupt_intensity│            -│
│   20│board.mb.cpu0.core[1][0]:cpu.cycles                         │   2000000000│
│   21│board.mb.cpu0.core[1][0]:cpu.disabled_reason                │          HLT│
│   22│board.mb.cpu0.core[1][0]:cpu.esteps                         │       108585│
│   23│board.mb.cpu0.core[1][0]:cpu.exec_mode.hypersim_steps       │            0│
│   24│board.mb.cpu0.core[1][0]:cpu.load_percent                   │           0%│
│   25│board.mb.cpu0.core[1][0]:cpu.steps                          │       108585│
│   26│board.mb.cpu0.core[1][0]:cpu.time.virtual                   │  00:00:01.00│
│   27│board.mb.cpu0.core[1][0]:cpu.time.virtual_ps                │1000000000000│
│   28│board.mb.cpu0.core[1][0]:cpu.vmp.vmexits.histogram          │             │
│   29│board.mb.cpu0.core[1][0]:cpu.vmp.vmexits.total              │            0│
│   30│board.mb.cpu0.core[1][0]:cpu.vmp.vmrets.total               │            0│
└─────┴────────────────────────────────────────────────────────────┴─────────────┘
simics> probes.read probe-kind = cpu.load_percent

┌─────┬─────────────────────────────────────────┬─────┐
│Row #│                  Probe                  │Value│
├─────┼─────────────────────────────────────────┼─────┤
│    1│board.mb.cpu0.core[0][0]:cpu.load_percent│  25%│
│    2│board.mb.cpu0.core[1][0]:cpu.load_percent│   0%│
└─────┴─────────────────────────────────────────┴─────┘
simics> echo (probes.read probe-kind = cpu.load_percent)
[["board.mb.cpu0.core[0][0]:cpu.load_percent", "25%"], ["board.mb.cpu0.core[1][0]:cpu.load_percent", "0%"]]
simics> echo (probes.read probe-kind = cpu.load_percent -values)

[["board.mb.cpu0.core[0][0]:cpu.load_percent", 0.251324975], ["board.mb.cpu0.core[1][0]:cpu.load_percent", 5.42925e-05]]
simics> echo (probes.read probe = board.mb.cpu0.core[0][0]:cpu.load_percent -values)

0.251324975
Figure 23. Examples of probes.list-details

Some probes are not active by default, typically because there could be overhead in maintaining their values. To activate a probe the probes.subscribe can be used. As long as there are at least one subscriber on a probe, it will be active, and when the number of subscribers goes down to zero, the probe becomes deactivated again.

Figure 24 shows some examples where probes first need to be subscribed in order to be read later.


simics> probes.subscribe probe-kind = sim.mips
simics> probes.subscribe probe-kind = sim.module_profile
simics> run-seconds 10
simics> probes.read  probe = sim:sim.module_profile

x86-intel64.so                          94   45%
x86-p4e JIT                             32   15%
libsimics-common.so                     19    9%
libc.so.6                               16    8%
ICH10.so                                 8    4%
libwx_gtk2u_core-3.0.so.0.4.0            8    4%
libX11.so.6.4.0                          6    3%
libpython3.7m.so.1.0                     5    2%
x86_tlb.so                               4    2%
libvtutils.so                            4    2%
X58-devices.so                           3    1%
_core.cpython-37m-x86_64-linux-gnu.so    3    1%
pcie-downstream-port.so                  2    1%
siplib.cpython-37m-x86_64-linux-gnu.so   1    0%
libgdk-x11-2.0.so.0.2400.33              1    0%
libwx_baseu-3.0.so.0.4.0                 1    0%
simics> probes.read probe-kind = sim.mips
┌─────┬────────────┬────────┐
│Row #│   Probe    │   Value│
├─────┼────────────┼────────┤
│    1│sim:sim.mips│608.72 M│
└─────┴────────────┴────────┘
simics> probes.read probe-kind = sim.mips -values
┌─────┬────────────┬────────────┐
│Row #│   Probe    │       Value│
├─────┼────────────┼────────────┤
│    1│sim:sim.mips│608721207.89│
└─────┴────────────┴────────────┘
simics> probes.read probe-kind = sim.mips -raw-values
┌─────┬────────────┬─────────────────────────┐
│Row #│   Probe    │                    Value│
├─────┼────────────┼─────────────────────────┤
│    1│sim:sim.mips│      7.35e+08 / 1.21e+00│
│     │            │= 6.09e+08 (608721207.89)│
└─────┴────────────┴─────────────────────────┘
Figure 24. Examples of probes.read

Default the read command returns a string formatted in a user-readable format, according to the probe's properties. To get the actual value of the probe the -values switch can be used,

The -raw-value flag returns internal representation of advanced types. This can be useful for understanding the source-values for a calculated value.

2.7.2 Probe Samplers

Probe samplers are a group of tools which use sampling during the simulation, to read user-configurable probes and displays their values. This captures deviations of the probes during the simulation, compared to just looking at the end result when the simulation is finished.

Probe samplers are a group of tools which read user-configurable probes during simulation, through sampling. This captures deviations of the probes during the simulation, compared to just looking at the end result when the simulation is finished.

By default, added probes are all sampled and printed out during run-time with one row representing each sample, each column representing a particular probe.

With many probes sampled, the table can become very wide, and hard to read. To avoid this, a probe can be added with special flags:

The result of the measurements can be saved to csv or json formats. There are also plot capabilities built in the tools when connected to Simics-client.

There are three different samplers, with very similar command sets. To create one of these samplers, the new-probe-monitor, new-probe-streamer or new-system-perfmeter is used. Since the command interface for these tools are largely identical, the probe-monitor chapter explains details which are generally applicable.

2.7.2.1 Probe Monitor

The probe-monitor tool offers a way to read out and present the probe values in continuous samples, while the simulation is running.

All probes being monitored will be presented in a table row for each sample taken, allowing detection of abnormal values during the simulation.

It is possible to add lots of probes to the same probe-monitor, but as the number of probes increase, so does the width of the produced table. Therefore, it can be useful to create several probe-monitors, each showing some group of related feature.

To create a probe-monitor use the new-probe-monitor command. This will create a new object such as pm0 which has some additional commands. See figure 25 for an example.

$> ./simics qsp-x86/firststeps machine:hardware:processor:num_cores=2
simics> new-probe-monitor sampling-mode = realtime interval = 1 -summary
simics> pm0.add-probe probe-kind = sim.time.wallclock sim.time.virtual mode = session
simics> pm0.add-probe probe-kind = sim.time.schedule sim.slowdown sim.esteps sim.mips cpu.esteps cpu.time.schedule cpu.schedule_percent cpu.mips
simics> run-seconds 60

┌──────────────────────────────────────────────────────────────┬──────────────────────────────────────┬──────────────────────────────────────┐
│                                                              │       board.mb.cpu0.core[0][0]       │       board.mb.cpu0.core[1][0]       │
├──────────┬──────────┬─────────┬────────┬────────────┬────────┼────────────┬─────────┬──────┬────────┼────────────┬─────────┬──────┬────────┤
│ Session  │ Session  │Sched (s)│Slowdown│   Esteps   │  IPS   │   Esteps   │Sched (s)│Sched%│  IPS   │   Esteps   │Sched (s)│Sched%│  IPS   │
│Wallclock │ Virtual- │         │        │            │        │            │         │      │        │            │         │      │        │
│(hh:mm:ss)│   Time   │         │        │            │        │            │         │      │        │            │         │      │        │
│          │(hh:mm:ss)│         │        │            │        │            │         │      │        │            │         │      │        │
├──────────┼──────────┼─────────┼────────┼────────────┼────────┼────────────┼─────────┼──────┼────────┼────────────┼─────────┼──────┼────────┤
│  00:00:01│  00:00:01│311.92 ms│    0.59│   462112845│872.65 M│   462021371│297.54 ms│   95%│  1.55 G│       91474│ 14.38 ms│    5%│  6.36 M│
│  00:00:02│  00:00:04│445.62 ms│    0.29│   252202461│251.80 M│   252171406│432.12 ms│   97%│583.56 M│       31055│ 13.50 ms│    3%│  2.30 M│
│  00:00:03│  00:00:22│999.63 ms│    0.06│    54925316│ 54.86 M│    54925316│959.26 ms│   96%│ 57.26 M│           0│ 40.37 ms│    4%│  0.00  │
│  00:00:04│  00:00:40│995.50 ms│    0.06│    55920137│ 55.86 M│    55920137│955.12 ms│   96%│ 58.55 M│           0│ 40.38 ms│    4%│  0.00  │
│  00:00:05│  00:00:54│997.73 ms│    0.07│   798812869│798.45 M│   798792570│964.31 ms│   97%│828.36 M│       20299│ 33.42 ms│    3%│607.45 k│
│  00:00:06│  00:00:55│998.69 ms│    0.61│  3001496016│  3.00 G│  2927954125│941.13 ms│   94%│  3.11 G│    73541891│ 57.56 ms│    6%│  1.28 G│
│  00:00:07│  00:00:59│966.83 ms│    0.33│   548000049│547.45 M│   184324157│491.32 ms│   51%│375.16 M│   363675892│475.51 ms│   49%│764.82 M│
│  00:00:08│  00:00:59│994.97 ms│    2.38│  1178737071│  1.18 G│   643116292│395.46 ms│   40%│  1.63 G│   535620779│599.52 ms│   60%│893.42 M│
│  00:00:09│  00:01:00│998.21 ms│    1.30│  2418507935│  2.42 G│  1159903342│565.55 ms│   57%│  2.05 G│  1258604593│432.66 ms│   43%│  2.91 G│
Figure 25. Sample probe-monitor session

Here, first a probe-monitor is created, that gets the name pm0 by default. The probe-monitor samples the probes each wallclock seconds, which is controlled by the mode and interval arguments.

The -summary flag means all probe values will be displayed when the simulation stops, providing the session or current value for the probes subscribed to, described later.

Next, the pm0.add-probe is issued with two probe-kinds using the mode session. If mode is not specified, it defaults to delta meaning the value presented for the probe is the difference from the previous sample. That is, it shows the value for the sample only. With the session mode, it instead shows the total value since the probe was added to the monitor. In this case, the accumulated times, both in host wall-clock and the virtual time of the simulated system is displayed.

Apart from session and delta there is also the current mode which can be selected, which displays the plain probe value, without any adjustments. The current mode is interesting for some probes, which are not monotonically increasing, such as a temperature. It can also be interesting to be used for probes derived from checkpointed state, for example the global virtual time, when the user wants to see the absolute time.

Finally, before the execution is started we add more probes, now using the default delta mode. Here we are mixing some singleton probes sim.* and some processor specific probes cpu.*. Since there are two processors in the simulated system used in this example, the number of all monitored probes is 14.

When the simulation is started, the probe-monitor starts to print out table rows for the probes being measured, one second between each sample. Probes which have an object associated will be sorted under that object, so here probes for the two processors have an additional heading showing where the probes belong to.

Some more detailed information on each column or probe, that was monitored in this example.

When the simulation is finished, the probe-monitor shows a table of all the probes that are monitored. The table provides the current value for all probes, see figure 26 for the example output.

┌─────┬────────────┬────────────────────┬────────────────────────┬───────────────────────┐
│Row #│Display Name│     Probe-kind     │         Object         │Session Formatted Value│
├─────┼────────────┼────────────────────┼────────────────────────┼───────────────────────┤
│    1│Wallclock   │sim.time.wallclock  │sim                     │            00:00:09.09│
│    2│Virtual-Time│sim.time.virtual    │sim                     │            00:01:00.00│
│    3│Sched       │sim.time.schedule   │sim                     │            00:00:08.27│
│    4│Slowdown    │sim.slowdown        │sim                     │                   0.15│
│    5│Esteps      │sim.esteps          │sim                     │             9512224930│
│    6│IPS         │sim.mips            │sim                     │                 1.05 G│
│    7│Esteps      │cpu.esteps          │board.mb.cpu0.core[0][0]│             6853454540│
│    8│Sched       │cpu.time.schedule   │board.mb.cpu0.core[0][0]│            00:00:06.43│
│    9│Sched%      │cpu.schedule_percent│board.mb.cpu0.core[0][0]│                    78%│
│   10│IPS         │cpu.mips            │board.mb.cpu0.core[0][0]│                 1.07 G│
│   11│Esteps      │cpu.esteps          │board.mb.cpu0.core[1][0]│             2658770390│
│   12│Sched       │cpu.time.schedule   │board.mb.cpu0.core[1][0]│            00:00:01.84│
│   13│Sched%      │cpu.schedule_percent│board.mb.cpu0.core[1][0]│                    22%│
│   14│IPS         │cpu.mips            │board.mb.cpu0.core[1][0]│                 1.45 G│
└─────┴────────────┴────────────────────┴────────────────────────┴───────────────────────┘
Figure 26. Probe-Monitor Summary Printout

The Session Formatted Value represents the probe value formatted accordingly to the probe properties. If probes have been added with the current mode then an additional Current Formatted Value column is also displayed.

It is also possible to get the same results printed out with the pm0.summary command, where it is possible to request more floating point decimals to be presented with the float-decimals argument.

The pm0.status shows the settings of the probe-monitor, including which probes that are currently monitored. Probes can be removed with the pm0.remove-probe command and it is possible to change the sampling settings of the probe-monitor itself with the pm0.sampling-settings command.

While the probe-monitor prints out the table data while the simulation is running, all data is also saved internally. To show the entire history of all samples, the pm0.print-table can be used and the data can also be saved into a .csv with the pm0.export-table-csv command. Any data in the csv file will be unformatted, allowing easily import in a spreadsheet for further analyses.

2.7.2.2 Probe Streamer

The probe-streamer tool offers a way to read out probe values and dump the samples in a .csv, while the simulation is running.

To create a probe-streamer use the new-probe-streamer command. This will create a new object such as ps0 which has some additional commands. Most of these commands are common to the probe-monitor. See figure 27 for an example.

$> ./simics qsp-x86/firststeps machine:hardware:processor:num_cores=2
simics> new-probe-streamer mode = realtime interval = 1 csv-file-output=stream.csv

2.7.2.3 Probe Based system-perfmeter

This tool replaces the old system-perfmeter tool, which had similar capabilities but more monolithic, with all measurement, calculations and output formatting part of the tool itself. Another difference is that the old system-perfmeter was a singleton object, you could not have multiple tools measuring different things.

The new-system-perfmeter extends the new-probe-monitor, with some added features making it easier to use when measuring Simics performance:

See figure 28 for an example.

$> ./simics qsp-x86/firststeps machine:hardware:processor:num_cores=2
simics> new-system-perfmeter -summary

Using board.mb.cpu0.core[0][0] as clock
Enabling probes
[probes info] Enabling probes (could take a while)
[probes info] Found 330 probes using 141 different probe-kinds
[probes info] Additional probe-related command now exists under the 'probes' singleton object
[sp0 info] Added: sim:sim.time.virtual with mode session
[sp0 info] Added: sim:sim.time.wallclock with mode session
[sp0 info] Added: sim:sim.time.virtual with mode delta
[sp0 info] Added: sim:sim.time.wallclock with mode delta
[sp0 info] Added: sim:sim.slowdown with mode delta
[sp0 info] Added: sim:sim.process.cpu_percent with mode delta
[sp0 info] Added: sim:sim.load_percent with mode delta
Created sp0
simics> run-seconds 20
┌──────────┬──────────┬─────────┬─────────┬────────┬────┬────┐
│ Session  │ Session  │Virtual- │Wallclock│Slowdown│Host│Sim │
│ Virtual- │Wallclock │Time (s) │   (s)   │        │CPU%│Load│
│   Time   │(hh:mm:ss)│         │         │        │    │ %  │
│(hh:mm:ss)│          │         │         │        │    │    │
├──────────┼──────────┼─────────┼─────────┼────────┼────┼────┤
│  00:00:00│  00:00:00│479.60 ms│215.28 ms│    0.45│118%│ 22%│
│  00:00:02│  00:00:01│  1.91 s │  1.00 s │    0.52│ 57%│  3%│
│  00:00:19│  00:00:02│ 16.47 s │999.54 ms│    0.06│114%│  0%│
┌─────┬────────────┬───────────────────────┬──────┬───────────────────────┐
│Row #│Display Name│      Probe-kind       │Object│Session Formatted Value│
├─────┼────────────┼───────────────────────┼──────┼───────────────────────┤
│    1│Virtual-Time│sim.time.virtual       │sim   │            00:00:30.00│
│    2│Wallclock   │sim.time.wallclock     │sim   │            00:00:02.83│
│    3│Slowdown    │sim.slowdown           │sim   │                   0.09│
│    4│Host CPU%   │sim.process.cpu_percent│sim   │                    93%│
│    5│Sim Load%   │sim.load_percent       │sim   │                     1%│
└─────┴────────────┴───────────────────────┴──────┴───────────────────────┘
simics> quit

$> ./simics qsp-x86/firststeps machine:hardware:processor:num_cores=2
simics> new-system-perfmeter -summary -cpu-exec-modes

Using board.mb.cpu0.core[0][0] as clock
Enabling probes
[probes info] Enabling probes (could take a while)
[probes info] Found 330 probes using 141 different probe-kinds
[probes info] Additional probe-related command now exists under the 'probes' singleton object
[sp0 info] Added: sim:sim.time.virtual with mode session
[sp0 info] Added: sim:sim.time.wallclock with mode session
[sp0 info] Added: sim:sim.time.virtual with mode delta
[sp0 info] Added: sim:sim.time.wallclock with mode delta
[sp0 info] Added: sim:sim.slowdown with mode delta
[sp0 info] Added: sim:sim.process.cpu_percent with mode delta
[sp0 info] Added: sim:sim.load_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[0][0]:cpu.exec_mode.hypersim_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[1][0]:cpu.exec_mode.hypersim_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[0][0]:cpu.exec_mode.vmp_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[1][0]:cpu.exec_mode.vmp_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[0][0]:cpu.exec_mode.jit_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[1][0]:cpu.exec_mode.jit_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[0][0]:cpu.exec_mode.interpreter_percent with mode delta
[sp0 info] Added: board.mb.cpu0.core[1][0]:cpu.exec_mode.interpreter_percent with mode delta
Created sp0
simics> run-seconds 30
┌────────────────────────────────────────────────────────────┬───────────────────┬───────────────────┐
│                                                            │  board.mb.cpu0.   │  board.mb.cpu0.   │
│                                                            │    core[0][0]     │    core[1][0]     │
├──────────┬──────────┬─────────┬─────────┬────────┬────┬────┼────┬────┬────┬────┼────┬────┬────┬────┤
│ Session  │ Session  │Virtual- │Wallclock│Slowdown│Host│Sim │HYP%│VMP%│JIT%│INT%│HYP%│VMP%│JIT%│INT%│
│ Virtual- │Wallclock │Time (s) │   (s)   │        │CPU%│Load│    │    │    │    │    │    │    │    │
│   Time   │(hh:mm:ss)│         │         │        │    │ %  │    │    │    │    │    │    │    │    │
│(hh:mm:ss)│          │         │         │        │    │    │    │    │    │    │    │    │    │    │
├──────────┼──────────┼─────────┼─────────┼────────┼────┼────┼────┼────┼────┼────┼────┼────┼────┼────┤
│  00:00:01│  00:00:00│508.60 ms│390.61 ms│    0.77│ 71%│ 21%│  0%│100%│  0%│  0%│  0%│ 63%│  4%│ 33%│
│  00:00:03│  00:00:01│  2.36 s │  1.00 s │    0.42│ 71%│  3%│  0%│ 99%│  1%│  0%│  0%│ 40%│ 25%│ 35%│
│  00:00:21│  00:00:02│ 17.71 s │  1.00 s │    0.06│113%│  0%│  0%│ 98%│  1%│  1%│-   │-   │-   │-   │
┌─────┬────────────┬─────────────────────────────────┬────────────────────────┬───────────────────────┐
│Row #│Display Name│           Probe-kind            │         Object         │Session Formatted Value│
├─────┼────────────┼─────────────────────────────────┼────────────────────────┼───────────────────────┤
│    1│Virtual-Time│sim.time.virtual                 │sim                     │            00:00:30.00│
│    2│Wallclock   │sim.time.wallclock               │sim                     │            00:00:02.92│
│    3│Slowdown    │sim.slowdown                     │sim                     │                   0.10│
│    4│Host CPU%   │sim.process.cpu_percent          │sim                     │                    92%│
│    5│Sim Load%   │sim.load_percent                 │sim                     │                     1%│
│    6│HYP%        │cpu.exec_mode.hypersim_percent   │board.mb.cpu0.core[0][0]│                     0%│
│    7│VMP%        │cpu.exec_mode.vmp_percent        │board.mb.cpu0.core[0][0]│                    99%│
│    8│JIT%        │cpu.exec_mode.jit_percent        │board.mb.cpu0.core[0][0]│                     0%│
│    9│INT%        │cpu.exec_mode.interpreter_percent│board.mb.cpu0.core[0][0]│                     0%│
│   10│HYP%        │cpu.exec_mode.hypersim_percent   │board.mb.cpu0.core[1][0]│                     0%│
│   11│VMP%        │cpu.exec_mode.vmp_percent        │board.mb.cpu0.core[1][0]│                    52%│
│   12│JIT%        │cpu.exec_mode.jit_percent        │board.mb.cpu0.core[1][0]│                    14%│
│   13│INT%        │cpu.exec_mode.interpreter_percent│board.mb.cpu0.core[1][0]│                    34%│
└─────┴────────────┴─────────────────────────────────┴────────────────────────┴───────────────────────┘

In the first run, only the default probes are used. The -summary switch automatically prints the value of the collected probes when Simics stops. In the second run, the -cpu-exec-modes is added, causing 8 additional probes to be monitored.

3 Low-Level Debugging

3.1 Introduction

This part of the document explains how to use Simics as a tool in low-level software development. In this setting, Simics plays two roles:

Simics Analyzer provides a full symbolic debugger for C and C++. This is described in the Simics Analyzer User's Guide. The debugger in Simics Analyzer is also capable of tracking software running on target machines, to give you the ability to debug individual processes, threads and tasks. Also, Simics provides some debugging capabilities.

Using Simics as a debugger has some major benefits compared to debugging on real hardware:

It is recommended that you have read Getting Started before tackling this document.

3.2 Low-level Debugging with Simics

Simics Analyzer provides a full symbolic C/C++ debugger with a command line interface. You can read all about it in Simics Analyzer User's Guide . This can be used in parallel with the low-level features described in this chapter.

This chapter explains Simics's powerful, but low-level, breakpoint support.

3.2.1 Breakpoints

Simics can set breakpoints on code and data. Unlike most debuggers, Simics breakpoints are not limited by what the hardware can support; for example, there is no restriction on the number of read/write breakpoints (also known as watchpoints).

In Simics you can set breakpoints on, for instance:

Simics is fully deterministic, and breakpoints in Simics are fully non-intrusive. This makes it possible to narrow down the location of difficult bugs by re-running the exact same run as many times as you need.

3.2.1.1 The Breakpoint Manager

Simics has a breakpoint manager which stores information about all breakpoints, of all types. It has commands for listing breakpoints, enabling, deleting and other common operations. Breakpoint creation is specific to the different types, and is described in the following chapters. The breakpoint manager is the object bp.

In general, breakpoint creation commands return an ID, which can be used to later delete the breakpoint, or obtain information about it. The most important generic commands are

3.2.1.2 Memory Breakpoints

A memory breakpoint stops the simulation whenever a memory location in a specified address interval is accessed. The address interval can be of arbitrary length and the type of memory access can be specified as any combination of read, write, and execute.

The easiest way to set memory breakpoints is to use the bp.memory.break command:

simics> output-radix 16
simics> bp.memory.break p:0x10000
Breakpoint 1 set on address 0x10000 in 'board.mb.cpu0.mem[0][0]' with access mode 'x'

Prefix the address with p: or v: to get a physical or virtual address, respectively. As you can see in the following example, Simics defaults to interpreting a breakpoint address as virtual if you do not specify otherwise:

simics> board.mb.cpu0.core[0][0]->current_virtual_context = "board.cell_context"
simics> bp.memory.break v:0x4711
[board.mb.cpu0.core[0][0] info] VMP not engaged. Reason: virtual breakpoint.
Breakpoint 2 set on address 0x4711 in 'board.cell_context' with access mode 'x'
simics> bp.memory.break p:0x4711
Breakpoint 3 set on address 0x4711 in 'board.mb.cpu0.mem[0][0]' with access mode 'x'
simics> bp.memory.break 0x4711
Breakpoint 4 set on address 0x4711 in 'board.cell_context' with access mode 'x'
Note: overlaps with breakpoint 2

This way of setting breakpoints will attach them to the memory space (physical address) or context (virtual address) connected to the current processor. If the current processor is not the processor you are interested in, it can be changed using the pselect command:

simics> pselect board.mb.cpu0.core[0][0]

Without an argument, pselect prints the current processor:

simics> pselect
"board.mb.cpu0.core[0][0]"

Also useful is the cpu object alias, which returns the current processor and can be used to expand child objects, commands and aliases on the current processor.

simics> cpu # type cpu<tab>
cpu->  cpu-pages-dump  cpu-switch-time  cpu.  cpu.vtime.

Physical memory breakpoints are handled by memory space objects. A memory space represents a physical address space; they sit between the processor and the actual hardware devices, for example RAM, that can be accessed with read and write instructions. Breakpoints are created with the memory space's break command:

simics> board.mb.cpu0.core[0][0]->physical_memory
"board.mb.cpu0.mem[0][0]"
simics> board.mb.cpu0.mem[0][0].bp-break-memory address = 0x10000 length = 16 -w
Breakpoint 1 set on address 0x10000 in 'board.mb.cpu0.mem[0][0]', length 16 with access mode 'w'

Virtual memory breakpoints are handled by context objects. A context represents a virtual address space; you can learn more about them in chapter 3.2.2. Essentially, they provide a level of indirection between processors and virtual memory breakpoints; a processor has a current context, which in turn has virtual breakpoints:

simics> board.mb.cpu0.core[0][0]->current_context
"board.cell_context"
simics> board.cell_context.bp-break-memory 0x1ff00
Breakpoint 2 set on address 0x1ff00 in 'board.cell_context' with access mode 'x'

Note that by default, all simulated processors in a cell share one context (celln_context). If you want a virtual breakpoint to apply only to a subset of the processors, create a new context just for them:

simics> new-context foo
simics> board.mb.cpu0.core[0][0].set-context foo
simics> foo.bp-break-memory 0xffffffffbfc008b8

When creating execution breakpoints, it is possible to specify filter rules to only trigger when instructions match certain syntactical criteria. This feature is mainly useful with breakpoints covering large areas of memory. The available parameters to the break command are prefix (to match the start of an instruction), substr (to match a particular substring), and pattern (to match the bit pattern of the instruction).

For example, to stop when an instruction with the name add is executed in a memory range from 0x10000 to 0x12000, use the following commands:

simics> bp.memory.break 0x10000 0x2000 -x prefix = "add"
Breakpoint 1 set on address 0x10000 in 'board.cell_context', length 8192 with access mode 'x'

Simics will now break on the first add instruction encountered. For more information, see the breakpoints section in the Simics Reference Manual or use the help bp.memory.break command.

3.2.1.3 Temporal Breakpoints

Unlike an ordinary debugger, Simics can handle temporal breakpoints, i.e., breakpoints in time. Since the concept of time is based on steps and cycles, to be precise a temporal breakpoint refers to a specific step or a cycle count as measured by a given processor, but one can also specify breakpoints in seconds of virtual time:

simics> bp.cycle.break cpu0 100
simics> bp.step.break cpu0 100
simics> bp.time.break cpu0 1

In the example above, the breakpoints are specified relative to the current time. It is also possible to give temporal breakpoints in absolute time (where 0 refers to the time when the original configuration was set up in Simics).

simics> board.mb.cpu0.core[0][0].bp-break-cycle -absolute 100
simics> board.mb.cpu0.core[0][0].bp-break-step -absolute 100
simics> board.mb.cpu0.core[0][0].bp-break-time -absolute 1

The commands bp.cycle.break, bp.step.break and bp.time.break, can be given without providing the CPU ad argument. This will set a breakpoint for the current processor.

3.2.1.4 Control Register Breakpoints

A control register breakpoint triggers when the selected control register is accessed. The access type is determined by the break command parameters used.

To break on a control register write, use bp.control_register.break. For example, to break when the register cr0 is written to:

simics> bp.control_register.break name = cr0

To break on a control register read, use bp.control_register.break -r. For example, to break when the register cr0 is read:

simics> bp.control_register.break name = cr0 -r

A list of available control registers can be obtained by tab-completing the name argument. See the documentation for bp.control_register.break in the Simics Reference Manual for more information.

3.2.1.5 I/O Breakpoints

An I/O breakpoint is always connected to a specific device object. The breakpoint is triggered when that device is accessed. The breakpoint is set using the break-io command, which take the device name as a parameter. For example, to break on accesses to a device called dma, we would use the following syntax:

simics> break-io device = board.mb.sb.dma

A list of devices can be obtained by tab-completing the device argument.

3.2.1.6 Text Output Breakpoints

Many simulated machines have a text console—a terminal window hooked up to a serial port on the target machine, so that you can type commands to the target and get replies. In particular x86 systems also often have a graphics console—a window acting as a monitor to the machine, hooked up to a graphics device on the target machine, and usually also to keyboard and mouse devices.

A text console can halt the simulation on the occurrence of a given character sequence in the output; this is called a text output breakpoint. This is also possible on a graphics console when it is running in text mode graphics, assuming that the graphics device implements this.

To set a breakpoint, use the command bp.console_string.break console string, which returns a breakpoint id. Simics will stop when string appears in the output.

To find out if a specific simulated machine uses these consoles, look for objects of class textcon or graphcon in the list provided by list-objects once the configuration is loaded.

3.2.1.7 Graphics Breakpoints

If your target machine has a graphical display (as opposed to just a text console), you can set graphical breakpoints on it. A graphical breakpoint is a (small or large) bitmap image and a pair of coordinates; when the pixels at those coordinates on the simulated display exactly match the breakpoint image, the simulation will halt.

Graphics breakpoints can be created from the console GUI window, but the following commands can also be used to save and set breakpoints for a graphics console:

3.2.1.8 Magic Instructions and Magic Breakpoints

For each simulated processor architecture, a special nop (no-operation) instruction has been chosen to be a magic instruction for the simulator. When the simulator executes such an instruction, it triggers a Core_Magic_Instruction hap and calls all the callbacks functions registered on this hap.

Since magic instructions are just no-operation instructions on hardware, you can run code containing magic instructions on hardware as well as in the simulator, but you will not get any of the extra behavior Simics implements for the magic instruction.

If the architecture makes it possible, a magic instruction parameter is encoded as an immediate value in the magic instruction. When the hap is triggered, this value is passed as an argument to the hap handlers. This provides the user with a rudimentary way of passing information from the simulated system to the hap handler.

Magic instructions have to be compiled into the binary files that are executed on the target. The file magic-instruction.h in [simics]/src/include/simics/ defines a MAGIC(n) macro that can be used to place magic instructions in your program, where n is the magic instruction parameter value to encode. Some parameter values are reserved for internal use; see figure 29 and figure 30.

A complete list of magic instructions and the range of the parameter n is provided in figure 29.

The declaration of the macros are heavily dependent on the compiler used, so you may get an error message telling you that your compiler is not supported. In that case, you will have to write the inline assembly corresponding to the magic instruction you want to use. The GCC compiler should always be supported.

The magic instruction macro is directly usable only from C and C++; if your program is written in another language, you will have to call a C function that uses the macro, or an assembly function that includes the magic instruction. (If the language supports inline assembly, that can of course be used as well.) For example, in Java it would be necessary to use the JNI interface. Check your compiler and language documentation for details.

(x86 specific) The magic instruction parameter is passed through the eax register on x86. The eax register contents is preserved when the magic instruction is emulated by Simics. Thus the eax register value can be used just after the magic instruction to extract the magic parameter value from it. The values of the ebx, ecx and edx registers are undefined after the magic instruction. Earlier Simics versions had different definitions of magic instructions for x86. The old magic instruction can still be used on x86 if the processor is not running in VMP mode.

(PowerPC specific) There are two different encodings of the rlwimi-based magic instruction on PowerPC. On 64-bit models, the new encoding is always used; it is also the one generated by the MAGIC() and MAGIC_BREAKPOINT() macros in magic-instruction.h when compiling 64-bit PowerPC code. The old encoding is used on 32-bit models when the old_rlwimi_magic attribute is set. When compiling 32-bit PowerPC code, the macros will use the old encoding unless the preprocessor symbol SIM_NEW_RLWIMI_MAGIC has been defined.

It is recommended that the new encoding is used with 32-bit PowerPC models and code by setting the appropriate attribute and preprocessor symbol.

Target Magic instruction Conditions on n
ARCmov 0, n 1 ≤ n < 64
ARMorr rn, rn, rn 0 ≤ n ≤ 14
ARMv8orr xn, xn, xn 0 ≤ n ≤ 31
ARM Thumb-2orr.w rn, rn, rn 0 ≤ n ≤ 12
H8300brn n -128 ≤ n ≤ 127
M680x0dbt dx,y 0 ≤ n < 0x3ffff
x=n[17:15], y=n[14:0] * 2
MIPSli %zero, n 0 ≤ n < 0x10000
Nios IIor rN, rN, rN, 0 ≤ N < 32
PowerPCrlwimi x,x,0,y,z 0 ≤ n < 8192 new encoding
x=n[12:8], y=n[7:4], z=n[3:0]|16
PowerPCrlwimi x,x,0,y,z 0 ≤ n < 32768 old encoding
x=n[14:10], y=n[9:5], z=n[4:0]
RISC-Vsrai zero, zero, n 0 ≤ n ≤ 31
SHmov rn, rn 0 ≤ n < 16
SPARCsethi n, %g0 1 ≤ n < 0x400000
x86 cpuid 0 ≤ n < 0x10000
with eax = 0x4711 + n * 216
Figure 29. Magic instructions for different Simics Targets
Reserved Magic Numbers
 0
12
Figure 30. Reserved magic numbers, for internal use only.

Here is a simple example of how to use magic instructions:

#include "magic-instruction.h"

int main(int argc, char **argv)
{
        initialize();
        MAGIC(1);                     // tell the simulator to start
                                      // the cache simulation
        do_something_important();
        MAGIC(2);                     // tell the simulator to stop
                                      // the cache simulation
        clean_up();
}

This code needs to be coupled with a callback registered on the magic instruction hap to handle what happens when the simulator encounters a magic instruction with the arguments 1 or 2 (in this example, to start and stop the cache simulation).

Simics implements a special handling of magic instructions called magic breakpoints. A magic breakpoint occurs if magic breakpoints are enabled and if the parameter n of a magic instruction matches a special condition. When a magic breakpoint is triggered, the simulation stops and returns to the prompt.

Magic breakpoints can be enabled with the command bp.magic.break . The condition on n for a magic instruction to be recognized as a magic breakpoint is the following:

n == 0 || (n & 0x3f0000) == 0x40000

Note that the value 0 is included for architectures where no immediate can be specified. The file magic-instruction.h defines a macro called MAGIC_BREAKPOINT that places a magic instruction with a correct parameter value in your program.

3.2.2 Symbolic Debugging

A vital part of a debugger's task is to understand the system being debugged at a higher level than just machine instructions and memory contents. The user thinks in terms of processes, functions, and named variables, so the debugger presents a view of the software that matches these concepts. This view is even more important in Simics, where the user has access to the whole system and not only user processes. To handle this, Simics Analyzer provides a full symbolic debugger, which you can read about in Simics Analyzer User's Guide.

Simics provides only some primitive building blocks for basic debugging like context objects.

3.2.2.1 Contexts

A context object represents a virtual address space. Each processor in the simulated system has a current context, which represents the virtual address space currently visible to code running on the processor. Virtual-address breakpoints are properties of contexts; different context objects have separate sets of virtual breakpoints, and by changing a processor's current context, you change its set of virtual breakpoints.

The correctness of the simulation does not depend on contexts in any way; the concept of multiple virtual address spaces is useful for understanding the simulated software, but not necessary for running it. What contexts to create and how to use them is entirely your business; Simics does not care.

By default, every processor in a simulation cell use the same default context. You may create new contexts and switch between them at any time. This allows you, for example, to maintain separate debugging symbols and breakpoints for different processes in your target machine. When a context is used in this manner (active when and only when a certain simulated process is active), the context is said to follow the process.

Simics Analyzer will help you coordinate contexts for processes running on the target system.

3.3 Using Simics for Hardware Bring-Up and Firmware

Development

Simics makes hardware bring-up, firmware development, and other low-level programming tasks easier in a number of ways:

3.3.1 A Simple Example

It is easy to write a handful of instructions directly to memory, fill the registers with any necessary values, and manually single-step through this little program:

simics> board.mb.cpu0.core[0][0]->cs[7] = 0
simics> board.mb.cpu0.core[0][0]->cs[8] = 0xffffffff
simics> load-file test.bin 0xf000
simics> set-pc 0x10001
simics> %dx = 4711
simics> si
[board.mb.cpu0.core[0][0]] cs:0x0000000000010001 p:0x000010001  mov ax,0x1
simics> si
[board.mb.cpu0.core[0][0]] cs:0x0000000000010004 p:0x000010004  xor ebx,ebx

As always in Simics, this can be scripted if you expect to run it more than once:

run-script targets/qsp-x86/firststeps.simics
board.mb.cpu0.core[0][0]->cs[7] = 0
board.mb.cpu0.core[0][0]->cs[8] = 0xffffffff
load-file test.bin 0xf000
set-pc 0x10001
%dx = 4711
continue 12
expect %dx 4713
expect %pc 0x1001c
quit
$ ./simics test.simics
*** Values differ in expect command: 4711 4713
$

Here, we first call another simics script to set up the machine for us, then run our test case. The expects will cause Simics to exit with an error code (as shown) if the conditions are not met; otherwise, the quit will cause Simics to quit successfully.

3.3.2 Going Further

The simple script in the last section can be extended in several directions:

3.4 Using Simics with GDB

This chapter describes how to use gdb-remote, a Simics module that lets you connect a GDB session running on your host machine to the simulated machine using GDB's remote debugging protocol, and use GDB to debug software running on the target machine.

If you load the gdb-remote module in Simics, you can use the remote debugging feature of GDB, the GNU debugger, to connect one or more GDB processes to Simics over TCP/IP. In order to do this, you need a GDB compiled to support the simulation's target architecture on whichever host you're running. The gdb-remote module has been tested with several different versions of GDB 6 and 7.

A prebuilt GDB is distributed in the Simics GDB (#1031) package. In a Simics project, the bin/gdb command (bin\gdb.bat on Windows) can be used to launch GDB.

The bin/gdb or bin\gdb.bat commands will only work if Simics GDB (#1031) is installed and available as addon package in the Simics project.

To connect a GDB session to Simics, start your Simics session and run the new-gdb-remote command, optionally followed by a TCP/IP port number, which defaults to 9123 otherwise. This will automatically load the gdb-remote module. You can also specify which processor or context the remote debugger should be attached to with the cpu and context arguments.

When a configuration is loaded, Simics will start listening to incoming TCP/IP connections on the specified port. Run the simulated machine up to the point where you want to connect GDB. To inspect a user process or dynamically loaded parts of the kernel, the easiest solution might be to insert magic instructions at carefully chosen points. For static kernel debugging, a simple breakpoint on a suitable address will solve the problem.

When debugging the start-up phase of an operating system, it might happen that gdb gets confused by the machine state and disconnects when you try to connect. In this case, execute a few instructions and try again.

Once Simics is in the desired state, start your GDB session, load any debugging information into it, and then connect it to Simics using the target remote host:port command, where host is the host Simics is running on, and port is the TCP/IP port number as described above. Here is a short sample session using firststeps:


(gdb) symbol-file targets/qsp-x86/images/yocto_linux/yocto-1.8-vmlinux
Reading symbols from targets/qsp-x86/images/yocto_linux/yocto-1.8-vmlinux...done.
(gdb) set endian little
The target is assumed to be little endian
(gdb) target remote localhost:9123
Remote debugging using localhost:9123
0x000000000000fff0 in unicast_sock ()
(gdb) 

Note that yocto-1.8-vmlinux is internal file and not distributed in the package, so the reader cannot run the example in simics.

The GDB supplied with Simics has builtin support for several target architectures. The architecture will be set automatically when GDB connects, but for little-endian architectures (such as x86 and x86_64), the following GDB command has to be issued: set endian little.

From this point, you can use GDB to control the target machine by entering normal GDB commands such as continue, step, stepi, info regs, breakpoint, etc.

GDB does not expect the target state to change while it is in control and has asked for the simulation to be stopped. If the simulation is started from another frontend (e.g., by the continue command from the Simics command line) then GDB will not be notified and will still use cached information about the program location and register values. To recover from such a situation, issue a GDB command that makes the simulation run.

To force Simics to close a connection, use the gdb0.disconnect command.

The gdb-remote module does not have any high-level information about the OS being run inside Simics. This means that in order to examine memory or disassemble code, the data or code you want to look at has to be in the active TLB.

When using gdb-remote with targets supporting multiple address sizes (such as x86-64 and SPARC), you must have a GDB compiled for the larger address size. For SPARC, run GDB's configure script with the --target=sparc64-sun-solaris2.8 option.

3.4.1 Remote GDB and Shared Libraries

It takes some work to figure out how to load symbol tables at the correct offsets for relocatable object modules in GDB. This is done automatically for normal (non-remote) targets, but for the remote target, you have to do it yourself. You need to find out the actual address at which the shared module is mapped in the current context on the simulated machine, and then calculate the offset to use for GDB's add-symbol-file command.

Under Linux, the list of memory mappings can be found in the file /proc/pid/maps (plain text format). The VMA column of the .text line of the output from objdump -h file contains the start address of the text segment.

Using these two values, map address and text address, you should use map address + text address as the offset to add-symbol-file (it has to be done this way to compensate for how GDB handles symbol loading).

To show you how it works, we will work through a simple example. The example uses a simple program with a simple shared library. The program can be found in [qsp-x86]/targets/qsp-x86/images/hello and the shared library is the libgreeter.so file in the same directory. Here and in the rest of this section [qsp-x86] refers to the location where the QSP-x86 package is installed.

Start by booting the firststeps machine. Then mount the host file system and copy the program and shared library onto the machine. This should be done on the target:


~ # mount /host
~ # cp /host/[qsp-x86]/targets/qsp-x86/images/hello .
~ # cp /host/[qsp-x86]/targets/qsp-x86/images/libgreeter.so .

Then run the program in the background. The program will enter the infinite loop in the shared library.

Now we need the map address and the text address of the shared library. To get the map address, look in the process file system to see where it has mapped the shared library:


~ # ./hello &
[1] 13104
~ # cat /proc/13104/maps
00400000-00401000 r-xp 00000000 08:02 9442              /home/root/hello
00600000-00601000 r--p 00000000 08:02 9442              /home/root/hello
00601000-00602000 rw-p 00001000 08:02 9442              /home/root/hello
7f686c5ed000-7f686c786000 r-xp 00000000 08:02 10049     /lib/libc-2.21.so
7f686c786000-7f686c986000 ---p 00199000 08:02 10049     /lib/libc-2.21.so
7f686c986000-7f686c98a000 r--p 00199000 08:02 10049     /lib/libc-2.21.so
7f686c98a000-7f686c98c000 rw-p 0019d000 08:02 10049     /lib/libc-2.21.so
7f686c98c000-7f686c990000 rw-p 00000000 00:00 0 
7f686c990000-7f686c991000 r-xp 00000000 08:02 10247     /home/root/libgreeter.so
7f686c991000-7f686cb90000 ---p 00001000 08:02 10247     /home/root/libgreeter.so
7f686cb90000-7f686cb91000 r--p 00000000 08:02 10247     /home/root/libgreeter.so
7f686cb91000-7f686cb92000 rw-p 00001000 08:02 10247     /home/root/libgreeter.so
7f686cb92000-7f686cbb4000 r-xp 00000000 08:02 9919      /lib/ld-2.21.so
7f686cdad000-7f686cdb0000 rw-p 00000000 00:00 0 
7f686cdb2000-7f686cdb3000 rw-p 00000000 00:00 0 
7f686cdb3000-7f686cdb4000 r--p 00021000 08:02 9919      /lib/ld-2.21.so
7f686cdb4000-7f686cdb5000 rw-p 00022000 08:02 9919      /lib/ld-2.21.so
7f686cdb5000-7f686cdb6000 rw-p 00000000 00:00 0 
7fffde6a7000-7fffde6c8000 rw-p 00000000 00:00 0         [stack]
7fffde7ff000-7fffde800000 r-xp 00000000 00:00 0         [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

From this output you can see that the program is running with PID 13104 and that the map address is 0x7f686c990000. The exact PID may differ, adapt the commands accordingly.

To get the text address we use objdump. This should be run on a host computer with objdump installed:


> objdump -h [qsp-x86]/targets/qsp-x86/images/libgreeter.so
libgreeter.so:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
    :
 10 .text         00000134  00000000000005e0  00000000000005e0  000005e0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
    :

The .text symbols starts at address 0x5e0 and this is what we call the text address, so if we connect GDB to Simics we have to add the symbols with an offset of 0x7f686c990000 + 0x5e0 = 0x7f686c9905e0.

Now we can set up GDB, connect it to Simics, and debug the program:


(gdb) dir [qsp-x86]/targets/qsp-x86/
Source directories searched: [qsp-x86]/targets/qsp-x86:$cdir:$cwd
(gdb) add-symbol-file [qsp-x86]/targets/qsp-x86/images/libgreeter.so 0x7f686c9905e0
add symbol table from file "[qsp-x86]/targets/qsp-x86/images/libgreeter.so" at
	.text_addr = 0x6c9905e0
(y or n) y
Reading symbols from [qsp-x86]/targets/qsp-x86/images/libgreeter.so...done.
(gdb) set endian little
The target is assumed to be little endian
(gdb) target remote localhost:9123
Remote debugging using localhost:9123
greet (name=0x4008eb "World") at ../greeter.c:12
14	        while (loop); /* Loop until the loop variable is reset by gdb */
(gdb)

This is just a toy program written to make it possible to debug it without any OS awareness. Normally you would use the OS awareness functionality described in Simics Analyzer User's Guide to debug user space programs.

3.5 Using Simics with Other IDEs

Simics does not explicitly support IDEs other than those listed in this manual. If you would like to use Simics with such an IDE, there are a few options you could try:

4 Performance

4.1 Simulation Performance

This chapter covers various topics related to Simics performance and what can be done to measure and improve it. It discusses the general performance features provided by Simics. For ways to scale the simulation even further see chapter 4.2.

Simics is a fast simulator utilizing various techniques such as run-time code generation to optimize performance. In some cases Simics can execute code faster than the target system being simulated, while it can also be considerably slower in other cases.

There are four major execution modes Simics uses to execute target instructions: hypersimulation, VMP, JIT and interpreted mode.

Hypersimulation means that Simics detects repetitive work performed by the target code and performs the effects of the code without actually having to run the code. In the most simple case this is a simple idle loop, but it can also be applied to more complex examples such as spin-locks and device polling. This is the fastest execution mode.

VMP, which is a part of Simics's x86 models, utilizes the virtualization capabilities of modern processors to run target instructions directly. This typically results in high simulation performance, but the host and target needs have the same instruction set, and you have to do special set up to enable it. VMP is currently only supported on x86 hosts.

JIT mode uses run-time code generation to translate blocks of the target instructions into blocks of host instructions. JIT mode is when Simics runs such translated blocks. This mode is supported by most target processor models in Simics.

Interpreted mode interprets the target instructions one by one. This mode is the slowest, but it is always available.

There are basically two ways to measure Simics performance:

In most cases the user is mostly interested in the first. Simics should execute instructions as fast as possible to finish the workload in shortest possible time. However, since Simics is a full system simulator, it is also important that the virtual time on the simulated machine advances quickly. That is important in cases where a program or operating system is waiting on a timer to expire or an interrupt from a device in order to proceed with the workload.

If we divide the wall-clock time on the host that Simics executes on, with the elapsed virtual time on the target machine, we get a slowdown number.

slowdown = Timehost/Timevirtual

A slowdown number of 2.3 means that Simics performance is 2.3 times slower than the system it simulates. A slowdown value of less than 1.0 means that Simics manages to execute the corresponding code faster than the system it simulates. The slowdown depends on various factors:

The default time model in Simics is that each target instruction takes one target cycle to execute. That is the default, Instructions Per Cycle (IPC) is 1.0. This is a simplification (but in many cases an adequate approximation) compared to the actual time it takes on the real hardware to execute instructions. It is possible to change the IPC number using the <cpu>.set-step-rate command. For example:

simics> board.mb.cpu0.core[0][0].set-step-rate ipc = 1.5
Setting step rate to 3/2 steps/cycle
simics> board.mb.cpu0.core[0][0].set-step-rate ipc = 0.5
Setting step rate to 1/2 steps/cycle

In the first example, IPC of 1.5 means that Simics needs to execute 3 instructions for 2 cycles to elapse. In the second example, for each instruction executed two cycles elapse. Thus, with a lower IPC value, virtual time will progress faster and simulation slowdown will decrease.

Note that there is nothing wrong in changing the default IPC when it comes to the accuracy of the simulation. In many cases, the IPC observed for a given benchmark is much lower than the 1.0 that Simics assumes, and matching it will both make the simulation closer to the real hardware and improve the simulation speed, at least in virtual time. Simulations that profits most from this change are simulations involving devices and long memory latencies.

4.1.1 Measuring Performance

The system-perfmeter extension can be used to understand the performance you get out of Simics. The system-perfmeter is sample based, which means that you can see the performance during the workload execution, and how it varies, not only the end result when a workload is finished.

The easiest way to try it out is simply to issue the system-perfmeter command without any additional arguments:

simics> system-perfmeter

This will cause a sample to be taken every 1.0 virtual seconds. For each sample the system-perfmeter extracts various counters from Simics and displays the delta since last time. The output can look like this:

simics> c
SystemPerf: Total-vt Total-rt Sample-vt Sample-rt Slowdown  CPU Idle
SystemPerf: -------- -------- --------- --------- -------- ---- ----
SystemPerf:     1.0s     6.0s     1.00s     5.99s     5.99  97%   0%
SystemPerf:     2.0s     6.7s     1.00s     0.69s     0.69  97%   0%
SystemPerf:     3.0s     8.0s     1.00s     1.34s     1.34  92%   0%
SystemPerf:     4.0s     8.4s     1.00s     0.42s     0.42 100%   0%
SystemPerf:     5.0s     9.2s     1.00s     0.78s     0.78  98%  14%
SystemPerf:     6.0s    10.5s     1.00s     1.31s     1.31  96%  55%
SystemPerf:     7.0s    10.7s     1.00s     0.12s     0.12  92%  93%
SystemPerf:     8.0s    10.7s     1.00s     0.00s     0.00 100% 100%

Here we can see the execution for the first 8 virtual seconds and the corresponding performance measured in each second sample. To simulate these 8 virtual seconds, it took Simics 10.7 host seconds, thus the average slowdown is 1.34.

The CPU column shows how much of the host CPU that Simics has used, allowing you to notice if there is another process consuming the host CPU resources. Another reason for CPU utilization to be low can be that Simics itself is running in real-time mode where Simics sleeps so that virtual time does not race ahead of host time.

When an 'idle' condition has been detected (see chapter 4.1.4.1), the total idleness of the system is reported in the Idle column. If the simulated system consists of multiple processors and you wish to see how much each processor is idling you can use the -cpu-idle switch to system-perfmeter. Note that idling is defined by the simulator, not by the target architecture (see chapter 4.1.4.1). With the -cpu-exec-mode, information is also gathered and printed on how simulation steps are executed in the CPU model. The fastest mode to be executing in is idle, followed by VMP, JIT, and interpreter.

The system-perfmeter can also be used to get an understanding of which processor that takes the longest time to simulate. The -cpu-host-ticks switch adds extra columns per CPU for this. For example:

SystemPerf: Total-vt Total-rt Sample-vt Sample-rt Slowdown  CPU Idle [   0   1   2   3 ]
SystemPerf: -------- -------- --------- --------- -------- ---- ----   --- --- --- ---
SystemPerf:     1.0s    15.6s     1.00s    15.63s     15.6  99%  74% [  71  11  11   8 ]
SystemPerf:     2.0s    32.6s     1.00s    16.97s     17.0  98%  72% [  70  11  10   9 ]

Here we have a 4 CPU system which is idle roughly 70% and the last columns show that CPU0 takes 70% of the time to simulate, while the other three about 10% each. Hence CPU0 is working while the other CPUs are idling.

The -module-profile flag enables live profiling of the main Simics thread. The profiling is sample based, and any sample hitting in code produced dynamically by a JIT engine will be reported as "classname JIT". The -module-profile data is not printed in the standard line print mode, so you must use either -top or -summary to get profiling information.

Figure 31. Annotated output from system-perfmeter -top -module-profile -cpu-exec-mode

Figure 31 explains the output of system-perfmeter. There are many other options to the system-perfmeter command, please read the associated help text for details.

simics> help system-perfmeter

4.1.2 Multithreaded Simulation Profiling and Tuning

With Simics Accelerator, simulation performance can be increased by utilizing host system parallelism. Simics exploits available target system parallelism and spreads the load over available host cores, in order to increase performance.

Simics Accelerator has two different mechanisms that can operate alone or work together to improve performance. The first is Simics® Multimachine Accelerator which is based upon the cell concept. The other mechanism is Multicore Accelerator which can parallelize simulation even within cells.

A cell ideally contains tightly coupled parts of the target system (typically one or more CPUs and associated devices). Different cells can be simulated in parallel with Multimachine Accelerator (which is default on), but unless Multicore Accelerator is enabled (default off), a single cell will be simulated in a single-threaded fashion (see chapter 4.2 for more details).

4.1.2.1 Simics® Multimachine Accelerator

The Multithreaded Simulation Profiler tool (mtprof) can be used to analyze some performance aspects of multithreaded simulation. Mtprof is primarily useful to

The Multithreaded Simulation Profiler helps in understanding threading behavior at the Multimachine Accelerator level. There is no explicit tool support for optimizing specific Multicore Accelerator issues.

The Multithreaded Simulation Profiler is started with the following command:

simics> enable-mtprof

When mtprof is enabled, Simics keeps track of how much CPU time is spent simulating each cell in the system. Once Simics has been running for a while, it is possible to ask mtprof for an overview:

simics> mtprof.cellstat
============================================================
  cellname                    rt   %elapsed  %cputime
------------------------------------------------------------
  ebony3.cell3             77.0s     93.6%    80.0% 
  ebony1.cell1              8.9s     10.9%     9.3% 
  ebony2.cell2              8.2s     10.0%     8.5% 
  ebony0.cell0              2.1s      2.5%     2.1% 
------------------------------------------------------------
  elapsed_realtime         82.2s    100.0%    85.5%
============================================================

From the above output, we can conclude that cell3 is a limiting factor: the other cells frequently have to wait upon this cell in order to keep the virtual time in sync. Investigating why cell3 is so expensive to simulate is the next natural step: there might be an expensive poll loop or the idle optimization might not function properly, for instance. Other potential ways to address the load imbalance include

The mtprof tool can also be used to estimate how fast the simulation would run on a machine with enough host cores to allow Simics to assign a dedicated host core to each cell:

simics> mtprof.modelstat
============================================================
  latency      rt_model  realtime/rt_model
------------------------------------------------------------
    10 ms        81.9s       100.4%
    40 ms        81.2s       101.2%
   160 ms        80.3s       102.4%
   640 ms        78.6s       104.6%
============================================================

The latency column corresponds to various values for set-min-latency. In this case, the simulation was executed with a 10 ms latency, which means that the model predicts that the simulation would take 81.9 s to run (which is pretty close to the measured value of 82.2 s above).

It is important to note that the performance model does not take modified target behavior, due to different latency settings, into account (which can be a huge factor if the cells interact). With substantial inter-cell interaction, only the row corresponding to the current latency setting should be trusted.

Below is an example from a more evenly loaded system run with a min-latency of 1 ms:

simics> mtprof.modelstat
============================================================
  latency      rt_model  realtime/rt_model
------------------------------------------------------------
     1 ms        35.3s       107.4%
     4 ms        29.5s       128.8%
    16 ms        25.9s       146.3%
    64 ms        24.2s       156.5%
============================================================

In this case, we see that increasing the latency setting to about 16 ms would improve simulation performance substantially (once again, without taking changed target behavior into account).

Quite often, the target behavior is not static but varies with simulated time. In that case, it is often useful to export the collected data and plot it using an external tool:

simics> mtprof.save-data output.txt

The exported data is essentially the information provided by mtprof.cellstat and/or mtprof.modelstat, but expressed as a function of virtual time (exported in a plot friendly way). The mtprof.save-data command takes multiple flags which can be used to customize the output. One useful flag is -oplot:

simics> mtprof.save-data mtprof-plot.m -oplot

which outputs the data in the form of an Octave file together with commands which plots the data.

The mtprof tool currently does not support Multicore Accelerator.

4.1.2.2 Multicore Accelerator

Whether to use Multicore Accelerator or not depends heavily on the workload of the system that is being modeled. Multicore Accelerator performs best for systems with CPU intensive tasks, with little communication, and a low I/O rate. The print-device-access-stats and system-perfmeter commands can be use measure the frequency of I/O operations in the system. (I/O operations are expensive in Multicore Accelerator mode.)

Note that some systems perform well even if the number of target instructions per I/O operation is as low as 300 while others perform bad even though this number is considerably higher. Different target systems behave differently, and in the end, one have to benchmark each system by itself to get highest possible performance. As a guideline, if a target system executes less than 10,000 instructions per I/O operation on average, one might have to consider switching off Multicore Accelerator, or tune the system software in order to achieve good performance.

The enable-multicore-accelerator command provides some tuning and settings for Multicore Accelerator. For more information, see the documentation for the enable-multicore-accelerator command.

It is probably best practice to try to optimize the cell partitioning as described in section 4.1.2.1 to find the most resource demanding cells first, and then switching on Multicore Accelerator for those cells (if available host cores still exists). Currently, it is not possible to restrict a number of host cores for particular target CPUs. Simics will instead automatically balance the system as efficient as possible.

4.1.3 Platform Effects

Starting with the most important, consider these factors when choosing a platform to run Simics on:

4.1.4 Workload Characteristics

The performance of Simics sometimes depends on the kind of software that runs inside the target machine being simulated. Thus, the slowdown can vary a lot depending on what the target software is currently executing. Here are some general tips for understanding what decreases simulation speed.

Typically, target code which runs with low performance on real hardware due to bad cache behavior, bad memory locality etc. will also cause Simics to run with poor performance.

4.1.4.1 Idle Loops and Performance

When an operating system does not have any processes to schedule, it typically runs some tight loop waiting for an interrupt to occur. This is referred to as the idle loop. The way the idle loop is implemented varies between operating systems and the capabilities in the underlying hardware.

For example, the most simple idle loop would be a "branch to itself" instruction. When the processor reaches this instruction, nothing but an interrupt will cause the execution to proceed somewhere else. Another example is when the operating system uses some kind of power-down mode on the processor, causing the processor to stop executing any more instructions (and consequently consume less power). Some processors also have dedicated instructions causing the processor to stop until something interesting happens, such as the x86 HLT instruction. Processor idling in Simics is defined by what the simulator can detect and usually includes architectural states such as halt or power-down, but can also be loops normally executed by the processor.

A fast simulation of the idle loop, is very important in some cases. For example, when simulating multiple processors, we want to use as much of the host CPU cycles as possible for simulating the processor that actually performs useful tasks. Rather than wasting cycles on the idle loop.

Even when simulating a single CPU, fast idle loops can be important, since all of the active processes might be stalling on disk or some other peripheral. Execution of the processes will not continue unless, for example, a disk issues an interrupt.

Simics processor models can sometimes detect idle conditions. When the processor model detects a branch to itself, there is no point in simulating the instruction, if it branches to itself repeatedly.

Instead, Simics is capable of fast-forwarding time until an event that can generate an interrupt is about to be executed. Hence, this model is equivalent to running the branch millions of times, but is much faster.

In some cases, the idle loop in the operating system is more than a single instruction, e.g., it might be a loop checking a variable in memory for the next process to schedule. These more difficult cases can be handled with hypersimulation (see chapter 4.1.5).

4.1.5 Hypersimulation

The term hypersimulation refers to a simulator feature which can detect, analyze and understand, frequently executed target instructions and fast-forward the simulation of these, thus providing the corresponding results more rapidly.

Being able to detect the idle loop (see chapter 4.1.4.1) is one example of when this technique is applicable. A much more extreme hypersimulation task would be to understand a complete program and simply provide the corresponding result without actually starting the program. Naturally, this is hardly ever applicable, and impossible in general. Busy-wait loops and spin-locks are more realistic examples of cases where it is easy to optimize away the execution with hypersimulation.

Hypersimulation can be achieved in several ways:

The following instructions are handled with CPU handled instruction hypersimulation:

TargetInstructionComment
ARMmcrEnabling "Wait for Interrupt"
m68kstop
MIPSwait
PowerPCmtmsrSetting MSR[POW].
PowerPCb 0Branch to itself
PowerPCwait
x86hlt
x86mwait

Hypersimulation should be as non-intrusive as possible, the only difference that should be noticeable as a Simics user is the increased performance. Registers, timing, memory contents, exceptions, interrupts etc. should be identical.

Hypersimulation using the hypersim-pattern-matcher may have some intrusions regarding Simics features:

Hypersimulation using the hypersim-pattern-matcher is activated by default, and can be activated/deactivated with enable-hypersim/disable-hypersim.

The hypersim-status command gives some details on what hypersim features that are currently active.

Hypersim patterns are typically fragile, since they depend on an exact instruction pattern. Simply changing the compiler revision or an optimizing flag to the compiler can break the pattern from being recognized.

The QSP-x86 machine does not use hypersim patterns, but with an old PPC-based machine we run the following example:

simics> disable-hypersim
simics> system-perfmeter -realtime -mips
Using real time sample slice of 1.000000s
simics> c
SystemPerf: Total-vt Total-rt Sample-vt Sample-rt Slowdown  CPU Idle  MIPS
SystemPerf: -------- -------- --------- --------- -------- ---- ---- -----
SystemPerf:     0.1s     0.3s     0.09s     0.33s      3.4 100%   0%    29
SystemPerf:     0.7s     1.3s     0.56s     1.00s      1.8  97%   0%    55
SystemPerf:     0.8s     2.3s     0.13s     1.00s      7.6  99%   0%    13
SystemPerf:     2.0s     3.3s     1.22s     1.00s      0.8  95%   0%   122
SystemPerf:     4.2s     4.3s     2.24s     1.00s      0.4  78%   0%   223
SystemPerf:     5.8s     5.3s     1.54s     1.00s      0.6  97%   0%   153
SystemPerf:    11.3s     6.3s     5.46s     1.00s      0.2  99%   0%   543
SystemPerf:    15.9s     7.3s     4.65s     1.00s      0.2  98%   0%   462
SystemPerf:    21.7s     8.3s     5.82s     1.00s      0.2  99%   0%   579
SystemPerf:    27.5s     9.3s     5.82s     1.00s      0.2 100%   0%   579
SystemPerf:    33.3s    10.3s     5.80s     1.00s      0.2  99%   0%   579

simics> enable-hypersim
simics> c
SystemPerf:    65.6s    11.2s    32.23s     0.88s      0.0  98%  85%  3673
SystemPerf:   491.1s    12.2s   425.52s     1.00s      0.0 100% 100% 42382
SystemPerf:   908.4s    13.2s   417.36s     1.00s      0.0  99% 100% 41550
SystemPerf:  1305.9s    14.2s   397.44s     1.00s      0.0 100% 100% 39745
SystemPerf:  1746.3s    15.2s   440.44s     1.00s      0.0  99% 100% 44039
SystemPerf:  2200.9s    16.2s   454.59s     1.00s      0.0  99% 100% 45457

This configuration has a Linux idle loop optimizer by default. We disable hypersim and execute the code "normally" during boot. After 6 seconds (host) or 12 seconds (virtual) the boot is finished and the operating system starts executing the idle loop. The idle loop itself is executed quickly in Simics, running at 579 MIPS. When idling, almost 6 virtual seconds is executed for each host second. That is, Simics executes 6 times faster than the hardware (the processor is configured to be running at 100 MHz).

Next, we stop the execution, enable hypersim, and continue the simulation. Now we can see the idle loop optimizer kicking in and 400 virtual seconds is executed each host second, that is about 70 times faster than without hypersim enabled.

4.1.6 VMP

The VMP add-on for Simics makes use of hardware virtualization support to provide vastly improved performance when simulating x86-based systems. It is an optional part of the x86-based models.

The VMP feature requires that the host machine running Simics has the Intel® Virtualization Technology (Intel® VT) for IA-32, Intel® 64 and Intel® Architecture (Intel® VT-x) enabled in the host machine firmware (the BIOS).

Almost all Intel® processors since the original Core™ architecture, including Xeon® server processors, support the Intel® VT-x feature set. See the Intel® ARK at ark.intel.com for information on specific products.

The Intel® VT-x virtualization feature and the NX execute protection feature must also be enabled in the host machine firmware to run VMP. Look for options under either Security or Virtualization to find where to enable Intel® VT and NX in your firmware. If Intel® Trusted Execution Technology (TXT) is enabled in the firmware, it can also happen that the Intel® VT-x virtualization feature is restricted to trusted applications only; there is usually a special firmware setting controlling this behavior. VMP requires Intel® VT-x to be generally available.

All major features of Simics, including full inspectability of simulated state, and ability to model heterogeneous systems, are fully supported when running with VMP.

4.1.6.1 Installing the VMP Kernel Module

4.1.6.1.1 Linux

Installing and managing VMP kernel modules requires sudo privileges. Installing will compile the kernel module and therefore also requires an environment to build kernel modules. Which packages you need for building kernel modules depend on the distribution of Linux that you are using, but at least for certain Red Hat based distributions you would need gcc-c++, kernel-headers, and kernel-devel. Change directory to the user project and run:

[project]$ bin/vmp-kernel-install

The script will build and then load the VMP kernel module. It will not install it permanently however, but it will tell you how to do that.

Disable VMP temporarily by running the bin/vmp-kernel-unload script, and enable VMP with the bin/vmp-kernel-load script. Permanently uninstall VMP from your host by running the bin/vmp-kernel-uninstall script.

If the installation is read-only, or if you for some other reason want to have the built VMP artifacts outside of the installation, you can give a directory to the relevant VMP scripts, for example:

[project]$ bin/vmp-kernel-install /somewhere/directory

4.1.6.1.2 Windows

The kernel module can be loaded and unloaded by running the bin\vmp-kernel-load.bat respective bin\vmp-kernel-unload.bat scripts as administrator. To do that, open a command shell as administrator and run:

[project]> bin\vmp-kernel-load.bat

Another way to perform the same action would be to right-click on vmp-kernel-load.bat and select run as administrator.

The /AUTO and /DEMAND options select the start option for the VMP service. With /AUTO (default), the service will be available after restart whereas with /DEMAND makes the service available just until shutdown or reboot, and then VMP has to be loaded again when needed.

If the script fails, see the Windows event log for more information. The most common reason is that Intel® VT-x technology or the NX feature is not enabled in the UEFI/BIOS. The kernel module will also fail to load if the Hyper-V feature is enabled.

4.1.6.1.3 Hyper-V Interaction

Windows 8 and later can be run with Hyper-V enabled. Hyper-V is a feature where the operating system is run as a guest under the control of a hypervisor. VMP cannot be used in conjunction with Hyper-V: loading the VMP kernel module will fail.

There are various ways to disable the Hyper-V feature. Below are instructions for adding an additional boot configuration database entry to make it easy to boot with Hyper-V disabled:

4.1.6.2 Running with the VMP Add-on

With the VMP kernel modules installed, VMP will be enabled by default for each processor. You can disable VMP by running the disable-vmp command.

Due to details about how the Intel® VT feature that VMP is based on works, the acceleration may not kick in. Use the system-perfmeter to find out if a processor actually uses the VMP execution mode. To find out why VMP is not used, either raise the log level of the CPU in question or use the info command on the CPU.

4.1.6.3 Current Limitations of the VMP Add-on

The VMP packages are currently available and supported for Linux and Windows.

Due to bugs in the performance counters on many Intel CPU types, precise instruction counting cannot be achieved with VMP. The only CPU types that we are aware of that do not have this bug is Intel® Core™ Duo/Solo, and Pentium® 4 processors. This results in that slight differences in timing between runs will appear and cannot be avoided due to the bug in the underlying hardware.

Software using hardware virtualization frequently have problems co-existing on a machine. If you are running other software using hardware virtualization such as VMware, VirtualBox, KVM, or Intel® Hardware Accelerated Execution Manager (Intel® HAXM), you may need to unload the driver corresponding to that software before using VMP.

Note that virtualizers such as VMware or Xen in most versions do not expose the Intel® VT feature. Even virtualizers that do expose the Intel® VT feature are likely to either lack support for the performance counters needed to run with VMP, or will have very different performance characteristics compared to native solutions. To summarize, it is highly recommended that VMP is run in a non-virtualized environment.

4.1.6.4 Differences Between VMP and non-VMP

VMP is designed to be a transparent performance enhancement. The implementation with Intel® VT-x makes that impossible in some areas, and in some other areas we have taken the chance to utilize the VMP mode to tweak performance further. Enabling features that cannot be emulated using Intel® VT-x will automatically disable VMP.

VMP is optimized for software development, and the default timing parameters used in VMP reflect that. The timing settings can be viewed with the info command on the CPU object. As default, VMP timing will stall the PAUSE instruction, the RDTSC instruction, and each port-mapped I/O access for 10 milliseconds. These stalls are added to allow loops containing these operations to consume more virtual time per iteration and therefore speed up simulation if the loop is terminated after a set virtual time. This is controlled by attributes in the CPU object and can be changed if somewhat more realistic timing is more important than maximum simulation speed.

The hardware performance counters are used to figure out the number of executed instructions when running in VMP mode. Since those counters count string instructions as one regardless of the number of iterations, this is the instruction count model that must be used to enable VMP. This is controlled by the one_step_per_string_instruction attribute, and setting that to classic counting will disable VMP.

The TLB model is bypassed in VMP mode. To simulate a particular configuration of the TLB, or listen to the haps that the TLB model generates, VMP has to be manually disabled.

4.1.7 Performance Tweaks

There are a number of parameters in Simics which can be tweaked which might lead to increased performance.

4.2 Scaling Simics

This chapter describes performance scaling features provided by Simics. To get the best performance out of Simics you should also read the Performance chapter for general information about better Simics performance.

This chapter talks about two major features: parallel simulation and page sharing. Parallel simulation allows you to use multithreading to speed up simulation on multiprocessor hosts. Parallel simulation works both at the machine (cell) level, called Simics® Multimachine Accelerator and at the processor level, called Multicore Accelerator. Multicore Accelerator is new in Simics 5. Multimachine Accelerator was known simply as multithreading in earlier Simics versions, but has been renamed to reflect that we have multiple types of multithreading in Simics 5.

You can also split the simulation and run it distributed across more than one host machine.

Page sharing finds identical memory pages and shares them to decrease memory consumption and increase execution performance. This is particularly useful when you have several machines running the same software.

4.2.1 Intel® Simics® Multimachine Accelerator

The easiest way to parallelize a simulation is to use the Simics® Multimachine Accelerator feature. It requires that the models used in the simulation are marked as thread-safe. The rest of this section describes how to use Multimachine Accelerator.

With Multimachine Accelerator the simulation runs in a single Simics process: you control the entire simulation from a single point, and the entire simulation state gets saved in one checkpoint, just as when you run a single threaded simulation.

To use Multimachine Accelerator the configuration must be partitioned into simulation cells. Each cell contains a subset of the configuration objects in the simulation. The only communication allowed between cells is over links. A link transmits messages between objects with a latency measured in simulated time, for example an Ethernet cable.

Dividing the system into cells can be done automatically via the Simics's component system. This makes it easy to parallelize an existing model.

4.2.1.1 Multithread-Ready Models

Most models provided with Simics can run with Multimachine Accelerator enabled and are thus marked thread-safe. Loading modules that are not marked thread-safe will result in a warning message and Multimachine Accelerator will be disabled. Please contact your Simics provider if you are running a model that is not multithread-ready and you want to utilize Multimachine Accelerator.

If you developed your own models of devices, you should refer to the Model Builder User's Guide to learn how to make them multithread-compatible.

Whenever possible, all default components provided with Simics create simulation cells for use with Multimachine Accelerator. For example, instantiating two MPC8641-Simple boards in the same Simics session will create two cells, which can be scheduled on two simulation threads. The maximum possible parallelism is limited by the number of cells in a session (as well as the number of processor cores on your host, of course). You can list the cells instantiated in a configuration with the following command:

simics> list-objects -all class = cell

4.2.1.2 Enabling and Disabling Multimachine Accelerator

Simics® Multimachine Accelerator is enabled by default. It can be turned off using the command

simics> disable-multithreading

and on again with

simics> enable-multithreading

This command will also check that the configuration looks reasonable before switching on Multimachine Accelerator, and warn you if something is incorrect.

4.2.1.3 Controlling Cell Synchronization

To allow multi-cell simulation to perform well, Simics lets each thread run for a certain amount of virtual time on its own before it needs to resynchronize with the other cells. This time span is the synchronization latency. Because of the synchronization latency, Simics does not allow communication between objects of different cells. Even if all accesses were properly locked and performed in a thread-safe way, the objects would have no way to control at what time their access would be done in the other cell, and the simulation would stop being deterministic.

The solution is to communicate via link objects. Link objects ensure that messages send from one cell are delivered at the expected virtual time in the other cell, at the cost of a virtual time delay in the transmission. For links to send messages deterministically, the delay in transmission must be greater or equal to the synchronization latency. For this reason, the synchronization latency is often called the minimum latency for link communication.

The next two sections explain how to control the synchronization latency—and the link latencies—in multi-cell simulations.

4.2.1.3.1 The Simple Way

By default, Simics creates a single synchronization domain called default_sync_domain. Cells created later in the simulation will be attached to this synchronization domain, unless specified otherwise. Thus the synchronization latency in the simulation will be controlled by the min_latency attribute set in default_sync_domain.

The simplest way to control the synchronization latency is to use the set-min-latency command, which will immediately create a default synchronization domain if it does not exist, and set its min_latency attribute with the given valid latency. An error message will be printed out if the given latency value failed the validity check.

simics> set-min-latency 0.01
simics> list-objects class = sync_domain
┌───────────────────┬─────────────┐
│      Object       │    Class    │
├───────────────────┼─────────────┤
│default_sync_domain│<sync_domain>│
└───────────────────┴─────────────┘

simics> default_sync_domain->min_latency
0.01

One important thing to remember is that the time quantum in each multiprocessor cell must be less than half the minimum latency. In other words: sync_latency > 2 × time_quantum for every multiprocessor cell in the system. Simics will print an error if this condition is not respected.

4.2.1.3.2 Understanding Synchronization Domains

Synchronization latencies can be controlled in a much finer way. Synchronization domains can be organized in a hierarchy that allows different cells to be synchronized with different latencies. This organization is the foundation of the domain-based distribution system, described in chapter 4.2.3.

Let us build a networked system with two-tightly coupled machines communicating on a very fast network, associated with a control server that sends a command from time to time. The two machines require a low communication latency, while the communication latency between them and the server does not matter. Using a hierarchy of two domains allows all latency requirements to be fulfilled without sacrificing performance:

Top-domain (latency 1.0s)
 -> Server cell
 -> Sub-domain (latency 1e-6s)
     -> Machine0 cell
     -> Machine1 cell

In that configuration, the two machines can communicate with a latency of 1e-6 s while the communication latency between the machines and the server is 1 s. In practice, this allows Simics to give the server a 1 s synchronization window with the two machines, hence much less synchronization overhead and a better usage of parallel simulation.

More concretely, in Simics, the domains are setup in the following way (in Python):

simics> @top_domain = pre_conf_object("top_domain", "sync_domain")
simics> @top_domain.min_latency = 1.0
simics> @sub_domain = pre_conf_object("sub_domain", "sync_domain")
simics> @sub_domain.min_latency = 1e-6
simics> @sub_domain.sync_domain = top_domain
simics> @SIM_add_configuration([top_domain, sub_domain], None)

Cells created automatically can be assigned to a domain by using the domain attribute of the corresponding top-component. It is also possible to set a cell's sync_domain attribute when creating it manually.

4.2.1.3.3 Setting Latencies: the Complete Rules

Latencies must obey certain rules for the domain hierarchy to work properly:

4.2.1.4 Multimachine Accelerator and Scripting

Commands and script branches are never run multithreaded, thus parallelism can be safely ignored most of the time when scripting Simics. However, using Simics® Multimachine Accelerator has side-effects that may cause scripts to behave in a correct but indeterministic way. If we consider the following script, in a configuration consisting of two cells, cell0 and cell1:

simics> cell0_console.break "foo"
simics> c
simics> cell1_console.input "bar"

Even with cell0 and cell1 running in parallel, the simulation will stop properly when the text breakpoint in cell0 is triggered. However, cell1 is not at a deterministic point in time: the only thing known about it is that it is within a certain window of virtual time in which it is allowed to drift without needing to re-synchronize with cell0, as explained in the previous section. So running this script twice in a row may not produce exactly the same results.

In many cases, it does not matter and the scripts will work fine. If perfect determinism is required, it is possible to save a checkpoint and run the sensitive part of the simulation single-threaded.

One aspect of Multimachine Accelerator that affects scripting directly is Python scripting. Hap handlers are run in the thread where they are triggered, which means that the same handler can run in parallel on different host processors. If the handler uses global state, it must use proper locks to access it. In general, this is not a problem since most haps are triggered for a specific object, so their handlers will only run in the thread where this object is scheduled. Some haps are triggered globally, however, and care must be taken when responding to them.

Python scripts are run with the global Python lock taken, so Python scripts never really run in parallel. However, the Python interpreter will schedule Python threads as it sees fit, so Python code that may run in several threads (device or extension code, hap handlers) should not assume that it has full control of the execution. The Python lock is also released every time a Simics API functions is called (including implicit calls like reading an attribute value).

When running Python scripts in a simulation thread, the script should not access state that is in a different cell, since this cell might be running on another host processor. When in need to access the whole simulation state, a callback function can be scheduled with SIM_run_alone() (this is currently how script branches and commands are handled).

Finally, running commands in the simulation thread is not allowed, as the CLI parser is not thread-safe and might cause unexpected problems. Commands must be scheduled with SIM_run_alone(). It is also possible to rewrite scripts to access directly objects and attributes instead of using the commands directly.

4.2.1.5 Dynamic Load Balancing

Simics uses dynamic load balancing to distribute the simulation workload across the available hardware resources (host threads). The dynamic load balancer optimizes the mapping of simulation threads onto available host resources.

When Simics is running with Simics® Multimachine Accelerator, CPUs belonging to the same cell can not be simulated concurrently by separate host threads. The available concurrency in this mode of operation is between CPUs belonging to different cells. Using as many cells as possible can potentially improve performance since this increases the parallelism of the simulation. Having many cells also makes it easier for the dynamic load balancer to keep all host threads fully loaded.

When Simics is running with Multicore Accelerator, CPUs belonging to the same cell can be simulated concurrently. Note that Multimachine Accelerator is implied by Multicore Accelerator.

Simics uses a non hierarchical scheduling algorithm based on simulated time and available work. By default, Simics spawns at most as many threads as there are host threads, but it is possible to limit this number using the set-thread-limit command. Setting a thread limit may be useful if the physical machine is shared by multiple users.

Simics does not interact with the host operating system with regards to scheduling. The details of the scheduling are internal and there exists no API for controlling it.

4.2.2 Multicore Accelerator

With Multicore Accelerator, multiple host threads can be used to simulate multiple processors and clocks within each cell concurrently. This requires that all the processors and clocks supports this execution model. Multicore Accelerator is compatible with Multimachine Accelerator meaning that both can be enabled at the same time, utilizing the dynamic load balancing (see 4.2.1.5).

When developing models to be used in the Multicore Accelerator execution mode there are a couple of considerations to take into account. See the section Threading Model in API Reference Manual for more information.

4.2.2.1 Enabling and Disabling Multicore Accelerator

Unlike Simics® Multimachine Accelerator, Multicore Accelerator is disabled by default. The reason for Multicore Accelerator being disabled is that it changes the timing model of simulation, notably no longer supporting deterministic simulation. In the Multicore Accelerator mode the modeled CPUs are run concurrently by host threads, which means that the interleaving of memory accesses to a shared memory is not guaranteed to occur in the same order between simulation sessions. This implementation was chosen to be able to leverage maximum simulation performance.

Multicore Accelerator is enabled with

simics> enable-multicore-accelerator

and can be disabled with

simics> disable-multicore-accelerator

The optional argument max-time-span to enable-multicore-accelerator controls how far the time is allowed to drift apart between CPUs/clocks in each cell where Multicore Accelerator is enabled (cf. min latency for cells, 4.2.1.3). For more information, consult the documentation for the enable-multicore-accelerator command.

4.2.2.2 When is Multicore Accelerator beneficial

Multicore Accelerator works best for parallel compute intensive applications with little interaction between the CPUs. On the other hand, a system with a large amount of I/O operations will not be a good candidate. Such workloads, for example Operating System boot, can perform worse when Multicore Accelerator is enabled.

Remember that although there are parallel workloads occupying all CPUs in the system, the amount of actual parallelism that Multicore Accelerator can extract may be much lower. This is mainly because of serial sections, communication, synchronization, and poor load balancing in the target system.

For more information about performance of Multicore Accelerator and how to measure how much parallelism there is in the modeled system, see the Performance chapter.

4.2.3 Distributed Simulation

Distributed simulation is used for connecting multiple Simics processes, possibly running on different host machines, to run synchronously and exchange data in a reliable and deterministic way.

4.2.3.1 Configuration

The Simics processes taking part in the combined simulation, here called nodes, are configured and managed individually. Each node will set up and run its own configuration and have its own name space. It will be controlled by its own command line or graphical interface.

Nodes are strung together by letting the local top-level synchronization domain in one node have a domain in another node as parent. Typically, there will be one global top-level domain in one node controlling the domains in all other nodes:

       D0                         global top-level domain
        |
        +---------+---------+
        |         |         |
       D1        D2        D3     local domains
        |         |         |
        +-+-+     +-+-+     +-+-+
        |   |     |   |     |   |
        C11 C12   C21 C22   C31 C32  cells
        |--------|---------|--------|
        node 1    node 2   node 3

In the above diagram, D0-D3 are synchronization domains and C11-C32 cells. D1, C11 and C12 are all in node 1, and so on. The top-level domain D0 could be placed either in a node of its own, without any cells, or in one of the other nodes. We will here assume it is located in node 1, the server node; the other nodes are then clients.

Domains in different nodes connect by proxies, which themselves connect over the network. The relation between D0 and D3 above is set up like follows:

           /     D0           sync_domain
   node 1 |      |
           \     D3_proxy     remote_sync_node
                 :
              (network connection)
                 :
           /     D0_proxy     remote_sync_domain
   node 3 |      |
           \     D3           sync_domain

The remote_sync_domain in the client node, D0_proxy, is created explicitly in the configuration for that node. The remote_sync_node in the server node is created automatically by a special server object when D0_proxy connects to the server node.

When a node has finished its configuration, it must inform the server to allow other clients to connect. This is done by setting to None the finished attribute of the remote_sync_domain object, or the remote_sync_server in the server. As a result, node configuration is done in sequence.

The default domain used by cells is default_sync_domain, so by using this as the local domain name, existing non-distributed configurations can be re-used. It is also a good idea to use the same name for the remote_sync_domain as for the actual top-level domain it is a proxy for. That way, it will matter less in what node the top-level domain is placed.

The configuration script for a single node could look like the following Python fragment:

srv_host = "serverhost"  # machine the server node runs on
srv_port = 4567          # TCP port the server listens on

# Start by creating the global and/or local domain objects:

if this_is_the_server_node:
    topdom = SIM_create_object("sync_domain", "top_domain",
                               [["min_latency", 0.04]])
    rss = SIM_create_object("remote_sync_server", "rss",
                            [["domain", topdom], ["port", srv_port]])
else:
    # Client nodes: create a proxy for the top-level domain.
    # This will initiate a connection to the server.
    topdom = SIM_create_object("remote_sync_domain", "top_domain",
                               [["server",
                                 "%s:%d" % (srv_host, srv_port)]])
# create a local domain to be a parent for the cells in this node
SIM_create_object("sync_domain", "default_sync_domain",
                  [["sync_domain", topdom], ["min_latency", 0.01]])

# --- Here the rest of the node should be configured. ---

if this_is_the_server_node:
    rss.finished = None     # let clients connect to the server
else:
    topdom.finished = None  # let other clients connect to the server

At the end of this script, the configuration is finished for that node. Note that other nodes may not have finished theirs yet—the simulation cannot start until the entire system has been set up. The user can just wait for this to happen, or write a mechanism to block until the system is ready; see the section about global messages below.

Links work across nodes in the same way as in a single process simulation. Using the same global ID for links in two different nodes ensures that they are considered as the same link in the distributed simulation. The global ID for a link is set using the global_id attribute when the link is created.

There is one important aspect of link distribution that should be taken into account when creating distributed configuration.

When creating single-session configuration, Simics provides only one object namespace, which means that all objects have a unique name in that session. This property is used to keep link message delivery deterministic when no other way of comparing the messages is available. To be more precise, messages arriving from different senders to the same receiver at the same cycle are sorted according to the pair (sender name, sender port).

In distributed sessions however, Simics does not impose a single object namespace. This allows several objects with the same name to be connected to the same distributed link. As a consequence, the delivery of messages as described in the previous paragraph may become indeterministic again, since different sender may report the same (sender name, sender port) pair. Distributed links report an error if such a configuration is found.

The solution is to name differently the various boards or machines that compose the complete distribution configuration.

Deleting a distributed link is not supported.

4.2.3.3 Global Messages

There is a supporting mechanism for sending simple messages to all nodes in the combined system: SIM_trigger_global_message(msg) will trigger the global notifier Sim_Global_Notify_Message, whose callbacks can use SIM_get_global_message to obtain the message. A notifier listener could look like:

def global_message_callback(_, ref):
    print("got message {0}".format(SIM_get_global_message(ref)))
SIM_add_global_notifier(Sim_Global_Notify_Message, None,
                        global_message_callback, my_ref)

Global messages will arrive and be processed during a call to SIM_process_work(). This is useful for blocking further execution of a script until a certain message has arrived.

Global messages can be used to assist in configuration and running a distributed system. Possible uses include:

This facility is not intended for simulation use; message delivery is reliable but not deterministic in timing. It should be regarded as a low-level mechanism to be used as a building block for higher-level communication.

4.2.3.4 Running the simulation

Each node will have to start the simulation for the combined system to make progress. If one node stops the simulation—by user intervention or because of a coded breakpoint—the remaining nodes will be unable to continue, because the top-level synchronization domain keeps cells in different nodes from diverging.

Each node can only access the objects it has defined locally. This means that inspection and interaction with the system must be done separately for each node. A combined front-end interface is not available for Simics at this time.

When one Simics process terminates, the other nodes will automatically exit as well.

4.2.3.5 Saving and restoring checkpoints

The state of a distributed simulation can be saved by issuing write-configuration in each node separately. To restore such a checkpoint, start an equal number of (empty) Simics processes and read the saved configuration for each node.

Note that the server host name and port number are saved in the configuration files. These have to be edited if the checkpoint is restored with a different server host, or if the port number needs to be changed. Similarly, if SimicsFS is mounted to a file system, it will be saved in the checkpoint. Not all connections to real network or file systems can be restored at a later time.

Note as well that the configurations must be read in sequence again, using the finished attribute to control which session is allowed to connect. However, the order of read-configuration sequence does not matter, as long as the server is started first.

4.2.3.6 Security

The distributed simulation facility has no authentication mechanism and is not security-hardened. It should therefore only be used in a trusted network environment.

4.2.4 Page-Sharing

When running multiple instances of the same target system, in a virtual network or similar, it is likely that many of the RAM, ROM, flash, or disk pages in each system are identical to the others. Simics Accelerator adds a new page-sharing feature which takes advantage of this fact to reduce host memory consumption and increase execution performance.

When the page-sharing feature is activated, at certain trigger points Simics will examine the contents of a page, comparing it with other pages examined earlier. If an identical match is found, the page is removed and instead set to share data with the other page(s).

When many pages are shared the host memory that Simics uses will be reduced and consequently it will take longer until the memory-limit is reached. If the frequency of reaching Simics memory-limit is a factor limiting performance, execution performance will also increase. Simics can also take advantage of the fact that the page is shared by sharing the generated JIT code corresponding to a shared page. This can lead to improved performance since the JIT code only needs to be created once, but also thanks to better instruction caching when many target processors use the same JIT code.

Shared pages are always read-only, if a shared page is written to, it will automatically be un-shared and the writer will be given a private copy of the page. Consequently, page-sharing works best for pages containing only instructions or data pages which are never or rarely modified.

To activate page-sharing, use the command enable-page-sharing. This command also has a -now command argument which causes all pages to be analyzed and possibly shared directly.

To monitor how much memory that is saved due to the page-sharing feature, the system-perfmeter has a -shared switch which shows how much memory in total that has been saved, in each measurement sample (see the Performance chapter).

Use disable-page-sharing to deactivate the page-sharing feature. This will not cause already shared pages to be un-shared but no more pages will be shared.

5 Appendix

5.1 Old Script Parameters

This describes the old script parameter system, which is deprecated. See section 2.3.3 for the target parameter framework.

A Simics target script may declare its parameters and resulting values.

5.1.1 Declarations and Parameters

Script declaration blocks are optional, and have the form

  decl {
    declarations
  }

Such a block must come before any commands in the script file. Possible declarations are:

Whitespace (spaces, tabs, line breaks) are in general not significant unless specifically noted. Comments are allowed, starting with '#' and running to the end of the line.

A parameter declaration takes the form

  param NAME : TYPE [= DEFAULT-VALUE]

where the default value is optional. Example:

  param ram_size : int = 8

This means that the variable $ram_size will be set when the script is executed, that it must be an integer, and that it will be 8 if not specified in any other way.

If there is no default value, then the parameter must be specified; otherwise, it is optional.

A script with a declaration block will only see the variables that have been declared as parameters. Other variables are hidden during the script's execution and will re-appear when the script terminates.

5.1.2 Results

A script can return variables that it has set by declaring results:

  result NAME : TYPE

Example:

  result mac_address : string

It means that the script must set $mac_address before terminating, that it must be a string value, and that this variable is available to the caller. All other variables assigned to by the script are lost.

The same name can be used for both a parameter and a result.

5.1.3 Running Scripts

Parameters are given on the command line when running Simics:

$ ./simics myscript.simics ram_size=64

or as arguments to the run-script command:

simics> run-script myscript.simics ram_size = 64

or by setting the CLI variables before invoking the script:

simics> $ram_size = 64
simics> run-script myscript.simics

The -help command-line option will show all parameters of a script:

$ ./simics myscript.simics -help

From the Simics command line, the arguments for a file can be listed by using the help command:

simics> help myscript.simics
The original IBM PC XT from 1983, complete with
a full-height 10 MB fixed disk and a 5 1/4' floppy drive.

networking:
  mac_addr  -  string or NIL
...

It is also possible to use tab completion with the run-script command:

simics> run-script myscript.simics<tab>
  -local  -main-branch  ram_size =  cpu_freq =

5.1.4 Types

The following type specifications are allowed in the declarations of parameters and results. Each item in the list below describes the values permitted by the type.

Complex or compound data types can often be packed into a string and unpacked and verified by the receiver in Simics. See documentation of the split-string command.

5.1.5 Documentation

Parameters can have documentation strings that are shown in the command-line. These consist of one or more lines that begin with an exclamation mark, immediately following the parameter. Example:

  param ram_size : int
  ! RAM size in megabytes.
  ! Note that Windows 95 needs at least 2 MiB to run.

The script itself can also be described in the same way, by putting a documentation string first in a declaration block:

  decl {
    ! Atari 2600 Video Game Console System
    param cartridge : string = "COMBAT"
  }

Documentation strings are written in a subset of the Simics JDocu mark-up language, which is XML-based. Most plain text can be written directly, but the symbols '<' and '&' must be written '&lt;' and '&amp;', respectively. Use blank lines to break paragraphs.

The following typographic elements exist:

5.1.6 Parameters from Other Scripts

Parameter declarations can be taken (imported) from other script files by using the declaration

  params from SCRIPT [except NAMES] [default NAME = VALUE ...]

All parameter (but not result) declarations from the given script will be used as if they had been declared in the importing script.

The optional except clause will exclude one or more comma-separated parameters.

Example: if the script a.simics contains:

  decl {
    params from "b.simics" except y
    param x : int
    result r : string
  }

and b.simics contains:

  decl {
    param y : int = 10
    param z : bool = TRUE
    result s : string
  }

then a.simics will behave as if it had been written:

  decl {
    param z : bool = TRUE
    param x : int
    result r : string
  }

The default value of imported parameters can be overridden by adding default clauses. Example:

  params from "hardware-setup.simics"
    default ram_size = 256
    default cpu_freq = 3.58e6

There is also a substitute declaration that is reserved for Simics internal use.

5.1.7 Parameter Groups

Parameters can be arranged into groups, to make them easier to browse when they are numerous. The declaration

  group "Networking"

will put all parameters that follow (in that file) into the group named "Networking". Groups do not affect how parameters actually work; they only serve to make the presentation better.

5.1.8 Script Execution

When a script with a declaration block is run, interactively or from another script, this is what happens in detail:

  1. The script declaration is read and checked for errors. Imported parameters ("params from") are resolved recursively.

  2. Actual parameter values are taken from, in order:

    1. The supplied arguments, if the script was started from a shell command, or from run-script.
    2. CLI variables.
    3. Default values from the script declaration.

    An error is signalled if a parameter value is missing or has the wrong type.

  3. The current CLI variables, if any, are saved away and replaced with the script parameters having their initial values as described above.

  4. The command part of the script (below the declaration) is executed. If the script runs another script, this entire algorithm is used again.

  5. CLI variables corresponding to declared result names are retrieved. An error is signalled for missing results or ones with the wrong type.

  6. The saved CLI variables are restored, throwing away all current variables except for the declared results, which retain their values from the executed script.

5.1.9 Example

Here is a slightly longer example of a script declaration block.

# A machine from the good old days (?)
decl {
  ! The original IBM PC XT from 1983, complete with
  ! a <em>full-height</em> 10 MB fixed disk and a 5 1/4" floppy drive.

  # Note that comments (like this one) are distinct from doc strings.

  param ram_size : int = 256
  ! RAM size in KiB. Max is 640, which should be enough for anyone.

  param cpu_freq : float = 4.77
  ! CPU frequency in MHz.

  param video : {mda, cga, hercules} = cga
  ! Type of video card. mda &amp; hercules look best.

  group "networking"
  param mac_addr : string or nil = NIL
  ! Ethernet MAC address, or NIL if no Ethernet adapter present.

  param rom_file : file("*.bin") = "%script%/pc_xt_rom.bin"
  ! File containing the ROM image. Must be exactly 40 KiB.

  param rtc_time : string = "2015-02-01 13:37:00"
  ! Initial time and date of the real-time clock, on the form
  ! <tt>"YYYY-MM-DD HH:MM:SS"</tt>.

  # Include parameters declared in another file.
  params from "ms-dos.simics"

  # Override the default value of a parameter from that file
  default dos_ver = "2.11"

  result system : string
  ! Name of generated system component object.
}

# Here the actual script starts with commands to execute.