OpenCores
URL https://opencores.org/ocsvn/forwardcom/forwardcom/trunk

Subversion Repositories forwardcom

Compare Revisions

  • This comparison shows the changes necessary to convert path
    /
    from Rev 51 to Rev 52
    Reverse comparison

Rev 51 → Rev 52

/forwardcom/bintools/emulator1.cpp
0,0 → 1,1275
/**************************** emulator1.cpp ********************************
* Author: Agner Fog
* date created: 2018-02-18
* Last modified: 2021-07-14
* Version: 1.11
* Project: Binary tools for ForwardCom instruction set
* Description:
* Basic functionality of the emulator
*
* Copyright 2018-2021 GNU General Public License http://www.gnu.org/licenses
*****************************************************************************/
 
#include "stdafx.h"
 
 
///////////////////
// CEmulator class
///////////////////
 
// constructor
CEmulator::CEmulator() {
memory = 0; // initialize
memsize = 0;
stackp = 0;
// set defaults. may be changed by command line or file header:
MaxVectorLength = 0x80; // 128 bytes = 1024 bits
maxNumThreads = 1; // multithreading not supported yet
stackSize = 0x100000; // 1 MB. data stack size for main thread
callStackSize = 0x800; // call stack size for main thread
heapSize = 0; // heap size for main thread
environmentSize = 0x100; // maximum size of environment and command line data
}
 
// destructor
CEmulator::~CEmulator() {
if (memory) delete[] memory; // free allocated program memory
}
 
// start
void CEmulator::go() {
threads.setSize(maxNumThreads); // initialize threads
load(); // load executable file
if (err.number()) return;
if (fileHeader.e_flags & EF_RELOCATE) relocate();
if (err.number()) return;
 
// set up disassembler for output list
if (cmd.outputListFile) disassemble();
 
// prepare main thread
threads[0].setRegisters(this);
// run main thread
threads[0].run();
}
 
// load executable file into memory
void CEmulator::load() {
const char * filename = cmd.getFilename(cmd.inputFile);
read(filename); // read executable file
if (err.number()) return;
split(); // extract components
if (getFileType() != FILETYPE_FWC || fileHeader.e_type != ET_EXEC) {
err.submit(ERR_LINK_FILE_TYPE_EXE, filename);
return;
}
// calculate necessary memory size
uint64_t blocksize = 0; // size of block of segments with same base pointer
uint32_t ph; // program header index
uint64_t align; // program header alignment
uint64_t address; // current address
uint32_t flags, lastflags; // flags of program header
bool hasDataSegment = false; // check if there is a data segment header
const uint32_t dataflags = SHF_READ | SHF_WRITE | SHF_ALLOC | SHF_DATAP; // expected flags for data segment
 
memsize = environmentSize; // reserve space for environment in the beginning
for (ph = 0; ph < programHeaders.numEntries(); ph++) {
if (programHeaders[ph].p_vaddr == 0) {
// start of a new block
memsize += blocksize;
 
if ((programHeaders[ph].p_flags & SHF_READ) && (ph+1 == programHeaders.numEntries())) {
// This is the last data section
// Make space for reading a vector beyond the end
// Note: we cannot do this after the const section because the space there must have fixed size, provided by the linker
uint32_t extraSpace = MaxVectorLength;
if (extraSpace < DATA_EXTRA_SPACE) extraSpace = DATA_EXTRA_SPACE;
programHeaders[ph].p_memsz += extraSpace;
}
align = (uint64_t)1 << programHeaders[ph].p_align;
memsize = (memsize + align - 1) & -(int64_t)align;
blocksize = programHeaders[ph].p_memsz;
}
else {
// continuation of previous block
blocksize += programHeaders[ph].p_vaddr + programHeaders[ph].p_memsz;
}
if ((programHeaders[ph].p_flags & dataflags) == dataflags) hasDataSegment = true;
}
if (!hasDataSegment) { // there is no data segment. make one for the stack
ElfFwcPhdr dataSegment;
zeroAllMembers(dataSegment);
dataSegment.p_type = PT_LOAD;
dataSegment.p_flags = dataflags;
dataSegment.p_align = 3;
programHeaders.push(dataSegment);
}
 
// end of last block
memsize += blocksize;
align = (uint64_t)1 << MEMORY_MAP_ALIGN;
memsize = (memsize + align - 1) & -(int64_t)align;
// add stack and heap
memsize += stackSize + heapSize;
// allocate memory
memory = new int8_t[size_t(memsize)];
if (!memory) {
err.submit(ERR_MEMORY_ALLOCATION);
return;
}
memset(memory, 0, size_t(memsize));
// start making memory map
address = 0;
flags = SHF_READ | SHF_IP; lastflags = flags;
SMemoryMap mapentry = {address, flags};
memoryMap.push(mapentry);
// make space for environment
address = environmentSize;
for (ph = 0; ph < programHeaders.numEntries(); ph++) {
flags = programHeaders[ph].p_flags & (SHF_PERMISSIONS | SHF_BASEPOINTER);
if (flags != lastflags && (lastflags & SHF_IP) && (!(flags & SHF_IP))) {
// insert stack here
align = 8;
address = (address + align - 1) & -(int64_t)align;
flags = SHF_DATAP | SHF_READ | SHF_WRITE;
mapentry.startAddress = address;
mapentry.access_addend = flags;
memoryMap.push(mapentry);
address += stackSize;
stackp = address;
lastflags = flags;
flags = programHeaders[ph].p_flags & (SHF_PERMISSIONS | SHF_BASEPOINTER);
}
if ((flags & SHF_PERMISSIONS) != (lastflags & SHF_PERMISSIONS)) {
// start new map entry
align = (uint64_t)1 << programHeaders[ph].p_align;
address = (address + align - 1) & -(int64_t)align;
mapentry.startAddress = address;
mapentry.access_addend = flags;
memoryMap.push(mapentry);
}
if (programHeaders[ph].p_vaddr == 0) {
switch (flags & SHF_BASEPOINTER) {
case SHF_IP:
ip0 = address; break;
case SHF_DATAP:
datap0 = address; break;
case SHF_THREADP:
threadp0 = address; break;
}
}
// check integrity before copying data
if (address + programHeaders[ph].p_filesz > memsize
|| programHeaders[ph].p_filesz > programHeaders[ph].p_memsz
|| programHeaders[ph].p_offset + programHeaders[ph].p_filesz > dataSize()) {
err.submit(ERR_ELF_INDEX_RANGE);
return;
}
// store address in program header
programHeaders[ph].p_vaddr = address;
// copy data
memcpy(memory + address, dataBuffer.buf() + programHeaders[ph].p_offset, size_t(programHeaders[ph].p_filesz));
address += programHeaders[ph].p_memsz;
lastflags = flags;
}
// make terminating entry
mapentry.startAddress = address;
mapentry.access_addend = 0;
memoryMap.push(mapentry);
}
 
