ETISS includes an example implementation of the OR1K cpu architecture.
It is located in ArchImpl/OR1K/ and should be refered to for examples.
For simplification the architecture in this guide will be referred to as X.
Step 1: Create a workfolder and necessary files
First of all a directory for your cpu architecture should be created. It is recommended to create a folder in ArchImpl with the name of the cpu architecture (analogue to ArchImpl/OR1K/: ArchImpl/X/)
For the following steps a header file for the cpu state structure, a header and source file for the etiss::CPUArch implementation and a source file containing an interface for etiss are needed. The following empty files should be created in your ArchImpl/X/ folder:
X.h
XArch.h
XArch.cpp
XArchLib.cpp
CMakeLists.txt
For a how-to for the creation of a CMakeLists.txt file please have a look at How to create a sub project for ETISS
Step 2: Implement a CPU state structure
ETISS requires the state of a cpu to be stored in a structure. The basic CPU structure defined as ETISS_CPU in include_c/etiss/jit/CPU.h contains fields for cpu time, instruction pointer and cycle time. This structure needs to be extended for a cpu architecture. The new structure should be defined in X.h .
NOTE: the code in X.h must be C compliant code (not just C++) since it needs to be used at runtime for just in time code compilation.
The content of X.h should look like this:
#ifndef X_X_H_
#define X_X_H_
#include "etiss/jit/CPU.h"
#ifdef __cplusplus
extern "C" {
#endif
#pragma pack(push, 1)
struct X {
};
#pragma pack(pop)
typedef struct X X;
#ifdef __cplusplus
}
#endif
#endif
basic cpu state structure needed for execution of any cpu architecture.
Additionally it is neccessary to support a VirtualStruct to allow access to the fields by various plugins. Please have a look at the documentation of etiss::VirtualStruct and etiss::VirtualStruct::Field. This needs to be implemented by the etiss::CPUArch object.
Step3 : Implement etiss::CPUArch
To enable ETISS to execute instructions of a cpu architecture, etiss::CPUArch needs to be implemented to handle translation of instructions to C code and to provide basic architecture related information and functions.
The implementation should be done in XArch.h/.cpp.
XArch.h:
#include "X.h"
#include "XTimer.h"
#include "XGDBCore.h"
public:
XArch();
~XArch();
private:
std::set<std::string> listenerSupportedRegisters_;
std::set<std::string> registers32_;
std::set<std::string> headers_;
XGDBCore xgdbcore_;
};
contains neccesary interfaces for instruction translation.
virtual std::shared_ptr< etiss::VirtualStruct > getVirtualStruct(ETISS_CPU *cpu)=0
this function must return a valid pointer to a virtual struct
virtual etiss::Plugin * newTimer(ETISS_CPU *cpu)
create a simple default timer implementaion instance for this architecture.
virtual void deleteTimer(etiss::Plugin *timer)
delete timer instance
virtual const std::set< std::string > & getListenerSupportedRegisters()=0
the interface to translate instructions of and processor architecture
virtual void deleteCPU(ETISS_CPU *)=0
delete cpu structure
virtual void resetCPU(ETISS_CPU *cpu, etiss::uint64 *startpointer)=0
reset cpu (structure)
virtual const std::set< std::string > & getHeaders() const =0
set of code header files e.g.
virtual etiss::plugin::gdb::GDBCore & getGDBCore()
returns arch dependent gdb functions.
virtual unsigned getInstructionSizeInBytes()=0
size of one instruction/ smalest data unit for instructions of variable length
virtual void deleteInterruptVector(etiss::InterruptVector *vec, ETISS_CPU *cpu)
delete an allocated interrupt vector object
virtual etiss::InterruptVector * createInterruptVector(ETISS_CPU *cpu)
allocate a new interrupt vector object for the given cpu
virtual unsigned getMaximumInstructionSizeInBytes()=0
used for variable instruction size and delay slots
virtual ETISS_CPU * newCPU()=0
allocate new cpu structure
virtual etiss::int32 handleException(etiss::int32 code, ETISS_CPU *cpu)
translate/process exceptions that occur at runtime
interface to set interrupt bits
base plugin class that provides access to different plugin functions if present
provides to architecture dependent registers as defined by gdb
XArch.cpp:
#include "XArch.h"
XArch::XArch() : CPUArch("X"){
registers32_.insert("timerConfig");
registers32_.insert("X_registers1");
headers_.insert("X.h");
headers_.insert("etiss/jit/fpu/softfloat.h");
listenerSupportedRegisters_.insert("timerConfig");
}
XArch::~XArch() {
}
const std::set<std::string> & XArch::getListenerSupportedRegisters(){
return listenerSupportedRegisters_;
}
std::shared_ptr<etiss::VirtualStruct> getVirtualStruct(
ETISS_CPU * cpu){
X * xcpu = (X*)cpu;
*ret,
std::string("X_registers[12]"),
std::string("X_register_12"),
4,
false,
[ret](){
return (
uint64_t) ((X*)ret->structure_)->X_registers[12];
},
((X*)ret->structure_)->X_registers[12] = (
uint32_t)val;
}
);
return ret;
}
return new XTimer();
}
delete timer;
}
}
if (startpointer)
for (int i = 0;i<16;i++)
((X*)cpu)->X_registers[i] = 0;
((X*)cpu)->timercfg = 0x43847594;
}
delete cpu;
}
unsigned XArch::getMaximumInstructionSizeInBytes(){
return 8;
}
unsigned XArch::getInstructionSizeInBytes(){
return 4;
}
const std::set<std::string> & XArch::getHeaders() const{
return headers_;
}
switch (code){
case etiss::RETURNCODE::DBUS_READ_ERROR:
default:
return code;
}
}
std::vector<etiss::uint32*> interruptRegisters;
interruptRegisters.push_back(interruptRegister);
std::vector<etiss::uint32*> interruptMasks;
interruptMasks.push_back(interruptMask);
return new MappedInterruptVector<etiss::uint32>(interruptRegisters,interruptMasks);
}
delete vec;
}
return xgdbcore_;
}
static __inline__ uint32_t
static __inline__ uint64_t
a Field instance represents e.g.
static const int W
write flag
static const int R
read flag
abstract representation of an module of a simulation which could be a embedded device of the cpu of a...
bool addField(Field *f, bool noerrorprint=false)
MM_EXPORT const int32_t NOERROR
Page Table Entry (PTE) defines the composition of Page Frame Number (PFN) and relavant flags.
etiss_uint64 instructionPointer
pointer to next instruction.
If the above example has been modified to remove timer and gdbcore support then the next two chapters can be skipped.
Besides implmenting the CPUArch interface it is necessary to implement the inherited etiss::TranslatorPlugin interface. That interface is responsible for adding translation functionality. For further information please refer to the classes defined in Instruction.h . The classes etiss::instr::ModedInstructionSet, etiss::instr::VariableInstructionSet, etiss::instr::InstructionSet and etiss::instr::Instruction implement lookup and translation functionality. etiss::instr::InstructionCollection, etiss::instr::InstructionClass, etiss::instr::InstructionGroup and etiss::instr::InstructionDefinition are helper classes that allow to store neccessary information for the previous group of classes in an ordered manner. The OR1K example uses those classes to define all instructions and uses the etiss::instr::InstructionCollection::addTo(ModedInstructionSet &set, bool &ok) function to push the definitions into the ModedInstruction set and sub sets.
A simple example of an fictive instruction set implementation provided by a CPUArch using above classes:
[...]
static InstructionDefinition add(
ARMvX_all,
"add",
{
CodePart & part = cs.
append(CodePart::APPENDEDRETURNINGREQUIRED);
part.code = "((X*)cpu)->X_registers[1] = ((X*)cpu)->X_registers[2] + ((X*)cpu)->X_registers[3];";
return true;
},
0,
0,
);
bool ok = true;
CortexXX.addTo(mis,ok);
if (!ok)
}
void append(const CodePart &part, CodePart::TYPE type)
maps to VariableInstructionSet
maps to ModedInstructionSet
holds etiss::instr::VariableInstructionSet instances for different modes.
uint32_t parse_i32(const char *s)
void log(Verbosity level, std::string msg)
write log message at the given level.
Step 4 (only required if newTimer(ETISS_CPU * cpu) is implemented) : Implement a timer plugin
It is recommended to provide a Timer plugin to enable common functionality out of the box. In this example the timer plugin class is named XTimer (see etiss::Plugin * XArch::newTimer(ETISS_CPU * cpu) of Step 3).
The additional files XTimer.h and XTimer.cpp need to be created in the ArchImpl/X/ folder.
This timer example assumes 1. that it is acceptable that the timer event only fires in between blocks (a more accurate timer would come at greater performance cost) and 2. that it needs to configure itself according to the value of X::timercfg. This results in the use of 2 plugin interface: etiss::CoroutinePlugin whose etiss::CoroutinePlugin::execute function is called in between blocks and etiss::RegisterDevicePlugin whose eitss::RegisterDevicePlugin::changedRegister function as called upon signaled register changes
XTimer.h:
#include "X.h"
public:
XTimer();
virtual ~XTimer();
protected:
private:
bool enabled_;
};
plugins for extensions to code translation and instruction execution
this plugin will be called before a block is executed.
virtual etiss::int32 execute()=0
called before a block and may act in the same way as a block
virtual std::string _getPluginName() const =0
RegisterDevicePlugin::changedRegister is called if a supported register has been changed.
virtual void changedRegister(const char *name)=0
called when an observable register has been changed
XTimer.cpp:
#include "XTimer.h"
XTimer::XTimer(){
enabled_ = false;
}
XTimer::~XTimer(){}
if (enabled_){
if (true) {
return 71;
}
}
return 0;
}
void XTimer::changedRegister(const char * name){
if (std::string(name) == std::string("timerConfig")){
enabled_ = ((X*)plugin_cpu_)->timercfg != 0;
}
}
std::string XTimer::_getPluginName() const{
return "XTimer";
}
Step 5 (only required if getGDBCore() is implemented) : Implement etiss::gdb::GDBCode to support a GDB server
It is recommended to provide a etiss::plugin::gdb::GDBCore implementation. This minimalistic and easy to implement interface allows to use gdb to debug a simulated program.
The additional files XGDBCore.h and XGDBCore.cpp need to be created in ArchImpl/X/.
XGDBCore.h:
};
virtual unsigned mappedRegisterCount()
returns the number of registers in the gdb defined register list
virtual std::string mapRegister(unsigned index)
the returned string identifies the register at the given index as defined by gdb.
virtual bool isLittleEndian()
returns true if the values are expected to be little endian
XGDBCore.cpp:
#include "XGDBCore.h"
std::string XGDBCore::mapRegister(unsigned index){
switch (index){
case 0:
return "timerConfig";
case 1:
return "X_register1";
default:
return "";
}
}
unsigned XGDBCore::mapRegister(std::string name){
if (name == std::string("timerConfig")){
return 0;
}
if (name == std::string("X_register1")){
return 1;
}
return INVALIDMAPPING;
}
unsigned XGDBCore::mappedRegisterCount(){
return 2;
}
bool XGDBCore::isLittleEndian(){
return true;
}
Step 6: Implement the loading interface
ETISS preferably loads a cpu architecture as a dynamic library at runtime. To do this it is necessary to implement some functions to find the CPUArch implementation(s).
Additional to the shown functions additional functions can be used to provide header files at runtime. Refer to The LibraryInterface file copy extension for an explanation on how to store files within a library and extract them at runtime. This should be used to provide the X.h header file for runtime compilation. Please note that all files with the name included_c/* can be included during runtime compilation as #include "<b>*</b>".
XArchLib.cpp:
#define ETISS_LIBNAME X
#include "XArch.h"
extern "C"{
unsigned X_countCPUArch(){
return 1;
}
const char * X_nameCPUArch(unsigned index){
switch (index){
case 0:
return "X";
default:
return "";
}
}
switch (index){
case 0:
return new XArch();
default:
return 0;
}
}
delete arch;
}
}
defines the functions needed for a library that provides etiss::CPUArch implementations
ETISS_PLUGIN_EXPORT etiss::CPUArch std::map< std::string, std::string > options
create new instance of the CPUArch type at index
#define ETISS_LIBRARYIF_VERSION_FUNC_IMPL
Step 7: Build and run the Library
Finally a makefile or a CMakeLists.txt is needed to build the new library with the X architecture implementation as a dynamic library. Currently the default project CMakeLists.txt include provided by ETISS (How to create a sub project for ETISS) doesn't support stripping of symbols. Plans are to support it at later time when cmake provides better support for this.
Makefile:
DEBUG?=1
CC=gcc
ifeq ($(DEBUG),0)
DBGPARAM =
OPTLEVEL?=-O3
else
DBGPARAM =-g
OPTLEVEL?=
endif
ETISS_FOLDER=../..
CFLAGS=-std=
c++0
x -
c -MMD -Wall -Werror -fPIC $(OPTLEVEL) $(DBGPARAM) -DDEBUG=$(DEBUG) -
I$(ETISS_FOLDER)/include -
I$(ETISS_FOLDER)/include_c
XArch.o : XArch.cpp
$(CC) $(CFLAGS) XArch.cpp
XGDBCore.o : XGDBCore.cpp
$(CC) $(CFLAGS) XGDBCore.cpp
XTimer.o : XTimer.cpp
$(CC) $(CFLAGS) XTimer.cpp
XArchLib.o: XArchLib.cpp
$(CC) $(CFLAGS) XArchLib.cpp
-include .
__device__ __2f16 float c
int __ovld __cnfn all(char x)
Returns 1 if the most significant bit in all components of x is set; otherwise returns 0.
Once the dynamic library was build it is available in ETISS by default if placed in ArchImpl/X/ or can be loaded with void etiss::loadLibrary(std::string path,std::string name). Use etiss::listLibraries() and etiss::listCPUArchs() to view the status of loaded libraries.
Refer to Removing symbols from a shared library once the api of the new library is out of it's testing state or runtime linkage errors arise.