CPU registers generation

To avoid code duplication and exposing some unimportant implementation details, every TranslationCPU implementation may have a partial class that will contain only register definitions. These definitions all have a very similar structure and are generated using a T4 Preprocessor from a *.tt template.

To generate registers for a new processor, create a <CPUName>Registers.tt file in the Registers directory. Its properties should be set as follows:

  • build action - none
  • copy to output directory - do not copy
  • custom tool - TextTemplatingFileGenerator.

The contents of this file is presented in the example below.

The generation framework is based on a set of T4 templates that parses header files from translation libraries written in C and generates corresponding C# classes providing read/write access to selected registers. Parsing is handled by the code provided by the RegisterEnumParserContent.tt template.

There are two basic template files required to create a C# class with CPU registers-related code:

  • RegisterTemplateDefinitions.tt
  • RegisterTemplate.tt

Both files are located in the EmulatorPeripheralsPeripheralsCPURegisters folder.

The first one provides the definitions of configuration variables used while generating code and must always be included in the final template file. What is more, two of those variables, CLASS_NAME and HEADER_FILE must be set before including the second template.

CLASS_NAME
specifies the name of a class that should be generated by the template. It should be the name of the CPU class (marked as a partial class) in order to link properly.
HEADER_FILE
specifies the relative path to a header file with the enum declaration.

There are some optional configuration variables that can be used to control generated code:

PC_REGISTER
name of a register that contains address of current instruction, so called program counter. TranslationCPU class requires that all derived classes provide property called PC, but this register can be named differently on some architectures. In such case it is required to set PC_REGISTER value to the name of this register in order to generate working code.
DEFINES
list of defined elements. Our parser has built-in support for simple #ifdef statements and is able to ignore parts of enum when argument of this statement is not contained in DEFINES list.
BEFORE_WRITE_HOOKS & AFTER_WRITE_HOOKS
list of callbacks to invoke before/after writing value to the register. This is a dictionary where key is a name of the register and value is a name of method to be called. Before callback methods must have signature of Func<T, T> and after callback methods must have signature of Action<T> where T is a type that depends on register width.
IGNORED_REGISTERS
list of registers names that should be ignored; no properties will be generated for those registers. This is useful when dealing with derived classes that already have some registers implemented.

Header file format

Registers should be declared as values of an enum called Registers which is declared as follows:

typedef enum {
    ...
} Registers;

Values of this enum should have proper format in order to be parsed:

NAME[_INDEX]_WIDTH = VALUE

where [_INDEX] is optional, e.g.:

A_32 = 0,
B_1_64 = 1,
B_2_64 = 2

NAME can consist of letters and numbers.

Simple CPU register template file

Below we present a minimal template file for a fake CPU architecture:

<#@ template language="C#" #>
<#@ include file="RegisterTemplateDefinitions.tt" #>
<#
    CLASS_NAME = "FakeCPU";
    HEADER_FILE = "Emulator/Cores/tlib/arch/fake/cpu_registers.h";
#>
<#@ include file="RegisterTemplate.tt" #>

where cpu_registers.h is:

#include "cpu-defs.h"

typedef enum {
    R_32 = 0,
    X_1_8 = 1,
    X_2_8 = 2,
    X_3_8 = 3
} Registers;

The C# code generated as a result of running this template is as follows:

/********************************************************
*
* Warning!
* This file was generated automatically.
* Please do not edit. Changes should be made in the
* appropriate *.tt file.
*
*/
using System;
using System.Collections.Generic;
using Emul8.Peripherals.CPU.Registers;

namespace Emul8.Peripherals.CPU
{
    public partial class FakeCPU
    {
        [Register]
        public UInt32 R
        {
            get
            {
                return GetRegisterValue((int)FakeCPURegisters.R);
            }
            set
            {
                SetRegisterValue((int)FakeCPURegisters.R, value);
            }
        }

        public RegistersGroup<byte> X { get; private set; }

        protected override void InitializeRegisters()
        {
            indexValueMapX = new Dictionary<int, FakeCPURegisters>
            {
                { 1, FakeCPURegisters.X1 },
                { 2, FakeCPURegisters.X2 },
                { 3, FakeCPURegisters.X3 },
            };
            X = new RegistersGroup<UInt32>(
                indexValueMapX.Keys,
                i => GetRegisterValue((int)indexValueMapX[i]),
                (i, v) => SetRegisterValue((int)indexValueMapX[i], v));

        }

        private Dictionary<int, FakeCPURegisters> indexValueMapR;
    }

    public enum FakeCPURegisters
    {
        R = 0,
        X1 = 1,
        X2 = 2,
        X3 = 3,
    }
}

Warning

The tt files are analysed and executed when saved. To recreate the underlying cs file, you have to save the file in your IDE. When introducing changes in RegisterTemplateDefinitions.tt or RegisterTemplate.tt it is necessary to recreate all templates that include them.