// relocate any absolute addresses and system function id's
void CEmulator::relocate() {
uint32_t r; // relocation index
uint32_t ph; // program header index
uint32_t rsection; // relocated section
uint32_t phFistSection; // first section covered by program header
uint32_t phNumSections; // number of sections covered by program header
uint64_t sourceAddress; // address of relocation source
uint64_t targetAddress; // address of relocation target
const char * symbolname; // name of target symbol
bool found; // program header found
for (r = 0; r < relocations.numEntries(); r++) {
// loadtime relocations are listed first. stop at first non-loadtime record
if (!(relocations[r].r_type & R_FORW_LOADTIME)) break;
// find program header containing relocated section
rsection = relocations[r].r_section;
found = false;
for (ph = 0; ph < programHeaders.numEntries(); ph++) {
phFistSection = (uint32_t)programHeaders[ph].p_paddr;
phNumSections = (uint32_t)(programHeaders[ph].p_paddr >> 32);
if (rsection >= phFistSection && rsection < phFistSection + phNumSections) {
found = true; break;
}
}
if (!found) {
err.submit(ERR_REL_SYMBOL_NOT_FOUND); continue;
}
// calculate address of relocation source
sourceAddress = programHeaders[ph].p_vaddr + sectionHeaders[rsection].sh_addr - sectionHeaders[phFistSection].sh_addr + relocations[r].r_offset;
if (sourceAddress >= memsize) {
err.submit(ERR_ELF_INDEX_RANGE); continue;
}
if ((relocations[r].r_type & R_FORW_RELTYPEMASK) == R_FORW_ABS) {
// needs absolute address of target
uint32_t symi = relocations[r].r_sym;
if (symi >= symbols.numEntries()) {
err.submit(ERR_ELF_INDEX_RANGE); return;
}
ElfFwcSym & targetSym = symbols[symi];
uint32_t tsec = targetSym.st_section; // section of target symbol
// find program header containing target section
found = false;
for (ph = 0; ph < programHeaders.numEntries(); ph++) {
phFistSection = (uint32_t)programHeaders[ph].p_paddr;
phNumSections = (uint32_t)(programHeaders[ph].p_paddr >> 32);
if (tsec >= phFistSection && tsec < phFistSection + phNumSections) {
found = true; break;
}
}
if (!found) {
err.submit(ERR_REL_SYMBOL_NOT_FOUND); continue;
}
// calculate target address
targetAddress = programHeaders[ph].p_vaddr + sectionHeaders[rsection].sh_addr - sectionHeaders[phFistSection].sh_addr + targetSym.st_value;
if (targetAddress >= memsize) {
err.submit(ERR_ELF_INDEX_RANGE); continue;
}
// scale (scaling of absolute addresses is rarely used, but allowed)
targetAddress >>= (relocations[r].r_type & R_FORW_RELSCALEMASK);
// insert relocation of desired size
switch (relocations[r].r_type & R_FORW_RELSIZEMASK) {
case R_FORW_8: // 8 bit relocation size
if (targetAddress >> 8) goto OVERFLW;
*(memory + sourceAddress) = int8_t(targetAddress);
break;
case R_FORW_16: // 16 bit relocation size
if (targetAddress >> 16) goto OVERFLW;
*(uint16_t*)(memory + sourceAddress) = uint16_t(targetAddress);
break;
case R_FORW_32: // 32 bit relocation size
if (targetAddress >> 32) goto OVERFLW;
*(uint32_t*)(memory + sourceAddress) = uint32_t(targetAddress);
break;
case R_FORW_32LO: // Low 16 of 32 bits relocation
*(uint16_t*)(memory + sourceAddress) = uint16_t(targetAddress);
break;
case R_FORW_32HI: // High 16 of 32 bits relocation
if (targetAddress >> 32) goto OVERFLW;
*(uint16_t*)(memory + sourceAddress) = uint16_t(targetAddress >> 16);
break;
case R_FORW_64: // 64 bit relocation size
*(uint64_t*)(memory + sourceAddress) = uint64_t(targetAddress);
break;
case R_FORW_64LO: // Low 32 of 64 bits relocation
*(uint32_t*)(memory + sourceAddress) = uint32_t(targetAddress);
break;
case R_FORW_64HI: // High 32 of 64 bits relocation
*(uint32_t*)(memory + sourceAddress) = uint32_t(targetAddress >> 32);
break;
default:
OVERFLW:
symbolname = symbolNameBuffer.getString(targetSym.st_name);
err.submit(ERR_LINK_RELOCATION_OVERFLOW, symbolname);
}
}
else {
// to do: get system function id from name
}
}
}
 
void CEmulator::disassemble() { // make disassembly listing for debug output
disassembler.copy(*this); // copy ELF file
disassembler.getComponents1(); // set up instruction list, etc.
if (err.number()) return;
//disassembler.outputFile = cmd.fileNameBuffer.pushString("ddd.txt");
disassembler.debugMode = 1; // produce disassembly for debug display/list
disassembler.go(); // disassemble
if (err.number()) return;
disassembler.getLineList(lineList); // get cross reference list from address to disassembly output file
lineList.sort(); // only needed if multiple segments in lineList
disassembler.getOutFile(disassemOut); // get disassembly output file
// replace all linefeeds by end of string
for (uint32_t i = 0; i < disassemOut.dataSize(); i++) {
if ((uint8_t)disassemOut.buf()[i] < ' ') disassemOut.buf()[i] = 0;
}
}
 
 
/////////////////
// CThread class
/////////////////
 
