Models

SimEng provides a number of default processor core models, which simulate the structure and behaviour of common processor archetypes.

Archetypes

Emulation

The emulation model is the simplest default model, simulating a simple atomic “emulation-style” approach to processing the instruction stream: each instruction is processed in its entirety before proceeding to the next instruction. This model is not particularly well suited for modelling all but the simplest processors, but due to its simplicity is extremely fast, and thus suitable for rapidly testing program correctness.

In future, this model may be suitable for rapidly progressing a program to a region of interest, before hot-swapping to a slower but more detailed model.

In-Order

The in-order model simulates a simple in-order pipelined processor core, with discrete fetch, decode, execute, and writeback stages. This model is capable of speculatively fetching instructions via a supplied branch prediction model, with a flush mechanism for mispredictions.

Note

Due to a lack of dependency handling, this model only supports single-cycle instructions, which necessitates a zero-cycle memory model. Attempting to use this model with a multi-cycle memory model will result in incorrect execution and undefined behaviour.

Out-of-order

The out-of-order model simulates a complex superscalar out-of-order core, similar to those found in modern high-performance processors. This core contains the following pipeline stages:

  • Fetch

  • Decode

  • Rename

  • Dispatch/Issue

  • Execute

  • Writeback

  • Commit

To facilitate out-of-order execution, this model contains the following features:

  • A reorder buffer for holding in-flight instructions

  • A set of reservation stations for dependency management and instruction scheduling

  • A load/store queue to enable out-of-order memory access while ensuring memory order correctness

  • A register alias table to allow register renaming and false dependency elimination

This model also supports speculative execution, using a supplied branch prediction model, and is capable of selectively flushing only mispredicted instructions from the pipeline while leaving correct instructions in place.

Current Hardware Models

Through SimEng’s configurable options, the above archetypes can be transformed into models based on existing processors. More information on the configurable options of SimEng can be found here.

The current existing processors have supplied configuration files:

Adding model configuration options

SimEng utilises a scheme which defines value expectations on mandatory and optional configuration options. The value expectations consist of:

  • A default value to be used when creating an in-code default config file

  • A boolean to describe whether the config option is mandatory or optional

  • Either a set of values or an upper and lower bound which the supplied config option must conform to

The scheme is implemented through the ExpectationNode class which creates a tree structure where each node holds the above expectations. The tree structure should map one-to-one to the structure of the passed config file. For example:

With the YAML:

Core:
    ISA: AArch64
    Simulation-Mode: outoforder

The ExpectationNode tree structure would be:

ExpectationNode structure example

The tree structure formed with ExpectationNode instances is constructed through the addChild(...) utility of the ExpectationNode class. Starting at a root node, addChild(...) is used to create various branches of the tree which map to the various config options as described in the above diagram. For those nodes which hold config values, either setValueBounds(...) or setValueSet(...) is used to set the config value restrictions.

Creating a new expectation

Many examples of utilising the ExpectationNode class to set the expectation of a specific config value exist in src/lib/config/ModelConfig.cc. To elaborate on these examples, below is a simplified outline of how you would create the expectations for the above diagram. As with src/lib/config/ModelConfig.cc, expectations_ is treated as the blank root node from which we construct the tree.

First, we create the expectation of the parent config key Core. The addChild(...) utility takes a new ExpectationNode instance which is appended to the vector of children in the parent ExpectationNode instance; thus forming a new branch of the eventual tree structure. To create a new ExpectationNode instance, the ExpectationNode::createExpectation(...) utility can be used. There are two variants of the function, namely:

1.
    createExpectation(std::string key, bool optional = false)

Which is used for those nodes whose corresponding config option has no value (only a key and children).

2.
    template <typename T>
    createExpectation(T defaultValue, std::string key, bool optional = false)

Which is used for those nodes whose corresponding config option does have a value. The function is templated to support different config value types (e.g. T = string, integer, floating point, etc).

For the expectation on the parent config key Core, given it holds no value, we don’t need to set any value expectations. Therefore, the first createExpectation can be used.

expectations_.addChild(ExpectationNode::createExpectation("Core"));

With both the ISA and Simulation-Mode config options which do have values, the second createExpectation must be used. First, we create the ExpectationNode instances for them and add them to the children of the new “Core” ExpectationNode instance. For simplicity of the example, we have set the default values of the ISA and Simulation-Mode config options to AArch64 and emulation respectively.

expectations_["Core"].addChild(
    ExpectationNode::createExpectation<std::string>("AArch64", "ISA"));

expectations_["Core"].addChild(
    ExpectationNode::createExpectation<std::string>("emulation",
                                              "Simulation-Mode"));

Then we set the value restrictions as described in the above diagram:

expectations_["Core"]["ISA"].setValueSet(
    std::vector<std::string>{"AArch64", "rv64"});

expectations_["Core"]["Simulation-Mode"].setValueSet(
    std::vector<std::string>{"emulation", "inorderpipelined", "outoforder"});

Wildcard nodes

To reduce code duplication, the concept of mapping one set of expectations to many config values has been implemented. Denoted as a wildcard node, these nodes can be used to specify the value expectations on a config option which has multiple instances of the same value format/structure. For example, in the “Latencies” config option, many latencies can be defined as seen below:

Latencies:
0:
    Instruction-Groups:
    - INT_SIMPLE_ARTH
    - INT_SIMPLE_LOGICAL
    Execution-Latency: 2
    Execution-Throughput: 2
1:
    Instruction-Groups:
    - INT_SIMPLE_ARTH_NOSHIFT
    - INT_SIMPLE_LOGICAL_NOSHIFT
    Execution-Latency: 1
    Execution-Throughput: 1
2:
    Instruction-Groups:
    - INT_MUL
    Execution-Latency: 5
    Execution-Throughput: 1

Taking the “Execution-Latency” option as an example, rather than setting the expectation for an unknown number of occurrences, we can instead write:

expectations_["Latencies"].addChild(
  ExpectationNode::createExpectation<uint16_t>(0, wildcard));

expectations_["Latencies"][wildcard].addChild(
  ExpectationNode::createExpectation<uint16_t>(1, "Execution-Latency"));
expectations_["Latencies"][wildcard]["Execution-Latency"]
  .setValueBounds<uint16_t>(1, UINT16_MAX);

When validating all config options under the “Latencies” key, the wildcard node created will be used for each option.

Sequence Nodes

Similar to the motivation behind the wildcard node usage, a value expectation can be set to be applied to all values in a YAML sequence/list/array/etc. By calling .setAsSequence() on an instance of ExpectationNode, its value expectations will be applied to a YAML sequence of values. Below is an example of when you might use this.

Ports:
0:
    Portname: Port 0
    Instruction-Group-Support:
    - INT_SIMPLE
    - INT_MUL
    - FP
1:
    Portname: Port 1
    Instruction-Group-Support:
    - INT
    - FP

To apply a value expectation for all values in the “Instruction-Group-Support” options, we’d write:

expectations_["Ports"][wildcard].addChild(
    ExpectationNode::createExpectation<std::string>(
        "ALL", "Instruction-Group-Support", true));

expectations_["Ports"][wildcard]["Instruction-Group-Support"].setValueSet(
    {vector of ISA-specific instruction groups});
expectations_["Ports"][wildcard]["Instruction-Group-Support"].setAsSequence();

We once again use a wildcard node here as all options under the “Ports” config key are repeated an unknown number of times.

Additional options

If any form of config value manipulation/verification is required, for example ensuring each reservation station port has an associated execution unit, this can be done in postValidation() within src/lib/config/ModelConfig.cc.