12 Using Python in a Simics module 14 Modeling with C
Model Builder User's Guide  /  II Device Modeling  / 

13 Adding New Commands

This chapter describes how to add new Simics CLI commands. By adding such commands you can extend what a user can do from the Simics command line. For example, you can add new ways for a user to inspect and control device models you have written.

Commands in Simics are written in Python. They are normally implemented by accessing the interface functions and attributes of configuration objects, and by calling Simics API functions. Commands are easy to write, and let the user conveniently perform tasks that otherwise would require detailed knowledge of the attributes in your device.

Most commands are related to a specific class or interface, and are declared when the corresponding module is loaded. For example, the sample-device-dml module registers the commands <sample_device_dml>.info and <sample_device_dml>.status when the module is loaded.

Commands can also be registered globally. For example, the sample-components module defines the new-sample-pci-card command. For device models, global commands are usually defined when the module loads. However, commands can also be defined when Simics is started. Doing so makes the command available without loading the module, but it also makes Simics startup a bit slower.

For a simple device, it is sufficient to declare all commands in a single Python file, called module_load.py. In some cases it can make sense to split up this file into multiple files; either in order to improve readability, to simplify unit testing, or to enable code sharing between Simics modules. Chapter 12 discusses in detail how Python files are added to a module.

13.1 Example of a New Command

This is an example on how to add a new Simics command using Python, as it would appear in the module_load.py file of a module's source code subdirectory:

# this line imports definitions needed below
import cli

# this is my Python function that will be called when the
# command is invoked from the Simics prompt.
def my_command_fun(int_arg, str_arg, flag_arg):
    print(f"My integer: {int_arg}")
    print(f"My string: {str_arg}")
    print(f"Flag is {'' if flag_arg else 'not '}given")
    return int_arg

# register our new command
cli.new_command("my-command", my_command_fun,
                args  = [cli.arg(cli.int_t, "arg", "?", 10),
                         cli.arg(cli.str_t, "name"),
                         cli.arg(cli.flag_t, "-f")],
                alias = "mc",
                type  = "[my-module-commands]",
                short = "my command does it",
                doc_items = [("NOTE", "This command is best")],
                see_also = ["my_other_command"],
                doc = """
<b>my-command</b> is best.
This is its documentation. <arg>arg</arg>
is the first argument...""")

The code above will define a command called my-command. When the command is invoked from the Simics command line interface, the function my_command_fun() will be called. The function must take exactly as many arguments as the command; in this case, three: an integer, a string, and a flag. The first command argument is called "arg" and is optional, indicated by "?". If omitted by the user, the command function will be called with the default value, 10. The second argument is called "name" and is required (since there is no "?", there is no default value). The last argument is a flag and will have a value of 1 if the flag is given, otherwise 0. Flags are thus always optional.

If the command function returns a value (a string or an integer) Simics will print this value on the terminal or pass it on to other commands as input arguments, e.g., print -x (my-command 15 foo).

Writing help my-command at the Simics prompt will display:

NAME
   my-command - my command does it
SYNOPSIS
   my-command [arg] name [-f]
ALIAS
   mc
DESCRIPTION
   my-command is best. This is its documentation. arg is the first argument.
NOTE
   This command is best
SEE ALSO
   my_other_command

The command can be invoked in different ways, here are some examples:

simics> my-command 2 foo -f
My integer: 2
My string: foo
Flag is given
2
simics> my-command bar
My integer: 10
My string: bar
Flag is not given
10
simics> my-command 20 -f
Argument error: argument number 2 is missing in 'my-command', string expected.
SYNOPSIS: my-command [arg] name [-f]
simics> print -x (mc -f name="hello there!" arg = 23)
My integer: 23
My string: hello there!
Flag is given
0x17

In the last case the alias is used and the command is passed to the print command that outputs the value in hexadecimal notation.

13.2 The new_command Function

The new_command function defines a new command that can be used on the command line and in scripts. The definition includes descriptions of the command parameters, its documentation, and the Python function to call when the command is run.

The parameters to the function are described below.

13.3 The arg Function

The arg function is used to define parameters to commands. It is used to populate the list in the args in the new_command function.

The parameters to the function are described below.