// constructor
CThread::CThread() {
numContr = 1 | (1<<MSK_SUBNORMAL); // default numContr. Bit 0 must be 1;
enableSubnormals (numContr & (1<<MSK_SUBNORMAL)); // enable or disable subnormal numbers
lastMask = numContr;
ninstructions = 0;
mapIndex1 = mapIndex2 = mapIndex3 = 0; // indexes into memory map
callDept = 0;
listLines = 0;
tempBuffer = 0;
}
 
// destructor
CThread::~CThread() {
if (tempBuffer != 0) {
delete[] tempBuffer; // free temporary buffer
}
}
 
// initialize registers etc. from values in emulator
void CThread::setRegisters(CEmulator * emulator) {
this->emulator = emulator;
this->memory = emulator->memory; // program memory
memoryMap.copy(emulator->memoryMap); // memory map
// ip_base = emulator->ip_base; // reference point for code and read-only data
ip0 = emulator->ip0; // reference point for code and read-only data
datap = emulator->datap0 + emulator->fileHeader.e_datap_base; // base pointer for writeable data
threadp = emulator->threadp0 + emulator->fileHeader.e_threadp_base; // base pointer for thread-local data
ip = entry_point = emulator->fileHeader.e_entry + ip0; // start value of instruction pointer
MaxVectorLength = emulator->MaxVectorLength;
tempBuffer = new int8_t[MaxVectorLength * 2]; // temporary buffer for vector operands
memset(registers, 0, sizeof(registers)); // clear all registers
memset(vectorLength, 0, sizeof(vectorLength));
vectors.setDataSize(32*MaxVectorLength);
registers[31] = emulator->stackp; // stack pointer
memset(perfCounters, 0, sizeof(perfCounters)); // reset performance counters
// initialize capability registers
memset(capabilyReg, 0, sizeof(capabilyReg)); // reset capability registers
capabilyReg[0] = 'E'; // brand ID. E = emulator
capabilyReg[1] = FORWARDCOM_VERSION * 0x10000 + FORWARDCOM_SUBVERSION * 0x100; // ForwardCom version
capabilyReg[8] = 0b1111; // support for operand sizes in g.p. registers
capabilyReg[9] = 0b101101111; // support for operand sizes in vector registers
capabilyReg[12] = MaxVectorLength; // maximum vector length
capabilyReg[13] = MaxVectorLength; // maximum vector length for permute
capabilyReg[14] = MaxVectorLength; // maximum block size for permute??
capabilyReg[15] = MaxVectorLength; // maximum vector length compress_sparse and expand_sparse
listFileName = cmd.outputListFile; // name for output list file. to do: add thread number to list file name if multiple threads
}
 
// start running
void CThread::run() {
listStart(); // start writing debug output list
running = 1; terminate = false;
while (running && !terminate) {
 
fetch(); // fetch next instruction
if (terminate) break;
decode(); // decode instruction
if (terminate) break;
execute(); // execute instruction
}
// write debug output
if (listFileName) {
listOut.write(cmd.getFilename(listFileName));
}
}
 
// fetch next instruction
void CThread::fetch() {
// find memory map entry
while (ip < memoryMap[mapIndex1].startAddress) {
if (mapIndex1 > 0) mapIndex1--;
else {
interrupt(INT_ACCESS_EXE); return;
}
}
while (ip >= memoryMap[mapIndex1 + 1].startAddress) {
if (mapIndex1 + 2 < memoryMap.numEntries()) mapIndex1++;
else {
interrupt(INT_ACCESS_EXE); return;
}
}
// check execute permission
if (!(memoryMap[mapIndex1].access_addend & SHF_EXEC)) interrupt(INT_ACCESS_EXE);
// get instruction
pInstr = (STemplate const *)(memory + ip);
}
 
// List of instructionlengths, used in decode()
static const uint8_t lengthList[8] = {1,1,1,1,2,2,3,4};
 
// decode current instruction
void CThread::decode() {
 
listInstruction(ip - ip0); // make debug listing
// decoding similar to CDisassembler::parseInstruction()
op = pInstr->a.op1;
//rs = pInstr->a.rs;
 
// Get format
uint32_t format = (pInstr->a.il << 8) + (pInstr->a.mode << 4); // Construct format = (il,mode,submode)
 
// Get submode
switch (format) {
case 0x200: case 0x220: case 0x300: case 0x320: // submode in mode2
format += pInstr->a.mode2;
break;
case 0x250: case 0x310: // Submode for jump instructions etc.
if (op < 8) {
format += op; // op1 defines sub-format
op = pInstr->b[0] & 0x3F; // OPJ is in IM1 (other positions for opj fixed below
}
else {
format += 8;
}
break;
}
// Look up format details (lookupFormat() is in emulator2.cpp)
fInstr = &formatList[lookupFormat(pInstr->q)];
format = fInstr->format2; // Include subformat depending on op1
 
if (fInstr->imm2 & 0x80) { // alternative position of opj
if (fInstr->imm2 & 0x40) { // no opj
op = 63;
}
else if (fInstr->imm2 & 0x10) {
op = pInstr->b[7] & 0x3F; // OPJ is in high part of IM2 in format A2
}
}
if (fInstr->tmplate == 0xE && pInstr->a.op2 && !(fInstr->imm2 & 0x100)) {
// Single format instruction if op2 != 0 in E template and op2 not used as immediate operand
static SFormat form; // don't initialize static object.
form = *fInstr; // copy format record
form.category = 1; // change category
fInstr = &form; // point to static object
// operand tables for single-format instructions
if (format == 0x207 && pInstr->a.op2 == 1) nOperands = numOperands2071[op]; // table for format 2.0.7
else if (format == 0x226 && pInstr->a.op2 == 1) nOperands = numOperands2261[op]; // table for format 2.2.6
else if (format == 0x227 && pInstr->a.op2 == 1) nOperands = numOperands2271[op]; // table for format 2.2.7
else nOperands = 0xB; // default value when there is no table
}
else {
// operand tables for multi-format instructions
nOperands = numOperands[fInstr->exeTable][op]; // number of source operands (see bit definitions in format_tables.cpp)
}
 
ignoreMask = (nOperands & 0x08) != 0; // bit 3: ignore mask
noVectorLength = (nOperands & 0x10) != 0; // bit 4: vector length determined by execution function
doubleStep = (nOperands & 0x20) != 0; // bit 5: take double steps
dontRead = (nOperands & 0x40) != 0; // bit 6: don't read source operand
unchangedRd = (nOperands & 0x80) != 0; // bit 7: RD is unchanged, not destination
nOperands &= 0x7; // bit 0-2: number of operands
 
// Get operand type
if (fInstr->ot == 0) { // Operand type determined by OT field
operandType = pInstr->a.ot; // Operand type
if (!(pInstr->a.mode & 6) && !(fInstr->vect & 0x11)) {
// Check use of M bit
format |= (operandType & 4) << 5; // Add M bit to format
operandType &= ~4; // Remove M bit from operand type
}
}
else if ((fInstr->ot & 0xF0) == 0x10) { // Operand type fixed. Value in formatList
operandType = fInstr->ot & 7;
}
else if (fInstr->ot == 0x32) { // int32 for even op1, int64 for odd op1
operandType = 2 + (pInstr->a.op1 & 1);
}
else if (fInstr->ot == 0x35) { // Float for even op1, double for odd op1
operandType = 5 + (pInstr->a.op1 & 1);
}
else {
operandType = 0; // Error in formatList. Should not occur
}
 
// Find instruction length
uint8_t instrLength = lengthList[pInstr->i[0] >> 29]; // Length up to 3 determined by il. Length 4 by upper bit of mode
ip += instrLength * 4; // next ip
 
// get address of memory operand
if (fInstr->mem) memAddress = getMemoryAddress();
 
// find operands
if (fInstr->category == 4 && fInstr->jumpSize) {
// jump instruction with self-relative jump address
// check if it uses vector registers
vect = (fInstr->vect & 0x10) && fInstr->tmplate != 0xC && (pInstr->a.ot & 4);
// pointer to address field
const uint8_t * pa = &pInstr->b[0] + fInstr->jumpPos;
// store relative address in addrOperand
switch (fInstr->jumpSize) {
case 1: // sign extend 8-bit offset
addrOperand = *(int8_t*)pa;
break;
case 2: // sign extend 16-bit offset
addrOperand = *(int16_t*)pa;
break;
case 3: // sign extend 24-bit offset
addrOperand = *(int32_t*)pa << 8 >> 8;
break;
case 4: // sign extend 32-bit offset
addrOperand = *(int32_t*)pa;
break;
case 8: // 64-bit offset
addrOperand = *(int64_t*)pa;
break;
default:
addrOperand = 0;
err.submit(ERR_INTERNAL);
}
// pointer to immediate field
const uint8_t * pi = &pInstr->b[0] + fInstr->immPos;
// get immediate operand or last register operand
if (fInstr->opAvail & 1) {
// last operand is immediate. sign extend or convert it into parm[2]
switch (fInstr->immSize) {
case 1:
parm[2].qs = parm[4].qs = *(int8_t*)pi; // sign extend
if (pInstr->a.ot == 5) parm[2].f = parm[4].bs; // convert to float
if (pInstr->a.ot == 6) parm[2].d = parm[4].bs; // convert to double
break;
case 2:
parm[2].qs = parm[4].qs = *(int16_t*)pi; // sign extend
if (pInstr->a.ot == 5) parm[2].f = half2float(*(uint16_t*)pi); // convert from half precision
if (pInstr->a.ot == 6) parm[2].d = half2float(*(uint16_t*)pi); // convert from half precision
break;
case 4:
parm[2].qs = parm[4].qs = *(int32_t*)pi; // sign extend
if (pInstr->a.ot == 6) parm[2].d = *(float*)pi; // convert to double
break;
case 8:
parm[2].qs = parm[4].qs = *(int64_t*)pi; break; // just copy
default:
err.submit(ERR_INTERNAL);
}
operands[5] = 0x20;
// first source operand
if (fInstr->opAvail & 0x20) operands[4] = pInstr->a.rs;
else operands[4] = pInstr->a.rd;
}
else if (fInstr->opAvail & 2) {
// last operand is memory
parm[2].q = readMemoryOperand(memAddress);
operands[5] = 0x40;
// first source operand
if (fInstr->opAvail & 0x20) operands[4] = pInstr->a.rs;
else operands[4] = pInstr->a.rd;
}
else {
// last source operand is a register
operands[4] = pInstr->a.rd;
if ((fInstr->opAvail & 0x30) == 0x30) {
// three registers
operands[4] = pInstr->a.rs;
operands[5] = pInstr->a.rt;
}
else if (fInstr->opAvail & 0x20) operands[5] = pInstr->a.rs;
else operands[5] = pInstr->a.rd;
// read register containing last operand
parm[2].q = readRegister(operands[5]);
}
operands[0] = pInstr->a.rd; // destination
operands[1] = 0xFF; // no mask
// read register containing first source operand
parm[1].q = readRegister(operands[4]);
// return type for debug output. may be changed by execution function
returnType = operandType | 0x1010;
return;
}
 
// single format, multi-format, and indirect jump instructions:
 
// Make list of operands from available operands.
// The operands[] array must have 6 elements to avoid overflow here,
// even if some elements are later overwritten and used for other purposes
uint8_t opAvail = fInstr->opAvail; // Bit index of available operands
// opAvail bits: 1 = immediate, 2 = memory,
// 0x10 = RT, 0x20 = RS, 0x40 = RU, 0x80 = RD
int j = 5;
if (opAvail & 0x01) operands[j--] = 0x20; // immediate operand
if (opAvail & 0x02) operands[j--] = 0x40; // memory operand
if (opAvail & 0x10) operands[j--] = pInstr->a.rt; // register RT
if (opAvail & 0x20) operands[j--] = pInstr->a.rs; // register RS
if (opAvail & 0x40) operands[j--] = pInstr->a.ru; // register RU
if (opAvail & 0x80) operands[j--] = pInstr->a.rd; // register RD
operands[0] = pInstr->a.rd; // destination
 
// find mask register
if (fInstr->tmplate == 0xA || fInstr->tmplate == 0xE) {
operands[1] = pInstr->a.mask;
// find fallback register
uint8_t fb = findFallback(fInstr, pInstr, nOperands);
operands[2] = fb; // fallback register, or 0xFF if zero fallback
}
else {
operands[1] = operands[2] = 0xFF; // no mask, no fallback
}
 
// determine if vector registers are used
vect = (fInstr->vect & 1) || ((fInstr->vect & 0x10) && (pInstr->a.ot & 4));
 
// return type for debug output. may be changed by execution function
returnType = operandType | 0x10 | vect << 8;
 
// get value of last operand if not a vector
if (opAvail & 0x01) {
// pointer to immediate field
const uint8_t * pi = &pInstr->b[0] + fInstr->immPos;
// get value, sign extended
switch (fInstr->immSize) {
case 1:
parm[2].qs = *(int8_t*)pi;
break;
case 2:
parm[2].qs = *(int16_t*)pi;
break;
case 4:
parm[2].qs = *(int32_t*)pi;
break;
case 8:
parm[2].qs = *(uint64_t*)pi;
break;
case 14: // 4 bits
parm[2].q = *(uint8_t*)pi & 0xF;
break;
default:
err.submit(ERR_INTERNAL);
}
// extend, shift, or convert
parm[4].q = parm[2].q; // preserve original value
switch (operandType) {
case 5: // float
if (fInstr->immSize == 1) { // convert integer
parm[2].f = (float)(int8_t)parm[2].b;
}
else if (fInstr->immSize == 2) { // convert half precision
parm[2].f = half2float(parm[2].i);
}
break;
case 6: // double precision
if (fInstr->immSize == 1) { // convert integer
parm[2].d = (double)(int8_t)parm[2].b;
}
else if (fInstr->immSize == 2) { // convert half precision
parm[2].d = half2float(parm[2].i);
}
else if (fInstr->immSize == 4) { // convert single precision
parm[2].d = parm[2].f;
}
break;
case 7: // quadruple precision
// to do
break;
default: // all integer types. shift value if needed
if (fInstr->imm2 & 4) parm[2].q <<= pInstr->a.im3;
else if (fInstr->imm2 & 8) parm[2].q <<= pInstr->a.im2;
}
if (opAvail & 2) {
// both memory and immediate operand
if ((!vect || (fInstr->vect & 4)) && !dontRead) {
// scalar or broadcast memory operand
parm[1].q = readMemoryOperand(memAddress);
}
if (nOperands > 2) parm[0].q = readRegister(operands[3] & 0x1F);
return;
}
}
else if ((!vect || (fInstr->vect & 4)) && (opAvail & 0x02) && !dontRead) {
// scalar or broadcast memory operand and no immediate operand
parm[2].q = readMemoryOperand(memAddress);
}
else if (!vect) {
// general purpose register
parm[2].q = readRegister(operands[5] & 0x1F);
}
// get values of remaining operands
if (nOperands > 1) parm[1].q = readRegister(operands[4] & 0x1F);
if (nOperands > 2) parm[0].q = readRegister(operands[3] & 0x1F);
}
 
 
// execute current instruction
void CThread::execute() {
uint64_t result = 0; // destination value
PFunc functionPointer = 0; // pointer to execution function
running = 1;
 
// find function pionter
if (fInstr->exeTable == 0) {
interrupt(INT_UNKNOWN_INST); return;
}
if (fInstr->tmplate == 0xE && pInstr->a.op2 != 0 && !(fInstr->imm2 & 0x100)) {
// single format instruction with E template
uint8_t index; // index into EDispatchTable
// bit 0-2 = mode2
// bit 3 = mode bit 1
// bit 4 = il bit 0
// bit 5-6 = op2 - 1
index = pInstr->a.mode2 | (pInstr->a.mode << 2 & 8) | (pInstr->a.il << 4 & 0x10) | (pInstr->a.op2 - 1) << 5;
functionPointer = EDispatchTable[index];
}
else { // all other instructions. fInstr->exeTable indicates which function table to look into
functionPointer = metaFunctionTable[fInstr->exeTable][op];
}
if (!functionPointer || !fInstr->exeTable) {
interrupt(INT_UNKNOWN_INST);
return;
}
if (vect) { // vector instruction
// length of each element
uint32_t elementSize = dataSizeTable[operandType];
// get vector length
// vector length of result = length of first source operand register
switch (nOperands) {
case 0: // no source operands. vector length will be set by instruction
vectorLengthR = 8; break;
case 1: // one source operand
if (operands[5] & 0x20) { // source operand is immediate.
vectorLengthR = dataSizeTable[operandType]; // vector length may be modified by instruction
}
else if (operands[5] & 0x40) { // source operand is memory
vectorLengthR = vectorLengthM;
}
else { // source operand is register
vectorLengthR = vectorLength[operands[5]];
}
break;
case 2: // two source operands
if (operands[4] & 0x40) { // first source operand is memory
vectorLengthR = vectorLengthM;
}
else { // first source operand is register
vectorLengthR = vectorLength[operands[4]];
}
break;
case 3: default: // three source operands. first source operand must be register
vectorLengthR = vectorLength[operands[3]];
break;
}
if (noVectorLength // vector length determined by execution function
|| fInstr->category == 4) { // call compare/jump function even if vector is empty
vectorLengthR = elementSize; // make sure it is called at least once
}
// set vector length of destination
if (!noVectorLength && !unchangedRd) {
vectorLength[operands[0]] = vectorLengthR;
}
 
// loop through vector
vect = 1;
for (vectorOffset = 0; vectorOffset < vectorLengthR; vectorOffset += elementSize) {
if (vect & 4) break; // stop loop
 
// read nOperands operands
for (int iOp = 3 - nOperands; iOp <= 2; iOp++) {
if (operands[iOp+3] & 0x20) { // immediate
// has already been read into parm[2]
}
else if (operands[iOp+3] & 0x40) { // memory
if (fInstr->vect & 4) { // broadcast memory operand
if (vectorOffset + elementSize > vectorLengthM) {
parm[iOp].q = 0; // beyond broadcast length
}
else { // read broadcast memory operand
parm[iOp].q = readMemoryOperand(memAddress);
}
}
else { // memory vector
if (!dontRead) {
if (vectorOffset + elementSize > vectorLengthM) {
parm[iOp].q = 0; // beyond memory operand length
}
else { // read memory vector
parm[iOp].q = readMemoryOperand(memAddress + vectorOffset);
}
}
}
}
else { // vector register
parm[iOp].q = readVectorElement(operands[iOp+3], vectorOffset);
}
}
// get mask
if ((operands[1] & 7) != 7) {
parm[3].q = readVectorElement(operands[1], vectorOffset);
}
else {
parm[3].q = numContr;
}
// skip instruction if mask = 0, except for certain instructions
if ((parm[3].q & 1) == 0 && !ignoreMask) {
// result is masked off. find fallback
if (operands[2] == 0xFF) result = 0; // fallback = 0
else result = readVectorElement(operands[2], vectorOffset); // fallback register
if (doubleStep) {
if (operands[2] == 0xFF) result = 0;
else result = readVectorElement(operands[2], vectorOffset + elementSize);
}
}
else {
// normal operation. execute instruction
result = (*functionPointer)(this);
}
// store in destination register
if ((running & 1) && !(returnType & 0x20)) {
vectorLength[operands[0]] = vectorLengthR;
// get mask for operand size (operandType may have been changed by function)
//uint64_t opmask = dataSizeMask[operandType];
// write result to vector
writeVectorElement(operands[0], result, vectorOffset);
if (dataSizeTable[operandType] >= 16) { // 128 bits
writeVectorElement(operands[0], parm[5].q, vectorOffset + (elementSize>>1)); // high part of double size result
}
if (doubleStep) { // double step
writeVectorElement(operands[0], parm[5].q, vectorOffset + elementSize); // high part of double size result
}
}
vect ^= 3; // toggle between 1 for even elements, 2 for odd
if (doubleStep) vectorOffset += elementSize; // skip next element if instruction takes two elements at a time
}
listResult(result); // debug output
}
else {
// general purpose registers
// get mask
if ((operands[1] & 7) != 7) {
parm[3].q = readRegister(operands[1]);
}
else {
parm[3].q = numContr;
}
// skip instruction if mask = 0, except for certain instructions
if ((parm[3].q & 1) == 0 && !ignoreMask) {
// result is masked off. find fallback
if (operands[2] == 0xFF) result = 0;
else result = readRegister(operands[2]);
}
else {
// normal operation.
// execute instruction
result = (*functionPointer)(this);
}
// get mask for operand size (operandType may have been changed by function)
// store in destination register, zero extended from operand size
if (running & 1) registers[operands[0]] = result & dataSizeMask[operandType];
listResult(result); // debug output
}
performanceCounters(); // update performance counters
}
 