13.4 Polyvalues

A command argument can be of multiple types as well (polyvalues). For example,

new_command(…,
    args = [ arg((str_t, int_t, flag_t), ("cpu","value","-all"), "?",
                 (int_t, 0, "value"), expander = (exp1, exp2, None)) ],
    …)

will create an argument that is either a string, an integer, or a flag. The argument passed to the command handler function is a tuple specifying the argument type, the value, and the name of the argument. E.g., command foo will pass (str_t, "foo", "cpu") to the command handler function. This is why the default value looks the way it does. If given the corresponding expander function will also be used. command cpu = abc<tab> will use the exp1 expander.

13.5 Command Errors

Command functions signal errors by raising the CliError exception, with an error message string as argument. For example:

    if not valid(argument):
        raise CliError("invalid argument")

Signalling errors with CliError ensures that the error will be reported in a correct way to the user.

Commands frequently read or write attributes in configuration objects. When doing so it is especially important to catch exceptions generated by the attribute access and handle it appropriately, perhaps by raising a CliError. Any uncaught attribute exception will cause a Python stacktrace to be shown in the Simics user interface. The stacktrace is not likely to be helpful to the user and should be thought of as a programming error by the command implementer.

def my_command(obj):
    try:
        val = obj.value
    except simics.SimExc_General, e:
        raise cli.CliError('Could not retrieve attribute "value" in object'
                           ' %s: %s' % (obj.name, str(e)))
    return cli.command_return(
        message = "Value of %s is %s." % (obj.name, str(val)),
        value   = val)

The exceptions that an attribute access may trigger is highly dependent on the accessed attribute and the context of the command. For instance, some commands only operate on objects whose type is known, and on attributes that cannot under any circumstances cause an exception. On the other hand, some commands operate on objects that may not even have the attribute the command is trying to access. The level of information available about the object and its attributes needs to be taken into consideration when deciding what exceptional conditions the command needs be able to handle.

For information on what exceptions an attribute access can trigger, refer to SIM_get_attribute and SIM_set_attribute in the reference manual.

For information on how to implement an attribute to raise exceptions in a correct way, refer to section 5.3.3 and 14.4.3.

13.6 Human-readable Messages

When run interactively at the Simics prompt, a command will get its return value printed to the user. Sometimes, however, a more human-friendly message is desirable. To achieve this, the command's function should return an object of the special type command_return:

import cli, platform

def platform_fun():
    sysname = platform.system()
    return cli.command_return(
        message = "This machine is running %s." % (sysname,),
        value   = sysname)

cli.new_command("get-os-type", platform_fun)

The get-os-type command will now return the name of the operating system when used in an expression, but print a human-readable message when used as a top-level interactive command.

simics> get-os-type
This machine is running Linux.
simics> $os_type = (get-os-type)
simics> echo $os_type
"Linux"

In the same way, you can also use cli.command_quiet_return to completely suppress the interactive message.

For more information, see the documentation on cli.command_return, cli.command_quiet_return and cli.command_verbose_return in the Simics Reference Manual.

13.7 Info and Status Commands

Every model should have an info command, giving static information about the device, and a status command, that gives dynamic information.

To simplify the creation of info and status commands, there are a couple of helper functions that make it easy to add these commands and have the output formatted in a standard fashion.

Instead of calling new_command directly, you call new_info_command and new_status_command. The functions you provide to these functions should not print anything directly, instead they should return the information to be printed.

The data returned from new_info_command and new_status_command should be a list of sections, where each section is a tuple of a section title and a list of entries. The section title should be a string or None. An entry is a tuple of a name and a value. The name is a string, and the value can be just about anything.

13.7.1 Example

import cli

def get_info(obj):
    return [("Connections",
             [("Up",   obj.up),
              ("Down", obj.down)]),
            ("Sizes",
             [("Width",  obj.width),
              ("Height", obj.height),
              ("Area",   obj.width * obj.height)])]

cli.new_info_command('sample-device', get_info)

def get_status(obj):
    return [(None,
             [("Attribute 'value'", obj.value)])]

cli.new_status_command('sample-device', get_status)
12 Using Python in a Simics module 14 Modeling with C