// update performance counters
void CThread::performanceCounters() {
perfCounters[perf_cpu_clock_cycles]++; // clock cycles
perfCounters[perf_instructions]++; // instructions
if ((fInstr->format2 & 0xF00) == 0x200) perfCounters[perf_2size_instructions]++; // double size instructions
if ((fInstr->format2 & 0xF00) == 0x300) perfCounters[perf_3size_instructions]++; // triple size instructions
if (vect) {
perfCounters[perf_vector_instructions]++; // vector instructions
}
else {
perfCounters[perf_gp_instructions]++; // g.p. instructions
if ((parm[3].q & 1) == 0 && !ignoreMask) perfCounters[perf_gp_instructions_mask0]++; // g.p. instructions masked off
}
if (fInstr->category == 4) { // jump instructions
perfCounters[perf_control_transfer_instructions]++; // all jumps, calls, returns
if (fInstr->tmplate == 0xD) { // direct jump/call
perfCounters[perf_direct_jumps]++; // g.p. instructions
}
else if (fInstr->exeTable == 2) {
if (op == 62 && fInstr->format2 >> 4 == 0x16) {
perfCounters[perf_direct_jumps]++; // simple return
}
else if (op >= 56) perfCounters[perf_indirect_jumps]++; // indirect jumps and calls
else perfCounters[perf_cond_jumps]++; // conditional jumps
}
}
}
 
// read vector element
uint64_t CThread::readVectorElement(uint32_t v, uint32_t vectorOffset) {
uint32_t size; // element size
uint64_t returnval = 0;
if (operandType == 8) size = 2;
else size = dataSizeTableMax8[operandType];
v &= 0x1F; // protect against array overflow
//if (vectorOffset < vectorLength[v]) {
if (vectorOffset + size <= vectorLength[v]) {
switch (size) { // zero-extend from element size
case 1:
returnval = *(uint8_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset);
break;
case 2:
returnval = *(uint16_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset);
break;
case 4:
returnval = *(uint32_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset);
break;
case 8:
returnval = *(uint64_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset);
break;
}
uint32_t sizemax = vectorLength[v] - vectorOffset;
if (size > sizemax) { // reading beyond end of vector. cut off element to max size
returnval &= (uint64_t(1) << sizemax*8) - 1;
}
}
return returnval;
}
 
// write vector element
void CThread::writeVectorElement(uint32_t v, uint64_t value, uint32_t vectorOffset) {
uint32_t size = dataSizeTableMax8[operandType];
v &= 0x1F; // protect against array overflow
if (vectorOffset + size <= vectorLength[v]) {
switch (size) { // zero-extend from element size
case 1:
*(uint8_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset) = (uint8_t)value;
break;
case 2:
*(uint16_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset) = (uint16_t)value;
break;
case 4:
*(uint32_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset) = (uint32_t)value;
break;
case 8:
*(uint64_t*)(vectors.buf() + MaxVectorLength*v + vectorOffset) = value;
break;
}
}
}
 
// get address of a memory operand
uint64_t CThread::getMemoryAddress() {
// find base register
if ((fInstr->mem & 3) == 0) err.submit(ERR_INTERNAL);
//uint8_t basereg = (fInstr->mem & 1) ? pInstr->a.rt : pInstr->a.rs;
uint8_t basereg = pInstr->a.rs;
readonly = false;
memory_error = false;
// base register value
uint64_t baseval = registers[basereg];
if (fInstr->addrSize > 1 && !(fInstr->mem & 0x20)) {
// special registers
switch (basereg) {
case 28: // threadp
baseval = threadp; break;
case 29: // datap
baseval = datap; break;
case 30: // ip
baseval = ip; readonly = true;
break;
}
}
// pointer to memory field
const uint8_t * pa = &pInstr->b[0] + fInstr->addrPos;
 
// find index register
uint64_t indexval = 0;
if ((fInstr->mem & 4) && (pInstr->a.rt != 0x1F)) {
// rt is index register
indexval = registers[pInstr->a.rt & 0x1F];
// check limit
if (fInstr->mem & 0x20) {
const uint8_t * pi = &pInstr->b[0] + fInstr->addrPos; // pointer to immediate field
uint64_t limit = *(uint64_t*)pi;
limit &= (uint64_t(1) << (fInstr->addrSize * 8)) - 1;
if (indexval > limit) {
interrupt(INT_ARRAY_BOUNDS);
memory_error = true;
//return 0;
}
}
}
// get offset, sign-extended
int64_t offset = 0;
if (fInstr->mem & 0x10) {
switch (fInstr->addrSize) {
case 0:
break;
case 1:
offset = *(int8_t*)pa;
break;
case 2:
offset = *(int16_t*)pa;
break;
case 4:
offset = *(int32_t*)pa;
break;
case 8:
offset = *(int64_t*)pa;
break;
default:
err.submit(ERR_INTERNAL);
}
}
// scale
switch (fInstr->scale) {
case 1: // offset is scaled
offset <<= dataSizeTableLog[operandType];
break;
case 2: // index is scaled by OS
indexval <<= dataSizeTableLog[operandType];
break;
case 4: // 4 = scale factor is -1
indexval = uint64_t(-(int64_t)indexval);
break;
}
// get length
if ((fInstr->vect & 6) && pInstr->a.rt < 0x1F) { // vector length or broadcast length is in RT
if (registers[pInstr->a.rt] > MaxVectorLength) vectorLengthM = MaxVectorLength;
else vectorLengthM = (uint32_t)registers[pInstr->a.rt];
}
else { // scalar
vectorLengthM = dataSizeTable[operandType & 7];
}
// offset and index may be negative, but the result must be positive
return baseval + indexval + (uint64_t)offset;
}
 
// read a memory operand
uint64_t CThread::readMemoryOperand(uint64_t address) {
// get most likely memory map index
uint32_t * indexp = readonly ? &mapIndex2 : &mapIndex3;
uint32_t index = * indexp;
 
// find memory map entry
while (address < memoryMap[index].startAddress) {
if (index > 0) index--;
else {
interrupt(INT_ACCESS_READ); return 0;
}
}
while (address >= memoryMap[index + 1].startAddress) {
if (index + 2 < memoryMap.numEntries()) index++;
else {
interrupt(INT_ACCESS_READ); return 0;
}
}
// check read permission
if (!(memoryMap[index].access_addend & SHF_READ)) {
interrupt(INT_ACCESS_READ); return 0;
}
 
// check if map boundary crossed
if (address + dataSizeTable[operandType] > memoryMap[index+1].startAddress
&& !(memoryMap[index+1].access_addend & SHF_READ)) {
interrupt(INT_ACCESS_READ);
}
 
// check alignment
 
 
// return zero if any kind of error
if (memory_error) return 0;
 
// save index for next time
*indexp = index;
 
// get value, zero extended
const int8_t * p = memory + address; // pointer to data
switch (dataSizeTableMax8[operandType]) {
case 0:
break;
case 1:
return *(uint8_t*)p;
case 2:
if (address & 1) interrupt(INT_MISALIGNED_MEM);
return *(uint16_t*)p;
case 4:
if (address & 3) interrupt(INT_MISALIGNED_MEM);
return *(uint32_t*)p;
case 8:
if (address & 7) interrupt(INT_MISALIGNED_MEM);
return *(uint64_t*)p;
}
return 0;
}
 
// write a memory operand
void CThread::writeMemoryOperand(uint64_t val, uint64_t address) {
// most likely memory map index is saved in mapIndex3
// find memory map entry
while (address < memoryMap[mapIndex3].startAddress) {
if (mapIndex3 > 0) mapIndex3--;
else {
interrupt(INT_ACCESS_WRITE); return;
}
}
while (address >= memoryMap[mapIndex3+1].startAddress) {
if (mapIndex3 + 2 < memoryMap.numEntries()) mapIndex3++;
else {
interrupt(INT_ACCESS_WRITE); return;
}
}
// check write permission
if (!(memoryMap[mapIndex3].access_addend & SHF_WRITE)) {
interrupt(INT_ACCESS_WRITE); return;
}
 
// check if map boundary crossed
if (address + dataSizeTable[operandType] > memoryMap[mapIndex3+1].startAddress
&& !(memoryMap[mapIndex3+1].access_addend & SHF_WRITE)) {
interrupt(INT_ACCESS_WRITE);
}
 
// write value
// get value, zero extended
int8_t * p = memory + address; // pointer to data
switch (dataSizeTableMax8[operandType]) {
case 0:
break;
case 1:
*(uint8_t*)p = (uint8_t)val;
break;
case 2:
if (address & 1) interrupt(INT_MISALIGNED_MEM);
*(uint16_t*)p = (uint16_t)val;
break;
case 4:
if (address & 3) interrupt(INT_MISALIGNED_MEM);
*(uint32_t*)p = (uint32_t)val;
break;
case 8:
if (address & 7) interrupt(INT_MISALIGNED_MEM);
*(uint64_t*)p = val;
break;
}
}
 
// start writing debug list
void CThread::listStart() {
if (!listFileName) return; // nothing if no list file
listOut.put("Debug listing of ");
listOut.put(cmd.getFilename(cmd.inputFile));
listOut.newLine();
// Date and time. (Will fail after year 2038 on computers that use 32-bit time_t)
time_t time1 = time(0);
char * timestring = ctime(&time1);
if (timestring) {
for (char *c = timestring; *c; c++) { // Remove terminating '\n' in timestring
if (*c < ' ') *c = 0;
}
listOut.put(timestring);
listOut.newLine(); listOut.newLine();
}
}
 
static uint32_t listIndex = 0; // index into lineList
// write current instruction to debug list
void CThread::listInstruction(uint64_t address) {
if (listFileName == 0 || cmd.maxLines == 0) return; // stop listing
SLineRef rec = {address, 1, 0};
const char * text = 0;
if (listIndex + 1 < emulator->lineList.numEntries() && emulator->lineList[listIndex+1] == rec) {
// just the next record. no need to search
listIndex = listIndex+1;
}
else { // we may have jumped. Find address in list
listIndex = (uint32_t)emulator->lineList.findFirst(rec);
}
if (listIndex < emulator->lineList.numEntries()) {
text = emulator->disassemOut.getString(emulator->lineList[listIndex].textPos); // get line from disassembly
listOut.put(text);
}
else { // corresponding disassembly not found
listOut.putHex((uint32_t)address, 2);
listOut.tabulate(emulator->disassembler.asmTab0);
listOut.put("???");
}
listOut.newLine();
}
 
// write result of current instruction to debug list
void CThread::listResult(uint64_t result) {
if (++listLines >= cmd.maxLines) cmd.maxLines = 0; // stop listing
if (listFileName == 0 || returnType == 0 || cmd.maxLines == 0) return; // nothing if no list file or no return value
listOut.tabulate(emulator->disassembler.asmTab0);
if (!(returnType & 0x100)) { // general purpose register
if (returnType & 0x20) { // memory destination
result = readMemoryOperand(getMemoryAddress());
}
if (returnType & 0x30) { // register or memory
switch (returnType & 0xF) {
case 0: // int8
listOut.putHex((uint8_t)result); break;
case 1: // int16
listOut.putHex((uint16_t)result); break;
case 2: case 5: // int32
listOut.putHex((uint32_t)result); break;
case 3: case 6: // int64
listOut.putHex(result); break;
case 4: // int128
listOut.putHex(parm[5].q, 2); listOut.putHex(result, 2); break;
default:
listOut.put("?");
}
}
}
else if (returnType & 0x30) { // vector
uint8_t destinationReg = operands[0] & 0x1F;
//uint32_t vectorLengthR = vectorLength[destinationReg];
if (!(returnType & 0x20)) vectorLengthR = vectorLength[destinationReg];
uint8_t type = returnType & 0xF;
operandType = type;
uint32_t elementSize = dataSizeTable[type & 7];
if (type == 8) elementSize = 2; // half precision
if (elementSize > 8) elementSize = 8; // int128 and float128 listed as two int64
union { // union to convert types
uint64_t q;
double d;
float f;
} u;
if (vectorLengthR == 0) listOut.put("Empty");
//if (returnType & 0x40) vectorLengthR += elementSize; // one extra element (save_cp instruction)
for (uint32_t vectorOffset = 0; vectorOffset < vectorLengthR; vectorOffset += elementSize) {
if (returnType & 0x20) { // memory destination
result = readMemoryOperand(getMemoryAddress() + vectorOffset);
}
else {
result = readVectorElement(destinationReg, vectorOffset);
}
switch (returnType & 0xF) {
case 0: // int8
listOut.putHex((uint8_t)result); break;
case 1: // int16
listOut.putHex((uint16_t)result); break;
case 2: // int32
listOut.putHex((uint32_t)result); break;
case 3: case 4: case 7: // int64
listOut.putHex(result); break;
case 5: // float
u.q = result;
listOut.putFloat(u.f); break;
case 6: // double
u.q = result;
listOut.putFloat(u.d); break;
case 8: // float16
listOut.putFloat16((uint16_t)result); break;
default:
listOut.put("???");
}
listOut.put(' ');
}
}
if (returnType & 0x3000) {
// conditional jump instruction
if (returnType & 0x30) listOut.put(", "); // space after value
listOut.put((returnType & 0x2000) ? "jump" : "no jump"); // tell if jump or not
}
listOut.newLine();
}
 
// make a NAN with exception code and address in payload
uint64_t CThread::makeNan(uint32_t code, uint32_t operandTyp) {
uint64_t retval = 0;
uint8_t instrLength = lengthList[pInstr->a.il]; // instruction length
uint64_t iaddress = ((ip - ip0) >> 2) - instrLength; // instruction address
iaddress = ~iaddress; // invert bits
switch (operandTyp) {
case 1: // half precision
retval = (uint8_t)code | 0x7E00 | (iaddress & 1) << 8;
break;
case 5: // single precision
retval = (uint8_t)code | 0x7FC00000 | uint32_t(iaddress & ((1 << 14) - 1)) << 8;
break;
case 6: // double precision
retval = (uint8_t)code | 0x7FF8000000000000 | (iaddress & (((uint64_t)1 << 43) - 1)) << 8;
break;
}
return retval;
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.