#include "VVexRiscv.h"
#include "VVexRiscv_VexRiscv.h"
#ifdef REF
#include "VVexRiscv_RiscvCore.h"
#endif
#include "verilated.h"
#include "verilated_fst_c.h"
#include <stdio.h>
#include <iostream>
#include <stdlib.h>
#include <stdint.h>
#include <cstring>
#include <string.h>
#include <iostream>
#include <fstream>
#include <vector>
#include <mutex>
#include <iomanip>
#include <queue>
#include <time.h>
#include "encoding.h"

#define VL_RANDOM_I_WIDTH(w) (VL_RANDOM_I() & (1l << w)-1l)

using namespace std;

struct timespec timer_get(){
    struct timespec start_time;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
    return start_time;
}

class Memory{
public:
	uint8_t* mem[1 << 12];

	Memory(){
		for(uint32_t i = 0;i < (1 << 12);i++) mem[i] = NULL;
	}
	~Memory(){
		for(uint32_t i = 0;i < (1 << 12);i++) if(mem[i]) delete [] mem[i];
	}

	uint8_t* get(uint32_t address){
		if(mem[address >> 20] == NULL) {
			uint8_t* ptr = new uint8_t[1024*1024];
			for(uint32_t i = 0;i < 1024*1024;i+=4) {
				ptr[i + 0] = 0xFF;
				ptr[i + 1] = 0xFF;
				ptr[i + 2] = 0xFF;
				ptr[i + 3] = 0xFF;
			}
			mem[address >> 20] = ptr;
		}
		return &mem[address >> 20][address & 0xFFFFF];
	}

	void read(uint32_t address,uint32_t length, uint8_t *data){
		for(int i = 0;i < length;i++){
			data[i] = (*this)[address + i];
		}
	}

	void write(uint32_t address,uint32_t length, uint8_t *data){
		for(int i = 0;i < length;i++){
			(*this)[address + i] = data[i];
		}
	}

	uint8_t& operator [](uint32_t address) {
		return *get(address);
	}

	/*T operator [](uint32_t address) const {
		return get(address);
	}*/
};

//uint8_t memory[1024 * 1024];

uint32_t hti(char c) {
	if (c >= 'A' && c <= 'F')
		return c - 'A' + 10;
	if (c >= 'a' && c <= 'f')
		return c - 'a' + 10;
	return c - '0';
}

uint32_t hToI(char *c, uint32_t size) {
	uint32_t value = 0;
	for (uint32_t i = 0; i < size; i++) {
		value += hti(c[i]) << ((size - i - 1) * 4);
	}
	return value;
}

void loadHexImpl(string path,Memory* mem) {
	FILE *fp = fopen(&path[0], "r");
	if(fp == 0){
		cout << path << " not found" << endl;
	}
	//Preload 0x0 <-> 0x80000000 jumps
	((uint32_t*)mem->get(0))[0] = 0x800000b7;
	((uint32_t*)mem->get(0))[1] = 0x000080e7;
	((uint32_t*)mem->get(0x80000000))[0] = 0x00000097;

	fseek(fp, 0, SEEK_END);
	uint32_t size = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	char* content = new char[size];
	fread(content, 1, size, fp);
	fclose(fp);

	int offset = 0;
	char* line = content;
	while (1) {
		if (line[0] == ':') {
			uint32_t byteCount = hToI(line + 1, 2);
			uint32_t nextAddr = hToI(line + 3, 4) + offset;
			uint32_t key = hToI(line + 7, 2);
//			printf("%d %d %d\n", byteCount, nextAddr,key);
			switch (key) {
			case 0:
				for (uint32_t i = 0; i < byteCount; i++) {
					*(mem->get(nextAddr + i)) = hToI(line + 9 + i * 2, 2);
					//printf("%x %x %c%c\n",nextAddr + i,hToI(line + 9 + i*2,2),line[9 + i * 2],line[9 + i * 2+1]);
				}
				break;
			case 2:
//				cout << offset << endl;
				offset = hToI(line + 9, 4) << 4;
				break;
			case 4:
//				cout << offset << endl;
				offset = hToI(line + 9, 4) << 16;
				break;
			default:
//				cout << "??? " << key << endl;
				break;
			}
		}

		while (*line != '\n' && size != 0) {
			line++;
			size--;
		}
		if (size <= 1)
			break;
		line++;
		size--;
	}

	delete [] content;
}

void loadBinImpl(string path,Memory* mem, uint32_t offset) {
	FILE *fp = fopen(&path[0], "r");
	if(fp == 0){
		cout << path << " not found" << endl;
	}

	fseek(fp, 0, SEEK_END);
	uint32_t size = ftell(fp);
	fseek(fp, 0, SEEK_SET);
	char* content = new char[size];
	fread(content, 1, size, fp);
	fclose(fp);

	for(int byteId = 0; byteId < size;byteId++){
		*(mem->get(offset + byteId)) = content[byteId];
	}

	delete [] content;
}



#define TEXTIFY(A) #A

void breakMe(){
    int a = 0;
}
#define assertEq(x,ref) if(x != ref) {\
	printf("\n*** %s is %d but should be %d ***\n\n",TEXTIFY(x),x,ref);\
	breakMe();\
	throw std::exception();\
}

class success : public std::exception { };




#define MVENDORID  0xF11 // MRO Vendor ID.
#define MARCHID    0xF12 // MRO Architecture ID.
#define MIMPID     0xF13 // MRO Implementation ID.
#define MHARTID    0xF14 // MRO Hardware thread ID.Machine Trap Setup
#define MSTATUS    0x300 // MRW Machine status register.
#define MISA       0x301 // MRW ISA and extensions
#define MEDELEG    0x302 // MRW Machine exception delegation register.
#define MIDELEG    0x303 // MRW Machine interrupt delegation register.
#define MIE        0x304 // MRW Machine interrupt-enable register.
#define MTVEC      0x305 // MRW Machine trap-handler base address. Machine Trap Handling
#define MSCRATCH   0x340 // MRW Scratch register for machine trap handlers.
#define MEPC       0x341 // MRW Machine exception program counter.
#define MCAUSE     0x342 // MRW Machine trap cause.
#define MBADADDR   0x343 // MRW Machine bad address.
#define MIP        0x344 // MRW Machine interrupt pending.
#define MBASE      0x380 // MRW Base register.
#define MBOUND     0x381 // MRW Bound register.
#define MIBASE     0x382 // MRW Instruction base register.
#define MIBOUND    0x383 // MRW Instruction bound register.
#define MDBASE     0x384 // MRW Data base register.
#define MDBOUND    0x385 // MRW Data bound register.
#define MCYCLE     0xB00 // MRW Machine cycle counter.
#define MINSTRET   0xB02 // MRW Machine instructions-retired counter.
#define MCYCLEH    0xB80 // MRW Upper 32 bits of mcycle, RV32I only.
#define MINSTRETH  0xB82 // MRW Upper 32 bits of minstret, RV32I only.


#define SSTATUS 0x100
#define SIE 0x104
#define STVEC 0x105
#define SCOUNTEREN 0x106
#define SSCRATCH 0x140
#define SEPC 0x141
#define SCAUSE 0x142
#define STVAL 0x143
#define SIP 0x144
#define SATP 0x180

#define UTIME    0xC01 // rdtime
#define UTIMEH   0xC81

#define SSTATUS_SIE         0x00000002
#define SSTATUS_SPIE        0x00000020
#define SSTATUS_SPP         0x00000100

#ifdef SUPERVISOR
#define MSTATUS_READ_MASK 0xFFFFFFFF
#else
#define MSTATUS_READ_MASK 0x7888
#endif

#ifdef RVF
#define STATUS_FS_MASK 0x6000
#else
#define STATUS_FS_MASK 0x0000
#endif

#define FFLAGS 0x1
#define FRM    0x2
#define FCSR   0x3

#define u32 uint32_t
#define u64 uint64_t

class FpuRsp{
public:
	u32 flags;
	u64 value;
};

class FpuCommit{
public:
	u64 value;
};

class FpuCompletion{
public:
	u32 flags;
};


bool fpuCommitLut[32] = {true,true,true,true,true,true,false,false,true,false,false,true,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,true,false};
bool fpuRspLut[32] = {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,true,false,false,false,true,false,false,false};
bool fpuRs1Lut[32] = {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,false,false,true,false};
class RiscvGolden {
public:
	int32_t pc, lastPc;
	uint32_t lastInstruction;
	int32_t regs[32];
	uint64_t stepCounter;

	uint32_t mscratch, sscratch;
	uint32_t misa;
	uint32_t privilege;

    uint32_t medeleg;
	uint32_t mideleg;

    queue<FpuRsp> fpuRsp;
    queue<FpuCommit> fpuCommit;
    queue<FpuCompletion> fpuCompletion;

	union status {
		uint32_t raw;
		struct {
			uint32_t _1a : 1;
			uint32_t sie : 1;
			uint32_t _1b : 1;
			uint32_t mie : 1;
			uint32_t _2a : 1;
			uint32_t spie : 1;
			uint32_t _2b : 1;
			uint32_t mpie : 1;
			uint32_t spp : 1;
			uint32_t _3 : 2;
			uint32_t mpp : 2;
			uint32_t fs : 2;
			uint32_t _4 : 2;
			uint32_t mprv : 1;
			uint32_t sum : 1;
			uint32_t mxr : 1;
		};
	}__attribute__((packed)) status;



	uint32_t ipInput;
	uint32_t ipSoft;
	union IpOr {
		uint32_t raw;
		struct {
			uint32_t _1a : 1;
			uint32_t ssip : 1;
			uint32_t _1b : 1;
			uint32_t msip : 1;
			uint32_t _2a : 1;
			uint32_t stip : 1;
			uint32_t _2b : 1;
			uint32_t mtip : 1;
			uint32_t _3a : 1;
			uint32_t seip : 1;
			uint32_t _3b : 1;
			uint32_t meip : 1;
		};
	}__attribute__((packed));

	IpOr getIp(){
		IpOr ret;
		ret.raw = ipSoft | ipInput;
		return ret;
	}

	union mie {
		uint32_t raw;
		struct {
			uint32_t _1a : 1;
			uint32_t ssie : 1;
			uint32_t _1b : 1;
			uint32_t msie : 1;
			uint32_t _2a : 1;
			uint32_t stie : 1;
			uint32_t _2b : 1;
			uint32_t mtie : 1;
			uint32_t _3a : 1;
			uint32_t seie : 1;
			uint32_t _3b : 1;
			uint32_t meie : 1;
		};
	}__attribute__((packed)) ie;

	union Xtvec {
		uint32_t raw;
		struct __attribute__((packed)) {
			uint32_t _1 : 2;
			uint32_t base : 30;
		};
	};

	Xtvec mtvec, stvec;



	union mcause {
		uint32_t raw;
		struct __attribute__((packed)) {
			uint32_t exceptionCode : 31;
			uint32_t interrupt : 1;
		};
	} mcause;


	union scause {
		uint32_t raw;
		struct __attribute__((packed)){
			uint32_t exceptionCode : 31;
			uint32_t interrupt : 1;
		};
	} scause;

	union satp {
		uint32_t raw;
		struct __attribute__((packed)){
			uint32_t ppn : 22;
			uint32_t _x : 9;
			uint32_t mode : 1;
		};
	}satp;

	union Tlb {
		uint32_t raw;
		struct __attribute__((packed)){
			uint32_t v : 1;
			uint32_t r : 1;
			uint32_t w : 1;
			uint32_t x : 1;
			uint32_t u : 1;
			uint32_t g : 1;
			uint32_t a : 1;
			uint32_t d : 1;
			uint32_t _dummy : 2;
			uint32_t ppn : 22;
		};
		struct __attribute__((packed)){
			uint32_t _dummyX : 10;
			uint32_t ppn0 : 10;
			uint32_t ppn1 : 12;
		};
	};

	union fcsr {
		uint32_t raw;
		struct __attribute__((packed)){
			uint32_t flags : 5;
			uint32_t frm : 3;
		};
	}fcsr;


	bool lrscReserved;
	uint32_t lrscReservedAddress;
    u32 fpuCompletionTockens;
    u32 dutRfWriteValue;

	RiscvGolden() {
		pc = 0x80000000;
		regs[0] = 0;
		for (int i = 0; i < 32; i++)
			regs[i] = 0;

		ie.raw = 0;
		mtvec.raw = 0x80000020;
		mcause.raw = 0;
		mbadaddr = 0;
		mepc = 0;
		misa = 0x40041101; //TODO
		status.raw = 0;
		status.mpp = 3;
		status.spp = 1;
		#ifdef RVF
		status.fs = 1;
		misa |= 1 << 5;
		#endif
		#ifdef RVD
		misa |= 1 << 3;
		#endif
		fcsr.flags = 0;
		fcsr.frm = 0;
		privilege = 3;
		medeleg = 0;
		mideleg = 0;
		satp.mode = 0;
		ipSoft = 0;
		ipInput = 0;
		stepCounter = 0;
		sbadaddr = 42;
		lrscReserved = false;
		fpuCompletionTockens = 0;
	}

	virtual void rfWrite(int32_t address, int32_t data) {
		if (address != 0)
			regs[address] = data;
	}

	virtual void pcWrite(int32_t target) {
		if(isPcAligned(target)){
			lastPc = pc;
			pc = target;
		} else {
			trap(0, 0, target);
		}
	}
	uint32_t mbadaddr, sbadaddr;
	uint32_t mepc, sepc;

	virtual bool iRead(int32_t address, uint32_t *data) = 0;
	virtual bool dRead(int32_t address, int32_t size, uint8_t *data) = 0;
	virtual void dWrite(int32_t address, int32_t size, uint8_t *data) = 0;

	enum AccessKind {READ,WRITE,EXECUTE,READ_WRITE};
	virtual bool isMmuRegion(uint32_t v) = 0;
	bool v2p(uint32_t v, uint32_t *p, AccessKind kind){
	    uint32_t effectivePrivilege = status.mprv && kind != EXECUTE ? status.mpp : privilege;
		if(effectivePrivilege == 3 || satp.mode == 0 || !isMmuRegion(v)){
			*p = v;
		} else {
			Tlb tlb;
			dRead((satp.ppn << 12) | ((v >> 22) << 2), 4, (uint8_t*)&tlb.raw);
			if(!tlb.v) return true;
			bool superPage = true;
			if(!tlb.x && !tlb.r && !tlb.w){
				dRead((tlb.ppn << 12) | (((v >> 12) & 0x3FF) << 2), 4, (uint8_t*)&tlb.raw);
				if(!tlb.v) return true;
				superPage = false;
			}
			if(!tlb.u && effectivePrivilege == 0) return true;
			if( tlb.u && effectivePrivilege == 1 && !status.sum) return true;
			if(superPage && tlb.ppn0 != 0 || !tlb.a) return true;
			if(kind == READ || kind == READ_WRITE) if(!tlb.r && !(status.mxr && tlb.x)) return true;
			if(kind == WRITE || kind == READ_WRITE) if(!tlb.w || !tlb.d) return true;
			if(kind == EXECUTE) if(!tlb.x) return true;

			*p = (tlb.ppn1 << 22) | (superPage ? v & 0x3FF000 : tlb.ppn0 << 12) | (v & 0xFFF);
		}
		return false;
	}

    void trap(bool interrupt,int32_t cause) {
        trap(interrupt, cause, false, 0);
    }
    void trap(bool interrupt,int32_t cause, uint32_t value) {
        trap(interrupt, cause, true, value);
    }
	void trap(bool interrupt,int32_t cause, bool valueWrite, uint32_t value) {
#ifdef FLOW_INFO
//	    cout << "TRAP " << (interrupt ? "interrupt" : "exception") << " cause=" << cause << " PC=0x" << hex << pc << " val=0x" << hex << value << dec << endl;
//	    if(cause == 9){
//	        cout << hex <<  " a7=0x" << regs[17] << " a0=0x" << regs[10] << " a1=0x" << regs[11] << " a2=0x" << regs[12] << dec << endl;
//	    }
#endif
		//Check leguality of the interrupt
		if(interrupt) {
			bool hit = false;
			for(int i = 0;i < 5;i++){
				if(pendingInterrupts[i] == 1 << cause){
					hit = true;
					break;
				}
			}
			if(!hit){
				cout << "DUT had trigger an interrupts which wasn't by the REF" << endl;
				fail();
			}
		}

		uint32_t deleg = interrupt ? mideleg : medeleg;
		uint32_t targetPrivilege = 3;
		if(deleg & (1 << cause)) targetPrivilege = 1;
		targetPrivilege = max(targetPrivilege, privilege);
		Xtvec xtvec = targetPrivilege == 3 ? mtvec : stvec;



		switch(targetPrivilege){
		case 3:
		    if(valueWrite) mbadaddr = value;
			mcause.interrupt = interrupt;
			mcause.exceptionCode = cause;
	        status.mpie = status.mie;
	        status.mie  = false;
	        status.mpp = privilege;
	        mepc = pc;
			break;
		case 1:
			if(valueWrite) sbadaddr = value;
			scause.interrupt = interrupt;
			scause.exceptionCode = cause;
	        status.spie = status.sie;
	        status.sie  = false;
	        status.spp  = privilege;
	        sepc = pc;
			break;
		}

		privilege = targetPrivilege;
		pcWrite(xtvec.base << 2);
		if(interrupt) livenessInterrupt = 0;

//		if(!interrupt) step(); //As VexRiscv instruction which trap do not reach writeback stage fire
	}

    uint32_t currentInstruction;
	void ilegalInstruction(){
		trap(0, 2, currentInstruction);
	}

	virtual void fail() {
	}



	virtual bool csrRead(int32_t csr, uint32_t *value){
		if(((csr >> 8) & 0x3) > privilege) return true;
		switch(csr){
		case MSTATUS: *value = (status.raw | (((status.raw & 0x6000) == 0x6000) ? 0x80000000 : 0)) & MSTATUS_READ_MASK;  break;
		case MIP: *value = getIp().raw; break;
		case MIE: *value = ie.raw; break;
		case MTVEC: *value = mtvec.raw; break;
		case MCAUSE: *value = mcause.raw; break;
		case MBADADDR: *value = mbadaddr; break;
		case MEPC: *value = mepc; break;
		case MSCRATCH: *value = mscratch; break;
		case MISA: *value = misa; break;
		case MEDELEG: *value = medeleg; break;
		case MIDELEG: *value = mideleg; break;
		case MHARTID: *value = 0; break;

		case SSTATUS: *value = (status.raw | (((status.raw & 0x6000) == 0x6000) ? 0x80000000 : 0)) & (0x800C0133 | STATUS_FS_MASK); break;
		case SIP: *value = getIp().raw & 0x333; break;
		case SIE: *value = ie.raw & 0x333; break;
		case STVEC: *value = stvec.raw; break;
		case SCAUSE: *value = scause.raw; break;
		case STVAL: *value = sbadaddr; break;
		case SEPC: *value = sepc; break;
		case SSCRATCH: *value = sscratch; break;
		case SATP: *value = satp.raw; break;

		#ifdef RVF
		case FCSR: *value = fcsr.raw; break;
		case FRM: *value = fcsr.frm; break;
		case FFLAGS: *value = fcsr.flags; break;
		#endif

        #ifdef UTIME_INPUT
		case UTIME: *value  = dutRfWriteValue; break;
		case UTIMEH: *value  = dutRfWriteValue; break;
		#endif

		default: {
            if(csr >= 0x3A0 && csr <= 0x3A3 || csr >= 0x3B0 && csr <= 0x3BF) break; //PMP
            return true;
		}break;
		}
//        if(csr == MSTATUS || csr == SSTATUS){
//            printf("READ  %x %x\n", pc, *value);
//        }
		return false;
	}

	virtual uint32_t csrReadToWriteOverride(int32_t csr, uint32_t value){
		if(((csr >> 8) & 0x3) > privilege) return true;
		switch(csr){
		case MIP: return ipSoft; break;
		case SIP: return ipSoft & 0x333; break;
		};
		return value;
	}

	#define maskedWrite(dst, src, mask) dst=((dst) & ~(mask))|((src) & (mask));

	virtual bool csrWrite(int32_t csr, uint32_t value){
		if(((csr >> 8) & 0x3) > privilege) return true;
//		if(csr == MSTATUS || csr == SSTATUS){
//		    printf("MIAOU %x %x\n", pc, value);
//		}
		switch(csr){
		case MSTATUS: status.raw = value & 0x7FFFFFFF; break;
		case MIP: ipSoft = value; break;
		case MIE: ie.raw = value; break;
		case MTVEC: mtvec.raw = value & 0xFFFFFFFC; break;
		case MCAUSE: mcause.raw = value; break;
		case MBADADDR: mbadaddr = value; break;
		case MEPC: mepc = value; break;
		case MSCRATCH: mscratch = value; break;
		case MISA: misa = value; break;
		case MEDELEG: medeleg = value & (~0x8); break;
		case MIDELEG: mideleg = value; break;

		case SSTATUS: maskedWrite(status.raw, value, 0xC0133 | STATUS_FS_MASK);  break;
		case SIP: maskedWrite(ipSoft, value,0x333); break;
		case SIE: maskedWrite(ie.raw, value,0x333); break;
		case STVEC: stvec.raw = value & 0xFFFFFFFC; break;
		case SCAUSE: scause.raw = value; break;
		case STVAL: sbadaddr = value; break;
		case SEPC: sepc = value; break;
		case SSCRATCH: sscratch = value; break;
		case SATP: satp.raw = value;  break;

		#ifdef RVF
		case FCSR: fcsr.raw = value & 0x7F; status.fs = 3; break;
		case FRM: fcsr.frm = value; status.fs = 3; break;
		case FFLAGS: fcsr.flags = value; status.fs = 3; break;
		#endif

		default: {
            if(csr >= 0x3A0 && csr <= 0x3A3 || csr >= 0x3B0 && csr <= 0x3BF) break; //PMP
            ilegalInstruction();
            return true;
		}break;
		}
//        if(csr == MSTATUS || csr == SSTATUS){
//            printf("      %x %x\n", pc, status.raw);
//        }
		return false;
	}

    
    int livenessStep = 0;
    int livenessInterrupt = 0;
    uint32_t pendingInterruptsPtr = 0;
    uint32_t pendingInterrupts[5] = {0,0,0,0,0};
    virtual void liveness(bool inWfi){
    	uint32_t pendingInterrupt = getPendingInterrupt();
    	pendingInterrupts[pendingInterruptsPtr++] = getPendingInterrupt();
    	if(pendingInterruptsPtr >= 5) pendingInterruptsPtr = 0;
        if(pendingInterrupt) livenessInterrupt++; else livenessInterrupt = 0;
        if(!inWfi) livenessStep++; else livenessStep = 0;

        if(livenessStep > 10000){
            cout << "Liveness step failure" << endl;
            fail();
        }
        
        if(livenessInterrupt > 1000){
            cout << "Liveness interrupt failure" << endl;
            fail();
        }
    }


    uint32_t getPendingInterrupt(){
    	uint32_t mEnabled = status.mie && (privilege == 3) || privilege < 3;
    	uint32_t sEnabled = status.sie && (privilege == 1) || privilege < 1;

    	uint32_t masked = getIp().raw & ~mideleg & -mEnabled & ie.raw;
		if (masked == 0)
			masked = getIp().raw & mideleg & -sEnabled & ie.raw & 0x333;

		if (masked) {
			if (masked & MIP_MEIP)
				masked &= MIP_MEIP;
			else if (masked & MIP_MSIP)
				masked &= MIP_MSIP;
			else if (masked & MIP_MTIP)
				masked &= MIP_MTIP;
			else if (masked & MIP_SEIP)
                masked &= MIP_SEIP;
            else if (masked & MIP_SSIP)
                masked &= MIP_SSIP;
            else if (masked & MIP_STIP)
                masked &= MIP_STIP;
            else {
                cout << "CPU model doesn't has pending interrupt" << endl;
			    fail();
            }
		}

		return masked;
    }


    bool isPcAligned(uint32_t pc){
#ifdef COMPRESSED
    	return (pc & 1) == 0;
#else
    	return (pc & 3) == 0;
#endif
    }



	virtual void step() {
	    stepCounter++;
	    livenessStep = 0;

	    while(fpuCompletionTockens != 0 && !fpuCompletion.empty()){
            FpuCompletion completion = fpuCompletion.front(); fpuCompletion.pop();
            fcsr.flags |= completion.flags;
            fpuCompletionTockens -= 1;
        }


		#define rd32 ((i >> 7) & 0x1F)
		#define iBits(lo,  len) ((i >> lo) & ((1 << len)-1))
		#define iBitsSigned(lo, len) int32_t(i) << (32-lo-len) >> (32-len)
		#define iSign() iBitsSigned(31, 1)
		#define i32_rs1 regs[(i >> 15) & 0x1F]
		#define i32_rs2 regs[(i >> 20) & 0x1F]
		#define i32_i_imm (int32_t(i) >> 20)
		#define i32_s_imm  (iBits(7, 5) + (iBitsSigned(25, 7) << 5))
		#define i32_shamt ((i >> 20) & 0x1F)
		#define i32_sb_imm ((iBits(8, 4) << 1) + (iBits(25,6) << 5) + (iBits(7,1) << 11) + (iSign() << 12))
		#define i32_csr iBits(20, 12)
		#define i32_func3 iBits(12, 3)
		#define i32_func7 iBits(25, 7)
		#define i16_addi4spn_imm ((iBits(6, 1) << 2) + (iBits(5, 1) << 3) + (iBits(11, 2) << 4) + (iBits(7, 4) << 6))
		#define i16_lw_imm ((iBits(6, 1) << 2) + (iBits(10, 3) << 3) + (iBits(5, 1) << 6))
		#define i16_addr2 (iBits(2,3) + 8)
		#define i16_addr1 (iBits(7,3) + 8)
		#define i16_rf1 regs[i16_addr1]
		#define i16_rf2 regs[i16_addr2]
		#define rf_sp regs[2]
		#define i16_imm (iBits(2, 5) + (iBitsSigned(12, 1) << 5))
		#define i16_j_imm ((iBits(3, 3) << 1) + (iBits(11, 1) << 4) + (iBits(2, 1) << 5) + (iBits(7, 1) << 6) + (iBits(6, 1) << 7) + (iBits(9, 2) << 8) + (iBits(8, 1) << 10) + (iBitsSigned(12, 1) << 11))
		#define i16_addi16sp_imm ((iBits(6, 1) << 4) + (iBits(2, 1) << 5) + (iBits(5, 1) << 6) + (iBits(3, 2) << 7) + (iBitsSigned(12, 1) << 9))
		#define i16_zimm (iBits(2, 5))
		#define i16_b_imm ((iBits(3, 2) << 1) + (iBits(10, 2) << 3) + (iBits(2, 1) << 5) + (iBits(5, 2) << 6) + (iBitsSigned(12, 1) << 8))
		#define i16_lwsp_imm ((iBits(4, 3) << 2) + (iBits(12, 1) << 5) + (iBits(2, 2) << 6))
		#define i16_swsp_imm ((iBits(9, 4) << 2) + (iBits(7, 2) << 6))
		uint32_t i;
		uint32_t u32Buf;
		uint32_t pAddr;
		if (pc & 2) {
			if(v2p(pc - 2, &pAddr, EXECUTE)){ trap(0, 12, pc - 2); return; }
			if(iRead(pAddr, &i)){
				trap(0, 1, 0);
				return;
			}
			i >>= 16;
			if ((i & 3) == 3) {
				uint32_t u32Buf;
				if(v2p(pc + 2, &pAddr, EXECUTE)){ trap(0, 12, pc + 2); return; }
				if(iRead(pAddr, &u32Buf)){
					trap(0, 1, 0);
					return;
				}
				i |= u32Buf << 16;
			}
		} else {
			if(v2p(pc, &pAddr, EXECUTE)){ trap(0, 12, pc); return; }
			if(iRead(pAddr, &i)){
				trap(0, 1, 0);
				return;
			}
		}
		lastInstruction = i;
		currentInstruction = i;
		if ((i & 0x3) == 0x3) {
			//32 bit
			switch (i & 0x7F) {
			#ifdef RVF
			case 0x43:// RVFD
			case 0x47:
			case 0x4B:
			case 0x4F:
			case 0x53: {
			    u32 format = iBits(25,2);
			    u32 opcode = iBits(27,5);
			    bool withCommit = fpuCommitLut[opcode];
			    bool withRsp = fpuRspLut[opcode];
			    bool withRs1 = fpuRs1Lut[opcode];
			    if((i & 0x7F) != 0x53) { // FMADD
			        withCommit = true;
			        withRsp = false;
			    }
			    #ifdef RVD
			    if(format > 1) ilegalInstruction();
			    #else
			    if(format > 0) ilegalInstruction();
			    #endif

			    if(withCommit){
			        FpuCommit commit = fpuCommit.front(); fpuCommit.pop();
			        fpuCompletionTockens += 1;
//			        cout << "withRs1 " << withRs1 << " " << opcode << endl;
                    if(withRs1 && memcmp(&i32_rs1, &commit.value, 4)){
                        cout << "FPU commit missmatch DUT=" << hex << commit.value << " REF=" << i32_rs1 << dec << endl;
                        fail();
                        return;
                    }
			    }
			    if(withRsp){
			        auto rsp = fpuRsp.front(); fpuRsp.pop();
			        fcsr.flags |= rsp.flags;
			        rfWrite(rd32, (u32)rsp.value);
			    }
                status.fs = 3;
                pcWrite(pc + 4);
			} break;
			case 0x07: { //Fpu load
                uint32_t size = 1 << ((i >> 12) & 0x3);
                if(size < 4) ilegalInstruction();
                #ifdef RVD
                if(size > 8) ilegalInstruction();
                #else
                if(format > 4) ilegalInstruction();
                #endif
                auto commit = fpuCommit.front();  fpuCommit.pop();
                fpuCompletionTockens += 1;


                uint64_t data = 0;
                uint32_t address = i32_rs1 + i32_i_imm;
                if(address & (size-1)){
                    trap(0, 4, address);
                } else {
                    if(v2p(address, &pAddr, READ)){ trap(0, 13, address); return; }
                    if(dRead(pAddr, size, (uint8_t*)&data)){
                        trap(0, 5, address);
                    } else {
                        if(memcmp(&data, &commit.value, size)){
                            cout << "FPU load missmatch DUT=" << hex << commit.value << " REF=" << data << dec << endl;
                            fail();
                        } else {
                            status.fs = 3;
                            pcWrite(pc + 4);
                        }
                    }
                }
			} break;
			case 0x27: { //Fpu store
                uint32_t size = 1 << ((i >> 12) & 0x3);
                if(size < 4) ilegalInstruction();
                #ifdef RVD
                if(size > 8) ilegalInstruction();
                #else
                if(format > 4) ilegalInstruction();
                #endif

                auto rsp = fpuRsp.front(); fpuRsp.pop();
                fcsr.flags |= rsp.flags;
                uint32_t address = i32_rs1 + i32_s_imm;
                if(address & (size-1)){
                    trap(0, 6, address);
                } else {
                    if(v2p(address, &pAddr, WRITE)){ trap(0, 15, address); return; }
                    dWrite(pAddr, size, (uint8_t*) &rsp.value);
                    status.fs = 3;
                    pcWrite(pc + 4);
                    lrscReserved = false;
                }
			} break;
			#endif
			case 0x37:rfWrite(rd32, i & 0xFFFFF000);pcWrite(pc + 4);break; // LUI
			case 0x17:rfWrite(rd32, (i & 0xFFFFF000) + pc);pcWrite(pc + 4);break; //AUIPC
			case 0x6F:rfWrite(rd32, pc + 4);pcWrite(pc + (iBits(21, 10) << 1) + (iBits(20, 1) << 11) + (iBits(12, 8) << 12) + (iSign() << 20));break; //JAL
			case 0x67:{
				uint32_t target = (i32_rs1 + i32_i_imm) & ~1;
				if(isPcAligned(target)) rfWrite(rd32, pc + 4);
				pcWrite(target);
			} break; //JALR
			case 0x63:
				switch ((i >> 12) & 0x7) {
				case 0x0:if (i32_rs1 == i32_rs2)pcWrite(pc + i32_sb_imm);else pcWrite(pc + 4);break;
				case 0x1:if (i32_rs1 != i32_rs2)pcWrite(pc + i32_sb_imm);else pcWrite(pc + 4);break;
				case 0x4:if (i32_rs1 < i32_rs2)pcWrite(pc + i32_sb_imm); else pcWrite(pc + 4);break;
				case 0x5:if (i32_rs1 >= i32_rs2)pcWrite(pc + i32_sb_imm);else pcWrite(pc + 4);break;
				case 0x6:if (uint32_t(i32_rs1) < uint32_t(i32_rs2)) pcWrite(pc + i32_sb_imm); else pcWrite(pc + 4);break;
				case 0x7:if (uint32_t(i32_rs1) >= uint32_t(i32_rs2))pcWrite(pc + i32_sb_imm); else pcWrite(pc + 4);break;
				}
				break;
			case 0x03:{ //LOADS
				uint32_t data;
				uint32_t address = i32_rs1 + i32_i_imm;
				uint32_t size = 1 << ((i >> 12) & 0x3);
				if(address & (size-1)){
					trap(0, 4, address);
				} else {
					if(v2p(address, &pAddr, READ)){ trap(0, 13, address); return; }
					if(dRead(pAddr, size, (uint8_t*)&data)){
					    trap(0, 5, address);
					} else {
                        switch ((i >> 12) & 0x7) {
                        case 0x0:rfWrite(rd32, int8_t(data));pcWrite(pc + 4);break;
                        case 0x1:rfWrite(rd32, int16_t(data));pcWrite(pc + 4);break;
                        case 0x2:rfWrite(rd32, int32_t(data));pcWrite(pc + 4);break;
                        case 0x4:rfWrite(rd32, uint8_t(data));pcWrite(pc + 4);break;
                        case 0x5:rfWrite(rd32, uint16_t(data));pcWrite(pc + 4);break;
                        }
					}
				}
			}break;
			case 0x23: { //STORE
				uint32_t address = i32_rs1 + i32_s_imm;
				uint32_t size = 1 << ((i >> 12) & 0x3);
				if(address & (size-1)){
					trap(0, 6, address);
				} else {
					if(v2p(address, &pAddr, WRITE)){ trap(0, 15, address); return; }
					dWrite(pAddr, size, (uint8_t*)&i32_rs2);
					pcWrite(pc + 4);
                    lrscReserved = false;
				}
			}break;
			case 0x13: //ALUi
				switch ((i >> 12) & 0x7) {
				case 0x0:rfWrite(rd32, i32_rs1 + i32_i_imm);pcWrite(pc + 4);break;
				case 0x1:
					switch ((i >> 25) & 0x7F) {
					case 0x00:rfWrite(rd32, i32_rs1 << i32_shamt);pcWrite(pc + 4);break;
					}
					break;
				case 0x2:rfWrite(rd32, i32_rs1 < i32_i_imm);pcWrite(pc + 4);break;
				case 0x3:rfWrite(rd32, uint32_t(i32_rs1) < uint32_t(i32_i_imm));pcWrite(pc + 4);break;
				case 0x4:rfWrite(rd32, i32_rs1 ^ i32_i_imm);pcWrite(pc + 4);break;
				case 0x5:
					switch ((i >> 25) & 0x7F) {
					case 0x00:rfWrite(rd32, uint32_t(i32_rs1) >> i32_shamt);pcWrite(pc + 4);break;
					case 0x20:rfWrite(rd32, i32_rs1 >> i32_shamt);pcWrite(pc + 4);break;
					}
					break;
				case 0x6:rfWrite(rd32, i32_rs1 | i32_i_imm);pcWrite(pc + 4);break;
				case 0x7:	rfWrite(rd32, i32_rs1 & i32_i_imm);pcWrite(pc + 4);break;
				}
				break;
			case 0x33: //ALU
				if (((i >> 25) & 0x7F) == 0x01) {
					switch ((i >> 12) & 0x7) {
					case 0x0:rfWrite(rd32, int32_t(i32_rs1) * int32_t(i32_rs2));pcWrite(pc + 4);break;
					case 0x1:rfWrite(rd32,(int64_t(i32_rs1) * int64_t(i32_rs2)) >> 32);pcWrite(pc + 4);break;
					case 0x2:rfWrite(rd32,(int64_t(i32_rs1) * uint64_t(uint32_t(i32_rs2)))>> 32);pcWrite(pc + 4);break;
					case 0x3:rfWrite(rd32,(uint64_t(uint32_t(i32_rs1)) * uint64_t(uint32_t(i32_rs2))) >> 32);pcWrite(pc + 4);break;
					case 0x4:rfWrite(rd32,i32_rs2 == 0 ? -1 : int64_t(i32_rs1) / int64_t(i32_rs2));pcWrite(pc + 4);break;
					case 0x5:rfWrite(rd32,i32_rs2 == 0 ? -1 : uint32_t(i32_rs1) / uint32_t(i32_rs2));pcWrite(pc + 4);break;
					case 0x6:rfWrite(rd32,i32_rs2 == 0 ? i32_rs1 : int64_t(i32_rs1)% int64_t(i32_rs2));pcWrite(pc + 4);break;
					case 0x7:rfWrite(rd32,i32_rs2 == 0 ? i32_rs1 : uint32_t(i32_rs1) % uint32_t(i32_rs2));pcWrite(pc + 4);break;
					}
				} else {
					switch ((i >> 12) & 0x7) {
					case 0x0:
						switch ((i >> 25) & 0x7F) {
						case 0x00:rfWrite(rd32, i32_rs1 + i32_rs2);pcWrite(pc + 4);break;
						case 0x20:rfWrite(rd32, i32_rs1 - i32_rs2);pcWrite(pc + 4);break;
						}
						break;
					case 0x1:rfWrite(rd32, i32_rs1 << (i32_rs2 & 0x1F));pcWrite(pc + 4);break;
					case 0x2:rfWrite(rd32, i32_rs1 < i32_rs2);pcWrite(pc + 4);break;
					case 0x3:rfWrite(rd32, uint32_t(i32_rs1) < uint32_t(i32_rs2));pcWrite(pc + 4);break;
					case 0x4:rfWrite(rd32, i32_rs1 ^ i32_rs2);pcWrite(pc + 4);break;
					case 0x5:
						switch ((i >> 25) & 0x7F) {
						case 0x00:rfWrite(rd32, uint32_t(i32_rs1) >> (i32_rs2 & 0x1F));pcWrite(pc + 4);break;
						case 0x20:rfWrite(rd32, i32_rs1 >> (i32_rs2 & 0x1F));pcWrite(pc + 4);break;
						}
						break;
					case 0x6:rfWrite(rd32, i32_rs1 | i32_rs2);pcWrite(pc + 4);break;
					case 0x7:rfWrite(rd32, i32_rs1 & i32_rs2); pcWrite(pc + 4);break;
					}
				}
				break;
			case 0x73:{
				if(i32_func3 == 0){
					switch(i){
					case 0x30200073:{ //MRET
						if(privilege < 3){ ilegalInstruction(); return;}
						privilege = status.mpp;
						status.mie = status.mpie;
						status.mpie = 1;
						status.mpp = 0;
						pcWrite(mepc);
					}break;
					case 0x10200073:{ //SRET
						if(privilege < 1){ ilegalInstruction(); return;}
						privilege = status.spp;
						status.sie = status.spie;
						status.spie = 1;
						status.spp = 0;
						pcWrite(sepc);
					}break;
					case 0x00000073:{ //ECALL
						trap(0, 8+privilege, 0x00000073); //To follow the VexRiscv area saving implementation
					}break;
					case 0x10500073:{ //WFI
						pcWrite(pc + 4);
					}break;
					default:
						if((i & 0xFE007FFF) == 0x12000073){ //SFENCE.VMA
							pcWrite(pc + 4);
						}else {
							ilegalInstruction();
						}
					break;
					}
				} else {
					//CSR
					uint32_t input = (i & 0x4000) ? ((i >> 15) & 0x1F) : i32_rs1;
					uint32_t clear, set;
					bool write;
					switch ((i >> 12) & 0x3) {
					case 1: clear = ~0; set = input; write = true; break;
					case 2: clear = 0; set = input; write = ((i >> 15) & 0x1F) != 0; break;
					case 3: clear = input; set = 0; write = ((i >> 15) & 0x1F) != 0; break;
					}
					uint32_t csrAddress = i32_csr;
					uint32_t old;
					if(csrRead(i32_csr, &old)) { ilegalInstruction();return; }
					if(write) if(csrWrite(i32_csr, (csrReadToWriteOverride(i32_csr, old) & ~clear) | set)) { ilegalInstruction();return; }
					rfWrite(rd32, old);
					pcWrite(pc + 4);
				}
				break;
			}
			case 0x2F: // Atomic stuff
				switch(i32_func3){
				case 0x2:
					switch(iBits(27,5)){
					case 0x2:{ //LR
						uint32_t data;
						uint32_t address = i32_rs1;
						if(address & 3){
							trap(0, 4, address);
						} else {
							if(v2p(address, &pAddr, READ)){ trap(0, 13, address); return; }
							if(dRead(pAddr, 4, (uint8_t*)&data)){
							    trap(0, 5, address);
							} else {
								lrscReserved = true;
								lrscReservedAddress = pAddr;
								rfWrite(rd32, data);
								pcWrite(pc + 4);
							}
						}
					}	break;
					case 0x3:{ //SC
						uint32_t address = i32_rs1;
						if(address & 3){
							trap(0, 6, address);
						} else {
							if(v2p(address, &pAddr, WRITE)){ trap(0, 15, address); return; }
							#ifdef DBUS_EXCLUSIVE
                            bool hit = lrscReserved && lrscReservedAddress == pAddr;
                            #else
                            bool hit = lrscReserved;
                            #endif
							if(hit){
								dWrite(pAddr, 4, (uint8_t*)&i32_rs2);
							}
							lrscReserved = false;
							rfWrite(rd32, !hit);
							pcWrite(pc + 4);
						}
					}	break;
					default: {
                        #ifndef AMO
                        ilegalInstruction();
                        #else
                        uint32_t sel = (i >> 27) & 0x1F;
                        uint32_t addr = i32_rs1;
                        int32_t  src = i32_rs2;
                        int32_t readValue;

                        lrscReserved = false;


                        uint32_t pAddr;
						if(v2p(addr, &pAddr, READ_WRITE)){ trap(0, 15, addr); return; }
                        if(dRead(pAddr, 4, (uint8_t*)&readValue)){
                        	trap(0, 15, addr); return;
                            return;
                        }
                        int writeValue;
                        switch(sel){
                        case 0x0:  writeValue = src + readValue; break;
                        case 0x1:  writeValue = src; break;
                        case 0x4:  writeValue = src ^ readValue; break;
                        case 0xC:  writeValue = src & readValue; break;
                        case 0x8:  writeValue = src | readValue; break;
                        case 0x10: writeValue = min(src, readValue); break;
                        case 0x14: writeValue = max(src, readValue); break;
                        case 0x18: writeValue = min((unsigned int)src, (unsigned int)readValue); break;
                        case 0x1C: writeValue = max((unsigned int)src, (unsigned int)readValue); break;
                        default: ilegalInstruction(); return; break;
                        }
                        dWrite(pAddr, 4, (uint8_t*)&writeValue);
						rfWrite(rd32, readValue);
						pcWrite(pc + 4);
                        #endif
					 } break;
					}
					break;
				default: ilegalInstruction(); break;
				}
				break;
				case 0x0f:
				    if(i == 0x100F || (i & 0xF00FFFFF) == 0x000F){ // FENCE FENCE.I
							pcWrite(pc + 4);
				    } else{
				        ilegalInstruction();
				    }
				break;
			default: ilegalInstruction(); break;
			}
		} else {
			#ifndef COMPRESSED
			cout << "ERROR : RiscvGolden got a RVC instruction while the CPU isn't RVC ready" << endl;
	        ilegalInstruction(); return;
			#endif
			switch((iBits(0, 2) << 3) + iBits(13, 3)){
			case 0: rfWrite(i16_addr2, rf_sp + i16_addi4spn_imm); pcWrite(pc + 2); break;
			case 2:  {
				uint32_t data;
				uint32_t address = i16_rf1 + i16_lw_imm;
				if(address & 0x3){
					trap(0, 4, address);
				} else {
					if(v2p(address, &pAddr, READ)){ trap(0, 13, address); return; }
					if(dRead(pAddr, 4, (uint8_t*)&data)) {
					    trap(0, 5, address);
					} else {
					    rfWrite(i16_addr2, data); pcWrite(pc + 2);
                    }
				}
			} break;
			case 6: {
				uint32_t address = i16_rf1 + i16_lw_imm;
				if(address & 0x3){
					trap(0, 6, address);
				} else {
					if(v2p(address, &pAddr, WRITE)){ trap(0, 15, address); return; }
					dWrite(pAddr, 4, (uint8_t*)&i16_rf2);
                    pcWrite(pc + 2);
                    lrscReserved = false;
				}
			}break;
			case 8: rfWrite(rd32, regs[rd32] + i16_imm); pcWrite(pc + 2); break;
			case 9: rfWrite(1, pc + 2);pcWrite(pc + i16_j_imm); break;
			case 10: rfWrite(rd32, i16_imm);pcWrite(pc + 2); break;
			case 11:
				if(rd32 == 2) { rfWrite(2, rf_sp + i16_addi16sp_imm);pcWrite(pc + 2);  }
				else {  		rfWrite(rd32, i16_imm << 12);pcWrite(pc + 2);  } break;
			case 12:
				switch(iBits(10,2)){
				case 0: rfWrite(i16_addr1, uint32_t(i16_rf1) >> i16_zimm); pcWrite(pc + 2);break;
				case 1: rfWrite(i16_addr1, i16_rf1 >> i16_zimm); pcWrite(pc + 2);break;
				case 2: rfWrite(i16_addr1, i16_rf1 & i16_imm); pcWrite(pc + 2);break;
				case 3:
					switch(iBits(5,2)){
					case 0: rfWrite(i16_addr1, i16_rf1 - i16_rf2); pcWrite(pc + 2);break;
					case 1: rfWrite(i16_addr1, i16_rf1 ^ i16_rf2); pcWrite(pc + 2);break;
					case 2: rfWrite(i16_addr1, i16_rf1 | i16_rf2); pcWrite(pc + 2);break;
					case 3: rfWrite(i16_addr1, i16_rf1 & i16_rf2); pcWrite(pc + 2);break;
					}
					break;
				}
				break;
			case 13: pcWrite(pc + i16_j_imm); break;
			case 14: pcWrite(i16_rf1 == 0 ? pc + i16_b_imm : pc + 2); break;
			case 15: pcWrite(i16_rf1 != 0 ? pc + i16_b_imm : pc + 2); break;
			case 16: rfWrite(rd32, regs[rd32] << i16_zimm); pcWrite(pc + 2); break;
			case 18:{
				uint32_t data;
				uint32_t address = rf_sp + i16_lwsp_imm;
				if(address & 0x3){
					trap(0, 4, address);
				} else {
					if(v2p(address, &pAddr, READ)){ trap(0, 13, address); return; }
				    if(dRead(pAddr, 4,(uint8_t*) &data)){
					    trap(0, 5, address);
                    } else {
					    rfWrite(rd32, data); pcWrite(pc + 2);
                    }
				}
			}break;
			case 20:
				if(i & 0x1000){
					if(iBits(2,10) == 0){

					} else if(iBits(2,5) == 0){
						rfWrite(1, pc + 2); pcWrite(regs[rd32] & ~1);
					} else {
						rfWrite(rd32, regs[rd32] + regs[iBits(2,5)]); pcWrite(pc + 2);
					}
				} else {
					if(iBits(2,5) == 0){
						pcWrite(regs[rd32] & ~1);
					} else {
						rfWrite(rd32, regs[iBits(2,5)]); pcWrite(pc + 2);
					}
				}
				break;
			case 22: {
				uint32_t address = rf_sp + i16_swsp_imm;
				if(address & 3){
					trap(0,6, address);
				} else {
					if(v2p(address, &pAddr, WRITE)){ trap(0, 15, address); return; }
					dWrite(pAddr, 4, (uint8_t*)&regs[iBits(2,5)]); pcWrite(pc + 2);
                    lrscReserved = false;
				}
			}break;
			}
		}
	}
};


class SimElement{
public:
	virtual ~SimElement(){}
	virtual void onReset(){}
	virtual void postReset(){}
	virtual void preCycle(){}
	virtual void postCycle(){}
};



class Workspace;

class Workspace{
public:
	static mutex staticMutex;
	static uint32_t testsCounter, successCounter;
	static uint64_t cycles;
	uint64_t instanceCycles = 0;
	vector<SimElement*> simElements;
	Memory mem;
	string name;
	uint64_t currentTime = 22;
	uint64_t mTimeCmp = 0;
	uint64_t mTime = 0;
	VVexRiscv* top;
	bool resetDone = false;
	bool riscvRefEnable = false;
	uint64_t i;
	double cyclesPerSecond = 10e6;
	double allowedCycles = 0.0;
	uint32_t bootPc = -1;
	uint32_t iStall = STALL,dStall = STALL;
	#ifdef TRACE
	VerilatedFstC* tfp;
	#endif
	bool allowInvalidate = true;

	uint32_t seed;

	Workspace* setIStall(bool enable) { iStall = enable; return this; }
	Workspace* setDStall(bool enable) { dStall = enable; return this; }

	ofstream regTraces;
	ofstream memTraces;
	ofstream logTraces;
	ofstream debugLog;

	struct timespec start_time;

    class CpuRef : public RiscvGolden{
    public:
    	Memory mem;

    	class MemWrite {
    	public:
    		int32_t address, size;
    		uint8_t data42[64];
    	};

    	class MemRead {
    	public:
    		int32_t address, size;
    		uint8_t data42[64];
    		bool error;
    	};

        uint32_t periphWriteTimer = 0;
    	queue<MemWrite> periphWritesGolden;
    	queue<MemWrite> periphWrites;
    	queue<MemRead> periphRead;
    	Workspace *ws;
    	CpuRef(Workspace *ws){
			this->ws = ws;
    	}

    	virtual void fail() { ws->fail(); }


	    virtual bool isMmuRegion(uint32_t v) {return ws->isMmuRegion(v);}

    	bool rfWriteValid;
    	int32_t rfWriteAddress;
    	int32_t rfWriteData;
        virtual void rfWrite(int32_t address, int32_t data){
        	rfWriteValid = address != 0;
        	rfWriteAddress = address;
        	rfWriteData = data;
        	RiscvGolden::rfWrite(address,data);
        }


        virtual bool iRead(int32_t address, uint32_t *data){
        	bool error;
        	ws->iBusAccess(address, data, &error);
//    		ws->iBusAccessPatch(address,data,&error);
    		return error;
        }

        virtual bool dRead(int32_t address, int32_t size, uint8_t *data){
            if(size < 1 || size > 8){
                cout << "dRead size=" << size << endl;
                fail();
            }
            if((address & (size-1)) != 0)
            	cout << "Ref did a unaligned read" << endl;
    		if(ws->isPerifRegion(address)){
				MemRead t = periphRead.front();
				if(t.address != address || t.size != size){
					cout << "DRead missmatch" << hex <<  endl;
					cout << " REF : address=" << address << " size=" << size << endl;
					cout << " DUT : address=" << t.address  << " size=" << t.size << endl;
					fail();
				}

                for(int i = 0; i < size; i++){
                    data[i] = t.data42[i];
                }
				periphRead.pop();
				return t.error;
    		}else {
            	mem.read(address, size, data);
    		}
    		return false;
        }
        virtual void dWrite(int32_t address, int32_t size, uint8_t *data){
            if(address & (size-1) != 0)
            	cout << "Ref did a unaligned write" << endl;

    		if(!ws->isPerifRegion(address)){
    			mem.write(address, size, data);
    		}
    		if(ws->isDBusCheckedRegion(address)){
				MemWrite w;
				w.address = address;
				w.size = size;
                for(int i = 0; i < size; i++){
				    w.data42[i] = data[i];
				}
				periphWritesGolden.push(w);
				if(periphWritesGolden.size() > 10){
				    cout << "??? periphWritesGolden" << endl;
				    fail();
				}
    		}
        }


        void step() {
        	rfWriteValid = false;
        	RiscvGolden::step();

        	switch(periphWrites.empty() + uint32_t(periphWritesGolden.empty())*2){
        	case 3: periphWriteTimer = 0; break;
        	case 1: case 2: if(periphWriteTimer++ == 20){
        		cout << "periphWrite timout" << endl; fail();
        	} break;
        	case 0:
    			MemWrite t = periphWrites.front();
    			MemWrite t2 = periphWritesGolden.front();
    			bool dataMatch = true;
    			for(int i = 0;i < min(t.size, t2.size);i++) dataMatch &= t.data42[i] == t2.data42[i];
    			if(t.address != t2.address || t.size != t2.size || !dataMatch){
    				cout << hex << "periphWrite missmatch" << endl;
    				cout << " DUT address=" << t.address << " size=" << t.size  << " data=" << *((uint32_t*)t.data42) << endl;
    				cout << " REF address=" << t2.address << " size=" << t2.size  << " data=" << *((uint32_t*)t2.data42) << endl;
    				fail();
    			}
    			periphWrites.pop();
    			periphWritesGolden.pop();
    			periphWriteTimer = 0;
    			break;
        	}


        }
    };

	CpuRef riscvRef = CpuRef(this);
    string vcdName;
    Workspace* setVcdName(string name){
        vcdName = name;
        return this;
    }
	Workspace(string name){
	    vcdName = name;
	    //seed = VL_RANDOM_I_WIDTH(32)^VL_RANDOM_I_WIDTH(32)^0x1093472;
	    //srand48(seed);
    //    setIStall(false);
   //     setDStall(false);
		staticMutex.lock();
		testsCounter++;
		staticMutex.unlock();
		this->name = name;
		top = new VVexRiscv;
		#ifdef TRACE_ACCESS
			regTraces.open (name + ".regTrace");
			memTraces.open (name + ".memTrace");
		#endif
		logTraces.open (name + ".logTrace");
		debugLog.open (name + ".debugTrace");
		fillSimELements();
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start_time);
	}

	virtual ~Workspace(){
		delete top;
		#ifdef TRACE
		delete tfp;
		#endif

		for(SimElement* simElement : simElements) {
			delete simElement;
		}
	}

	Workspace* loadHex(string path){
		loadHexImpl(path,&mem);
		loadHexImpl(path,&riscvRef.mem);
		return this;
	}

    Workspace* loadBin(string path, uint32_t offset){
    	loadBinImpl(path,&mem, offset);
    	loadBinImpl(path,&riscvRef.mem, offset);
        return this;
    }

	Workspace* setCyclesPerSecond(double value){
		cyclesPerSecond = value;
		return this;
	}

    Workspace* bootAt(uint32_t pc) {
    	bootPc = pc;
    	riscvRef.pc = pc;
		return this;
    }

    Workspace* withRiscvRef(){
        #ifdef WITH_RISCV_REF
    	riscvRefEnable = true;
        #endif
		return this;
    }

    Workspace* withInvalidation(){
        allowInvalidate = true;
        return this;
    }
    Workspace* withoutInvalidation(){
        allowInvalidate = false;
        return this;
    }
    Workspace* writeWord(uint32_t address, uint32_t data){
        mem.write(address, 4, (uint8_t*)&data);
        riscvRef.mem.write(address, 4, (uint8_t*)&data);
        return this;
    }

    virtual bool isPerifRegion(uint32_t addr) { return false; }
    virtual bool isMmuRegion(uint32_t addr) { return true;}
    virtual void iBusAccess(uint32_t addr, uint32_t *data, bool *error) {
		if(addr % 4 != 0) {
			cout << "Warning, unaligned IBusAccess : " << addr << endl;
			fail();
		}
		*data =     (  (mem[addr + 0] << 0)
					 | (mem[addr + 1] << 8)
					 | (mem[addr + 2] << 16)
					 | (mem[addr + 3] << 24));
		*error = false;
	}


    virtual bool isDBusCheckedRegion(uint32_t address){ return isPerifRegion(address);}
	virtual void dBusAccess(uint32_t addr,bool wr, uint32_t size, uint8_t *data, bool *error) {
		assertEq(addr % size, 0);
		if(!isPerifRegion(addr)) {
			if(wr){
				for(uint32_t b = 0;b < size;b++){
                    *mem.get(addr + b) = ((uint8_t*)data)[b];
				}

			}else{
                uint32_t innerOffset = addr & (DBUS_LOAD_DATA_WIDTH/8-1);
				for(uint32_t b = 0;b < size;b++){
					((uint8_t*)data)[b] = mem[addr + b];
				}
			}
		}


		if(wr){
			if(isDBusCheckedRegion(addr)){
				CpuRef::MemWrite w;
				w.address = addr;
				w.size = size;
				for(uint32_t b = 0;b < size;b++){
				    w.data42[b] = data[b];
				}
				riscvRef.periphWrites.push(w);
			}
		} else {
			if(isPerifRegion(addr)){
				CpuRef::MemRead r;
				r.address = addr;
				r.size = size;
				for(uint32_t b = 0;b < size;b++){
				    r.data42[b] = data[b];
				}
				r.error = *error;
				riscvRef.periphRead.push(r);
			}
		}
	}

//	void periphAccess(uint32_t addr,bool wr, uint32_t size,uint32_t mask, uint32_t *data, bool *error){
//		if(wr){
//			CpuRef::MemWrite w;
//			w.address = addr;
//			w.size = 1 << size;
//			w.data = *data;
//			riscvRef.periphWrites.push(w);
//		} else {
//			CpuRef::MemRead r;
//			r.address = addr;
//			r.size = 1 << size;
//			r.data = *data;
//			r.error = *error;
//			riscvRef.periphRead.push(r);
//		}
//	}

	virtual void postReset() {}
	virtual void checks(){}
	virtual void pass(){ throw success();}
	virtual void fail(){ throw std::exception();}
    virtual void fillSimELements();
	void dump(uint64_t i){
		#ifdef TRACE
		if(i == TRACE_START && i != 0) cout << "**" << endl << "**" << endl << "**" << endl << "**" << endl << "**" << endl << "START TRACE" << endl;
		if(i >= TRACE_START) tfp->dump(i);
		#ifdef TRACE_SPORADIC
		else if(i % 1000000 < 100) tfp->dump(i);
		#endif
		#endif
	}

	uint64_t privilegeCounters[4] = {0,0,0,0};
	Workspace* run(uint64_t timeout = 5000){
//		cout << "Start " << name << endl;
		if(timeout == 0) timeout = 0x7FFFFFFFFFFFFFFF;

		currentTime = 4;
		// init trace dump
		#ifdef TRACE
		Verilated::traceEverOn(true);
		tfp = new VerilatedFstC;
		top->trace(tfp, 99);
		tfp->open((vcdName + ".fst").c_str());
		#endif

		// Reset
		top->clk = 0;
		top->reset = 0;


		top->eval(); currentTime = 3;
		for(SimElement* simElement : simElements) simElement->onReset();

		top->reset = 1;
		top->eval();
		top->clk = 1;
		top->eval();
		top->clk = 0;
		top->eval();
		#ifdef CSR
		top->timerInterrupt = 0;
		top->externalInterrupt = 1;
		top->softwareInterrupt = 0;
		#endif
		#ifdef SUPERVISOR
		top->externalInterruptS = 0;
		#endif
		#ifdef DEBUG_PLUGIN_EXTERNAL
		top->timerInterrupt = 0;
		top->externalInterrupt = 0;
		#endif
		dump(0);
		top->reset = 0;
		for(SimElement* simElement : simElements) simElement->postReset();

		top->eval(); currentTime = 2;


		postReset();

        //Sync register file initial content
        for(int i = 1;i < 32;i++){
            riscvRef.regs[i] = top->VexRiscv->RegFilePlugin_regFile[i];
        }
		resetDone = true;

		#ifdef  REF
		if(bootPc != -1) top->VexRiscv->core->prefetch_pc = bootPc;
		#else
		if(bootPc != -1) {
		    #if defined(IBUS_SIMPLE) || defined(IBUS_SIMPLE_WISHBONE) || defined(IBUS_SIMPLE_AHBLITE3)
                top->VexRiscv->IBusSimplePlugin_fetchPc_pcReg = bootPc;
                #ifdef COMPRESSED
                top->VexRiscv->IBusSimplePlugin_decodePc_pcReg = bootPc;
                #endif
            #else
                top->VexRiscv->IBusCachedPlugin_fetchPc_pcReg = bootPc;
                #ifdef COMPRESSED
                top->VexRiscv->IBusCachedPlugin_decodePc_pcReg = bootPc;
                #endif
            #endif
		}
		#endif


        bool failed = false;
		try {
			// run simulation for 100 clock periods
			for (i = 16; i < timeout*2; i+=2) {
				/*while(allowedCycles <= 0.0){
					struct timespec end_time;
					clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end_time);
					uint64_t diffInNanos = end_time.tv_sec*1e9 + end_time.tv_nsec -  start_time.tv_sec*1e9 - start_time.tv_nsec;
					start_time = end_time;
					double dt = diffInNanos*1e-9;
					allowedCycles += dt*cyclesPerSecond;
					if(allowedCycles > cyclesPerSecond/100) allowedCycles = cyclesPerSecond/100;
				}
				allowedCycles-=1.0;*/


				#ifndef REF_TIME
                #ifndef MTIME_INSTR_FACTOR
                mTime = i/2;
                #else
				mTime += top->VexRiscv->lastStageIsFiring*MTIME_INSTR_FACTOR;
                #endif
				#endif
				#ifdef TIMER_INTERRUPT
				top->timerInterrupt = mTime >= mTimeCmp ? 1 : 0;
				//if(mTime == mTimeCmp) printf("SIM timer tick\n");
				#endif


				#ifdef UTIME_INPUT
				top->utime = mTime;
				#endif

				currentTime = i;

                #ifdef FLOW_INFO
                    if(i % 5000000 == 0) cout << endl << "**" << endl << "**"  << endl << "PROGRESS TRACE_START=" << i << endl;
                #endif


				// dump variables into VCD file and toggle clock

				dump(i);
				//top->eval();
				top->clk = 0;
				top->eval();

				#ifdef CSR
				    if(riscvRefEnable) {
                        riscvRef.ipInput = 0;
    #ifdef TIMER_INTERRUPT
                        riscvRef.ipInput |= top->timerInterrupt << 7;
    #endif
    #ifdef EXTERNAL_INTERRUPT
                        riscvRef.ipInput |= top->externalInterrupt << 11;
    #endif
    #ifdef CSR
                        riscvRef.ipInput |= top->softwareInterrupt << 3;
    #endif
    #ifdef SUPERVISOR
    //					riscvRef.ipInput |= top->timerInterruptS << 5;
                        riscvRef.ipInput |= top->externalInterruptS << 9;
    #endif

                        riscvRef.liveness(top->VexRiscv->CsrPlugin_inWfi);
                        if(top->VexRiscv->CsrPlugin_interruptJump){
                            if(riscvRefEnable) riscvRef.trap(true, top->VexRiscv->CsrPlugin_interrupt_code);
                        }
                    }
				#endif

                #ifdef RVF
                if(riscvRefEnable) {
                    if(top->VexRiscv->writeBack_FpuPlugin_commit_valid && top->VexRiscv->writeBack_FpuPlugin_commit_ready && top->VexRiscv->writeBack_FpuPlugin_commit_payload_write){
                        FpuCommit c;
                        c.value = top->VexRiscv->writeBack_FpuPlugin_commit_payload_value;
                        riscvRef.fpuCommit.push(c);
                    }

                    if(top->VexRiscv->FpuPlugin_port_rsp_valid && top->VexRiscv->FpuPlugin_port_rsp_ready && top->VexRiscv->lastStageIsFiring){
                        FpuRsp c;
                        c.value = top->VexRiscv->FpuPlugin_port_rsp_payload_value;
                        c.flags = (top->VexRiscv->FpuPlugin_port_rsp_payload_NX << 0) |
                                  (top->VexRiscv->FpuPlugin_port_rsp_payload_NV << 4);
                        riscvRef.fpuRsp.push(c);
                    }

                    if(top->VexRiscv->FpuPlugin_port_completion_valid && top->VexRiscv->FpuPlugin_port_completion_payload_written){
                        FpuCompletion c;
                        c.flags = (top->VexRiscv->FpuPlugin_port_completion_payload_flags_NX << 0) |
                                  (top->VexRiscv->FpuPlugin_port_completion_payload_flags_UF << 1) |
                                  (top->VexRiscv->FpuPlugin_port_completion_payload_flags_OF << 2) |
                                  (top->VexRiscv->FpuPlugin_port_completion_payload_flags_DZ << 3) |
                                  (top->VexRiscv->FpuPlugin_port_completion_payload_flags_NV << 4);
                        riscvRef.fpuCompletion.push(c);
                    }
                }
                #endif



                if(top->VexRiscv->lastStageIsFiring){
                   	if(riscvRefEnable) {
//                        privilegeCounters[riscvRef.privilege]++;
//                        if((riscvRef.stepCounter & 0xFFFFF) == 0){
//                            cout << "privilege report" << endl;
//                            cout << "- U " << privilegeCounters[0] << endl;
//                            cout << "- S " << privilegeCounters[1] << endl;
//                            cout << "- M " << privilegeCounters[3] << endl;
//                        }
                        riscvRef.dutRfWriteValue = top->VexRiscv->lastStageRegFileWrite_payload_data;
                   	    riscvRef.step();
                   	    bool mIntTimer = false;
                   	    bool mIntExt = false;
                   	}

                   	if(riscvRefEnable && top->VexRiscv->lastStagePc != riscvRef.lastPc){
						cout << hex << " pc missmatch " << top->VexRiscv->lastStagePc << " should be " << riscvRef.lastPc << dec << endl;
						fail();
					}


                	bool rfWriteValid = false;
                	int32_t rfWriteAddress;
                	int32_t rfWriteData;

                    if(top->VexRiscv->lastStageRegFileWrite_valid == 1 && top->VexRiscv->lastStageRegFileWrite_payload_address != 0){
                    	rfWriteValid = true;
                    	rfWriteAddress = top->VexRiscv->lastStageRegFileWrite_payload_address;
                    	rfWriteData = top->VexRiscv->lastStageRegFileWrite_payload_data;
                    	#ifdef TRACE_ACCESS
                        regTraces <<
                            #ifdef TRACE_WITH_TIME
                            currentTime <<
                             #endif
                             " PC " << hex << setw(8) <<  top->VexRiscv->lastStagePc << " : reg[" << dec << setw(2) << (uint32_t)top->VexRiscv->lastStageRegFileWrite_payload_address << "] = " << hex << setw(8) << top->VexRiscv->lastStageRegFileWrite_payload_data <<  dec << endl;
                        #endif
                    } else {
                        #ifdef TRACE_ACCESS
                        regTraces <<
                                #ifdef TRACE_WITH_TIME
                                currentTime <<
                                 #endif
                                 " PC " << hex << setw(8) <<  top->VexRiscv->lastStagePc << dec << endl;
                        #endif
                    }
					if(riscvRefEnable) if(rfWriteValid != riscvRef.rfWriteValid ||
						(rfWriteValid && (rfWriteAddress!= riscvRef.rfWriteAddress || rfWriteData!= riscvRef.rfWriteData))){
                    	cout << "regFile write missmatch :" << endl;
                    	if(rfWriteValid) cout << " REF: RF[" << riscvRef.rfWriteAddress << "] = 0x" << hex << riscvRef.rfWriteData << dec << endl;
                    	if(rfWriteValid) cout << " DUT: RF[" << rfWriteAddress << "] = 0x" << hex << rfWriteData << dec << endl;
                    	fail();
                    }
                }

                #ifdef CSR
                    if(top->VexRiscv->CsrPlugin_hadException){
                        if(riscvRefEnable) {
                            riscvRef.step();
                        }
                    }
                #endif

				for(SimElement* simElement : simElements) simElement->preCycle();

				dump(i + 1);

				checks();
				//top->eval();
				top->clk = 1;
				top->eval();

				instanceCycles += 1;

				for(SimElement* simElement : simElements) simElement->postCycle();
				#ifdef RVF
				top->fpuCmdHalt = VL_RANDOM_I_WIDTH(1);
                top->fpuCommitHalt = VL_RANDOM_I_WIDTH(1);
                top->fpuRspHalt = VL_RANDOM_I_WIDTH(1);
                #endif



				if (Verilated::gotFinish())
					exit(0);
			}
			cout << "timeout" << endl;
			fail();
		} catch (const success e) {
			staticMutex.lock();
			cout <<"SUCCESS " << name <<  endl;
			successCounter++;
			cycles += instanceCycles;
			staticMutex.unlock();
		} catch (const std::exception& e) {
			staticMutex.lock();

			cout << "FAIL " <<  name << " at PC=" << hex << setw(8) << top->VexRiscv->lastStagePc << dec; //<<  " seed : " << seed <<
			if(riscvRefEnable) cout << hex << " REF PC=" << riscvRef.lastPc << " REF I=" << riscvRef.lastInstruction << dec;
			cout << " time=" << i;
			cout << endl;

			cycles += instanceCycles;
			staticMutex.unlock();
			failed = true;
		}



		dump(i+2);
		dump(i+10);
		#ifdef TRACE
		tfp->close();
		#endif
        #ifdef STOP_ON_ERROR
            if(failed){
                sleep(1);
                exit(-1);
            }
        #endif
		return this;
	}
};


class WorkspaceRegression : public Workspace {
public:

	WorkspaceRegression(string name) : Workspace(name){

	}

	virtual bool isPerifRegion(uint32_t addr) { return (addr & 0xF0000000) == 0xF0000000;}


	virtual void iBusAccess(uint32_t addr, uint32_t *data, bool *error){
		Workspace::iBusAccess(addr,data,error);
		*error = addr == 0xF00FFF60u;
	}

	virtual void dutPutChar(char c){}

	virtual void dBusAccess(uint32_t addr,bool wr, uint32_t size, uint8_t *dataBytes, bool *error) {
        uint32_t *data = ((uint32_t*)dataBytes);
		if(wr){
			switch(addr){
			case 0xF0010000u: {
				cout << (char)*data;
				logTraces << (char)*data;
				dutPutChar((char)*data);
				break;
			}
#ifdef EXTERNAL_INTERRUPT
			case 0xF0011000u: top->externalInterrupt = *data & 1; break;
#endif
#ifdef SUPERVISOR
			case 0xF0012000u: top->externalInterruptS = *data & 1; break;
#endif
#ifdef CSR
			case 0xF0013000u: top->softwareInterrupt = *data & 1; break;
#endif
			case 0xF00FFF00u: {
				cout << (char)*data;
				logTraces << (char)*data;
				dutPutChar((char)*data);
				break;
			}
			#ifndef DEBUG_PLUGIN_EXTERNAL
			case 0xF00FFF20u:
				if(*data == 0)
					pass();
				else
				    cout << "0xF00FFF20 test asked for failure " << *data << endl;
					fail();
				break;
			case 0xF00FFF24u:
					cout << "TEST ERROR CODE " << *data << endl;
					fail();
				break;
			#endif
			case 0xF00FFF48u: mTimeCmp = (mTimeCmp & 0xFFFFFFFF00000000) | *data;break;
			case 0xF00FFF4Cu: mTimeCmp = (mTimeCmp & 0x00000000FFFFFFFF) | (((uint64_t)*data) << 32); break;
			case 0xF00FFF50u: cout << "mTime " << *data << " : " << mTime << endl;
			}
			if((addr & 0xFFFFF000) == 0xF5670000){
			    uint32_t t = 0x900FF000 | (addr & 0xFFF);
			    uint32_t old = (*mem.get(t + 3) << 24) | (*mem.get(t + 2) << 16)  | (*mem.get(t + 1) << 8)  | (*mem.get(t + 0) << 0);
			    old++;
			    *mem.get(t + 0) = old & 0xFF; old >>= 8;
			    *mem.get(t + 1) = old & 0xFF; old >>= 8;
			    *mem.get(t + 2) = old & 0xFF; old >>= 8;
			    *mem.get(t + 3) = old & 0xFF; old >>= 8;
			}
		}else{
			switch(addr){
			case 0xF00FFF10u:
				*data = mTime;
				#ifdef REF_TIME
				mTime += 100000;
				#endif
				break;
			case 0xF00FFF40u: *data = mTime;		  break;
			case 0xF00FFF44u: *data = mTime >> 32;    break;
			case 0xF00FFF48u: *data = mTimeCmp;       break;
			case 0xF00FFF4Cu: *data = mTimeCmp >> 32; break;
			case 0xF0010004u: *data = ~0;		      break;
			}
		}

		*error = addr == 0xF00FFF60u;
		Workspace::dBusAccess(addr,wr,size,dataBytes,error);
	}



};



class ZephyrRegression : public WorkspaceRegression{
public:


	uint32_t regFileWriteRefIndex = 0;
	const char *target = "PROJECT EXECUTION SUCCESSFUL";
	const char *hit = target;

	ZephyrRegression(string name) : WorkspaceRegression(name) {
            cout << endl << endl;

	}

    virtual void dutPutChar(char c){
        if(*hit == c) hit++; else hit = target;
        if(*hit == 0) {
            cout  << endl << "T=" << i <<endl;
            cout << endl;
            pass();
        }
    }
};






#ifdef IBUS_SIMPLE
class IBusSimple : public SimElement{
public:
	uint32_t pendings[256];
	uint32_t rPtr = 0, wPtr = 0;

	Workspace *ws;
	VVexRiscv* top;
	IBusSimple(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->iBus_cmd_ready = 1;
		top->iBus_rsp_valid = 0;
	}

	virtual void preCycle(){
		if (top->iBus_cmd_valid && top->iBus_cmd_ready) {
			//assertEq(top->iBus_cmd_payload_pc & 3,0);
			pendings[wPtr] = (top->iBus_cmd_payload_pc);
			wPtr = (wPtr + 1) & 0xFF;
			//ws->iBusAccess(top->iBus_cmd_payload_pc,&inst_next,&error_next);
		}
	}
	//TODO doesn't catch when instruction removed ?
	virtual void postCycle(){
		top->iBus_rsp_valid = 0;
		if(rPtr != wPtr && (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100)){
	        uint32_t inst_next;
	        bool error_next;
		    ws->iBusAccess(pendings[rPtr], &inst_next,&error_next);
        	rPtr = (rPtr + 1) & 0xFF;
			top->iBus_rsp_payload_inst = inst_next;
			top->iBus_rsp_valid = 1;
			top->iBus_rsp_payload_error = error_next;
		} else {
		    top->iBus_rsp_payload_inst = VL_RANDOM_I_WIDTH(32);
		    top->iBus_rsp_payload_error = VL_RANDOM_I_WIDTH(1);
		}
		if(ws->iStall) top->iBus_cmd_ready = VL_RANDOM_I_WIDTH(7) < 100;
	}
};
#endif


#ifdef IBUS_TC

class IBusTc : public SimElement{
public:

    uint32_t nextData;

	Workspace *ws;
	VVexRiscv* top;
	IBusTc(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
	}

	virtual void preCycle(){
		if (top->iBusTc_enable) {
		    if((top->iBusTc_address & 0x70000000) != 0){
		        printf("IBusTc access out of range\n");
		        ws->fail();
		    }
	        bool error_next;
		    ws->iBusAccess(top->iBusTc_address, &nextData,&error_next);
		}
	}

	virtual void postCycle(){
		top->iBusTc_data = nextData;
	}
};

#endif


#ifdef IBUS_SIMPLE_AVALON

struct IBusSimpleAvalonRsp{
	uint32_t data;
	bool error;
};


class IBusSimpleAvalon : public SimElement{
public:
	queue<IBusSimpleAvalonRsp> rsps;

	Workspace *ws;
	VVexRiscv* top;
	IBusSimpleAvalon(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->iBusAvalon_waitRequestn = 1;
		top->iBusAvalon_readDataValid = 0;
	}

	virtual void preCycle(){
		if (top->iBusAvalon_read && top->iBusAvalon_waitRequestn) {
			IBusSimpleAvalonRsp rsp;
			ws->iBusAccess(top->iBusAvalon_address,&rsp.data,&rsp.error);
			rsps.push(rsp);
		}
	}
	//TODO doesn't catch when instruction removed ?
	virtual void postCycle(){
		if(!rsps.empty() && (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100)){
			IBusSimpleAvalonRsp rsp = rsps.front(); rsps.pop();
			top->iBusAvalon_readDataValid = 1;
			top->iBusAvalon_readData = rsp.data;
			top->iBusAvalon_response = rsp.error ? 3 : 0;
		} else {
			top->iBusAvalon_readDataValid = 0;
			top->iBusAvalon_readData = VL_RANDOM_I_WIDTH(32);
			top->iBusAvalon_response = VL_RANDOM_I_WIDTH(2);
		}
		if(ws->iStall)
			top->iBusAvalon_waitRequestn = VL_RANDOM_I_WIDTH(7) < 100;
	}
};
#endif



#ifdef IBUS_SIMPLE_AHBLITE3
class IBusSimpleAhbLite3 : public SimElement{
public:
	Workspace *ws;
	VVexRiscv* top;

	uint32_t iBusAhbLite3_HRDATA;
	bool iBusAhbLite3_HRESP;
	bool pending;

	IBusSimpleAhbLite3(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
	    pending = false;
		top->iBusAhbLite3_HREADY = 1;
		top->iBusAhbLite3_HRESP = 0;
	}

	virtual void preCycle(){
        if (top->iBusAhbLite3_HTRANS == 2 && top->iBusAhbLite3_HREADY && !top->iBusAhbLite3_HWRITE) {
            ws->iBusAccess(top->iBusAhbLite3_HADDR,&iBusAhbLite3_HRDATA,&iBusAhbLite3_HRESP);
            pending = true;
        }
	}

	virtual void postCycle(){
		if(ws->iStall)
			top->iBusAhbLite3_HREADY = (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100);

		if(pending && top->iBusAhbLite3_HREADY){
			top->iBusAhbLite3_HRDATA = iBusAhbLite3_HRDATA;
			top->iBusAhbLite3_HRESP  = iBusAhbLite3_HRESP;
			pending = false;
		} else {
			top->iBusAhbLite3_HRDATA = VL_RANDOM_I_WIDTH(32);
			top->iBusAhbLite3_HRESP = VL_RANDOM_I_WIDTH(1);
		}
	}
};
#endif


#ifdef IBUS_CACHED
class IBusCached : public SimElement{
public:
	bool error_next = false;
	uint32_t pendingCount = 0;
	uint32_t address;

	Workspace *ws;
	VVexRiscv* top;
	IBusCached(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}


	virtual void onReset(){
		top->iBus_cmd_ready = 1;
		top->iBus_rsp_valid = 0;
	}

	virtual void preCycle(){
		if (top->iBus_cmd_valid && top->iBus_cmd_ready && pendingCount == 0) {
			assertEq((top->iBus_cmd_payload_address & 3),0);
			pendingCount = (1 << top->iBus_cmd_payload_size)/4;
			address = top->iBus_cmd_payload_address;
		}
	}

	virtual void postCycle(){
		bool error;
		top->iBus_rsp_valid = 0;
		if(pendingCount != 0 && (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100)){
		    #ifdef IBUS_TC
            if((address & 0x70000000) == 0){
                printf("IBUS_CACHED access out of range\n");
                ws->fail();
            }
            #endif
            error = false;
            for(int idx = 0;idx < IBUS_DATA_WIDTH/32;idx++){
                bool localError = false;
			    ws->iBusAccess(address+idx*4,((uint32_t*)&top->iBus_rsp_payload_data)+idx,&localError);
			    error |= localError;
            }
			top->iBus_rsp_payload_error = error;
			pendingCount-=IBUS_DATA_WIDTH/32;
			address = address + IBUS_DATA_WIDTH/8;
			top->iBus_rsp_valid = 1;
		}
		if(ws->iStall) top->iBus_cmd_ready = VL_RANDOM_I_WIDTH(7) < 100 && pendingCount == 0;
	}
};
#endif

#ifdef IBUS_CACHED_AVALON
#include <queue>

struct IBusCachedAvalonTask{
	uint32_t address;
	uint32_t pendingCount;
};

class IBusCachedAvalon : public SimElement{
public:
	uint32_t inst_next = VL_RANDOM_I_WIDTH(32);
	bool error_next = false;

	queue<IBusCachedAvalonTask> tasks;
	Workspace *ws;
	VVexRiscv* top;

	IBusCachedAvalon(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->iBusAvalon_waitRequestn = 1;
		top->iBusAvalon_readDataValid = 0;
	}

	virtual void preCycle(){
		if (top->iBusAvalon_read && top->iBusAvalon_waitRequestn) {
			assertEq(top->iBusAvalon_address & 3,0);
			IBusCachedAvalonTask task;
			task.address = top->iBusAvalon_address;
			task.pendingCount = top->iBusAvalon_burstCount;
			tasks.push(task);
		}
	}

	virtual void postCycle(){
		bool error;
		top->iBusAvalon_readDataValid = 0;
		if(!tasks.empty() && (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100)){
			uint32_t &address = tasks.front().address;
			uint32_t &pendingCount = tasks.front().pendingCount;
			bool error;
			ws->iBusAccess(address,&top->iBusAvalon_readData,&error);
			top->iBusAvalon_response = error ? 3 : 0;
			pendingCount--;
			address = (address & ~0x1F) + ((address + 4) & 0x1F);
			top->iBusAvalon_readDataValid = 1;
			if(pendingCount == 0)
				tasks.pop();
		}
		if(ws->iStall)
			top->iBusAvalon_waitRequestn = VL_RANDOM_I_WIDTH(7) < 100;
	}
};
#endif


#if defined(IBUS_CACHED_WISHBONE) || defined(IBUS_SIMPLE_WISHBONE)
#include <queue>

class IBusCachedWishbone : public SimElement{
public:

	Workspace *ws;
	VVexRiscv* top;

	IBusCachedWishbone(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->iBusWishbone_ACK = !ws->iStall;
		top->iBusWishbone_ERR = 0;
	}

	virtual void preCycle(){

	}

	virtual void postCycle(){

		if(ws->iStall)
			top->iBusWishbone_ACK = VL_RANDOM_I_WIDTH(7) < 100;

        top->iBusWishbone_DAT_MISO = VL_RANDOM_I_WIDTH(32);
        if (top->iBusWishbone_CYC && top->iBusWishbone_STB && top->iBusWishbone_ACK) {
            if(top->iBusWishbone_WE){

            } else {
                bool error;
                ws->iBusAccess(top->iBusWishbone_ADR << 2,&top->iBusWishbone_DAT_MISO,&error);
                top->iBusWishbone_ERR = error;
            }
        }
	}
};
#endif


#ifdef DBUS_SIMPLE
class DBusSimple : public SimElement{
public:
	uint32_t data_next = VL_RANDOM_I_WIDTH(32);
	bool error_next = false;
	bool pending = false;

	Workspace *ws;
	VVexRiscv* top;
	DBusSimple(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->dBus_cmd_ready = 1;
		top->dBus_rsp_ready = 1;
	}

	virtual void preCycle(){
		if (top->dBus_cmd_valid && top->dBus_cmd_ready) {
			pending = true;
			data_next = top->dBus_cmd_payload_data;
			ws->dBusAccess(top->dBus_cmd_payload_address,top->dBus_cmd_payload_wr,1 << top->dBus_cmd_payload_size,((uint8_t*)&data_next) + (top->dBus_cmd_payload_address & 3),&error_next);
		}
	}

	virtual void postCycle(){
		top->dBus_rsp_ready = 0;
		if(pending && (!ws->dStall || VL_RANDOM_I_WIDTH(7) < 100)){
			pending = false;
			top->dBus_rsp_ready = 1;
			top->dBus_rsp_data = data_next;
			top->dBus_rsp_error = error_next;
		} else{
			top->dBus_rsp_data = VL_RANDOM_I_WIDTH(32);
		}

		if(ws->dStall) top->dBus_cmd_ready = VL_RANDOM_I_WIDTH(7) < 100 && !pending;
	}
};
#endif

#ifdef DBUS_SIMPLE_AVALON
#include <queue>
struct DBusSimpleAvalonRsp{
	uint32_t data;
	bool error;
};


class DBusSimpleAvalon : public SimElement{
public:
	queue<DBusSimpleAvalonRsp> rsps;

	Workspace *ws;
	VVexRiscv* top;
	DBusSimpleAvalon(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->dBusAvalon_waitRequestn = 1;
		top->dBusAvalon_readDataValid = 0;
	}

	virtual void preCycle(){
		if (top->dBusAvalon_write && top->dBusAvalon_waitRequestn) {
			bool dummy;
			ws->dBusAccess(top->dBusAvalon_address,1,2,top->dBusAvalon_byteEnable,&top->dBusAvalon_writeData,&dummy);
		}
		if (top->dBusAvalon_read && top->dBusAvalon_waitRequestn) {
			DBusSimpleAvalonRsp rsp;
			ws->dBusAccess(top->dBusAvalon_address,0,2,0xF,&rsp.data,&rsp.error);
			rsps.push(rsp);
		}
	}
	//TODO doesn't catch when instruction removed ?
	virtual void postCycle(){
		if(!rsps.empty() && (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100)){
			DBusSimpleAvalonRsp rsp = rsps.front(); rsps.pop();
			top->dBusAvalon_readDataValid = 1;
			top->dBusAvalon_readData = rsp.data;
			top->dBusAvalon_response = rsp.error ? 3 : 0;
		} else {
			top->dBusAvalon_readDataValid = 0;
			top->dBusAvalon_readData = VL_RANDOM_I_WIDTH(32);
			top->dBusAvalon_response = VL_RANDOM_I_WIDTH(2);
		}
		if(ws->iStall)
			top->dBusAvalon_waitRequestn = VL_RANDOM_I_WIDTH(7) < 100;
	}
};
#endif



#ifdef DBUS_SIMPLE_AHBLITE3
class DBusSimpleAhbLite3 : public SimElement{
public:
	Workspace *ws;
	VVexRiscv* top;

    uint32_t dBusAhbLite3_HADDR, dBusAhbLite3_HSIZE, dBusAhbLite3_HTRANS, dBusAhbLite3_HWRITE;

	DBusSimpleAhbLite3(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->dBusAhbLite3_HREADY = 1;
		top->dBusAhbLite3_HRESP = 0;
		dBusAhbLite3_HTRANS = 0;
	}

	virtual void preCycle(){
        if(top->dBusAhbLite3_HREADY && dBusAhbLite3_HTRANS == 2 && dBusAhbLite3_HWRITE){
            uint32_t data = top->dBusAhbLite3_HWDATA;
            bool error;
            ws->dBusAccess(dBusAhbLite3_HADDR, 1, (1<<dBusAhbLite3_HSIZE),((uint8_t*)&data) + (dBusAhbLite3_HADDR&0x3),&error);
        }

        if(top->dBusAhbLite3_HREADY){
	        dBusAhbLite3_HADDR = top->dBusAhbLite3_HADDR ;
	        dBusAhbLite3_HSIZE = top->dBusAhbLite3_HSIZE ;
	        dBusAhbLite3_HTRANS = top->dBusAhbLite3_HTRANS ;
	        dBusAhbLite3_HWRITE = top->dBusAhbLite3_HWRITE ;
        }
	}

	virtual void postCycle(){
		if(ws->iStall)
                    top->dBusAhbLite3_HREADY = (!ws->iStall || VL_RANDOM_I_WIDTH(7) < 100);

                top->dBusAhbLite3_HRDATA = VL_RANDOM_I_WIDTH(32);
                top->dBusAhbLite3_HRESP = VL_RANDOM_I_WIDTH(1);

		if(top->dBusAhbLite3_HREADY && dBusAhbLite3_HTRANS == 2 && !dBusAhbLite3_HWRITE){
		    bool error;
		    ws->dBusAccess(dBusAhbLite3_HADDR, 0, (1<<dBusAhbLite3_HSIZE),((uint8_t*)&top->dBusAhbLite3_HRDATA) + (dBusAhbLite3_HADDR&0x3),&error);
		    top->dBusAhbLite3_HRESP  = error;
		}
	}
};
#endif


#if defined(DBUS_CACHED_WISHBONE) || defined(DBUS_SIMPLE_WISHBONE)
#include <queue>


class DBusCachedWishbone : public SimElement{
public:

	Workspace *ws;
	VVexRiscv* top;

	DBusCachedWishbone(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->dBusWishbone_ACK = !ws->iStall;
		top->dBusWishbone_ERR = 0;
	}

	virtual void preCycle(){

	}

	virtual void postCycle(){
		if(ws->iStall)
			top->dBusWishbone_ACK = VL_RANDOM_I_WIDTH(7) < 100;
        top->dBusWishbone_DAT_MISO = VL_RANDOM_I_WIDTH(32);
        if (top->dBusWishbone_CYC && top->dBusWishbone_STB && top->dBusWishbone_ACK) {
            if(top->dBusWishbone_WE){
                bool dummy;
                ws->dBusAccess(top->dBusWishbone_ADR << 2 ,1,2,top->dBusWishbone_SEL,&top->dBusWishbone_DAT_MOSI,&dummy);
            } else {
                bool error;
                ws->dBusAccess(top->dBusWishbone_ADR << 2,0,2,0xF,&top->dBusWishbone_DAT_MISO,&error);
                top->dBusWishbone_ERR = error;
            }
        }
	}
};
#endif

#ifdef DBUS_CACHED

//#include "VVexRiscv_DataCache.h"
#include <queue>

struct DBusCachedTask{
	char data[DBUS_LOAD_DATA_WIDTH/8];
	bool error;
	bool last;
	bool exclusive;
};

class DBusCached : public SimElement{
public:
	queue<DBusCachedTask> rsps;
	queue<uint32_t> invalidationHint;

	bool reservationValid = false;
	uint32_t reservationAddress;
	uint32_t pendingSync = 0;

	Workspace *ws;
	VVexRiscv* top;
    DBusCachedTask rsp;

	DBusCached(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->dBus_cmd_ready = 1;
		top->dBus_rsp_valid = 0;
		#ifdef DBUS_AGGREGATION
		top->dBus_rsp_payload_aggregated = 0;
		#endif
		#ifdef DBUS_INVALIDATE
		top->dBus_inv_valid = 0;
		top->dBus_ack_ready = 0;
		top->dBus_sync_valid = 0;
		#ifdef DBUS_AGGREGATION
		top->dBus_sync_payload_aggregated = 0;
		#endif
		#endif
	}

	virtual void preCycle(){
		if (top->dBus_cmd_valid && top->dBus_cmd_ready) {
            if(top->dBus_cmd_payload_wr){
                int size = 1 << top->dBus_cmd_payload_size;
                #ifdef DBUS_INVALIDATE
                    pendingSync += 1;
                #endif
                #ifndef DBUS_EXCLUSIVE
                    bool error;
                    int shift = top->dBus_cmd_payload_address & (DBUS_STORE_DATA_WIDTH/8-1);
                    ws->dBusAccess(top->dBus_cmd_payload_address,1,size,((uint8_t*)&top->dBus_cmd_payload_data) + shift,&error);
                #else
                    bool cancel = false, error = false;
                    if(top->dBus_cmd_payload_exclusive){
                        bool hit = reservationValid && reservationAddress == top->dBus_cmd_payload_address;
                        rsp.exclusive = hit;
                        cancel = !hit;
                    }
                    if(!cancel) {
                        for(int idx = 0;idx < 1;idx++){
                            bool localError = false;
                            int shift = top->dBus_cmd_payload_address & (DBUS_STORE_DATA_WIDTH/8-1);
                            ws->dBusAccess(top->dBus_cmd_payload_address,1,size,((uint8_t*)&top->dBus_cmd_payload_data) + shift,&localError);
                            error |= localError;
                        }
                    }

                    reservationValid = false;
                    rsp.last = true;
                    rsp.error = error;
                    rsps.push(rsp);
                #endif
            } else {
                bool error = false;
                uint32_t beatCount = (((1 << top->dBus_cmd_payload_size)*8+DBUS_LOAD_DATA_WIDTH-1) / DBUS_LOAD_DATA_WIDTH)-1;
                uint32_t startAt = top->dBus_cmd_payload_address;
                uint32_t endAt = top->dBus_cmd_payload_address + (1 << top->dBus_cmd_payload_size);
                uint32_t address = top->dBus_cmd_payload_address & ~(DBUS_LOAD_DATA_WIDTH/8-1);
                uint8_t buffer[64];
                ws->dBusAccess(top->dBus_cmd_payload_address,0,1 << top->dBus_cmd_payload_size,buffer, &error);
                for(int beat = 0;beat <= beatCount;beat++){
                    for(int i = 0;i < DBUS_LOAD_DATA_WIDTH/8;i++){
                        rsp.data[i] = (address >= startAt && address < endAt) ? buffer[address-top->dBus_cmd_payload_address] : VL_RANDOM_I_WIDTH(8);
                        address += 1;
                    }
                    rsp.last = beat == beatCount;
                    #ifdef DBUS_EXCLUSIVE
                        if(top->dBus_cmd_payload_exclusive){
                            rsp.exclusive = true;
                            reservationValid = true;
                            reservationAddress = top->dBus_cmd_payload_address;
                        }
                    #endif
                    rsp.error = error;
                    rsps.push(rsp);
                }

                #ifdef DBUS_INVALIDATE
                    if(ws->allowInvalidate){
                        if(VL_RANDOM_I_WIDTH(7) < 10){
                            invalidationHint.push(top->dBus_cmd_payload_address + VL_RANDOM_I_WIDTH(5));
                        }
                    }
                #endif
            }
		}
		#ifdef DBUS_INVALIDATE
            if(top->dBus_sync_valid && top->dBus_sync_ready){
                pendingSync -= 1;
            }
        #endif
	}

	virtual void postCycle(){

		if(!rsps.empty() && (!ws->dStall || VL_RANDOM_I_WIDTH(7) < 100)){
			DBusCachedTask rsp = rsps.front();
			rsps.pop();
			top->dBus_rsp_valid = 1;
			top->dBus_rsp_payload_error = rsp.error;
            for(int idx = 0;idx < DBUS_LOAD_DATA_WIDTH/32;idx++){
                ((uint32_t*)&top->dBus_rsp_payload_data)[idx] = ((uint32_t*)rsp.data)[idx];
            }
			top->dBus_rsp_payload_last = rsp.last;
            #ifdef DBUS_EXCLUSIVE
            top->dBus_rsp_payload_exclusive = rsp.exclusive;
            #endif
		} else{
			top->dBus_rsp_valid = 0;
            for(int idx = 0;idx < DBUS_LOAD_DATA_WIDTH/32;idx++){
			    ((uint32_t*)&top->dBus_rsp_payload_data)[idx] = VL_RANDOM_I_WIDTH(32);
			}
			top->dBus_rsp_payload_error = VL_RANDOM_I_WIDTH(1);
			top->dBus_rsp_payload_last = VL_RANDOM_I_WIDTH(1);
            #ifdef DBUS_EXCLUSIVE
            top->dBus_rsp_payload_exclusive = VL_RANDOM_I_WIDTH(1);
            #endif
		}
		top->dBus_cmd_ready = (ws->dStall ? VL_RANDOM_I_WIDTH(7) < 100 : 1);

        #ifdef DBUS_INVALIDATE
            if(ws->allowInvalidate){
                if(top->dBus_inv_ready) top->dBus_inv_valid = 0;
                if(top->dBus_inv_valid == 0 && VL_RANDOM_I_WIDTH(7) < 5){
                    top->dBus_inv_valid = 1;
                    top->dBus_inv_payload_fragment_enable = VL_RANDOM_I_WIDTH(7) < 100;
                    if(!invalidationHint.empty()){
                        top->dBus_inv_payload_fragment_address = invalidationHint.front();
                        invalidationHint.pop();
                    } else {
                        top->dBus_inv_payload_fragment_address = VL_RANDOM_I_WIDTH(32);
                    }
                }
            }
		    top->dBus_ack_ready = (ws->dStall ? VL_RANDOM_I_WIDTH(7) < 100 : 1);
		    if(top->dBus_sync_ready) top->dBus_sync_valid = 0;
		    if(top->dBus_sync_valid == 0 && pendingSync != 0 && (ws->dStall ? VL_RANDOM_I_WIDTH(7) < 80 : 1) ){
		        top->dBus_sync_valid = 1;
            }
        #endif

	}
};
#endif

#ifdef DBUS_CACHED_AVALON
#include <queue>

struct DBusCachedAvalonTask{
	uint32_t data;
	bool error;
};

class DBusCachedAvalon : public SimElement{
public:
	uint32_t beatCounter = 0;
	queue<DBusCachedAvalonTask> rsps;

	Workspace *ws;
	VVexRiscv* top;
	DBusCachedAvalon(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
		top->dBusAvalon_waitRequestn = 1;
		top->dBusAvalon_readDataValid = 0;
	}


	virtual void preCycle(){
		if ((top->dBusAvalon_read || top->dBusAvalon_write) && top->dBusAvalon_waitRequestn) {
			if(top->dBusAvalon_write){
                uint32_t size = __builtin_popcount(top->dBusAvalon_byteEnable);
                uint32_t offset = ffs(top->dBusAvalon_byteEnable)-1;
				bool error_next = false;
				ws->dBusAccess(top->dBusAvalon_address + beatCounter * 4 + offset,1,size,((uint8_t*)&top->dBusAvalon_writeData)+offset,&error_next);
				beatCounter++;
				if(beatCounter == top->dBusAvalon_burstCount){
					beatCounter = 0;
				}
			} else {
				for(int beat = 0;beat < top->dBusAvalon_burstCount;beat++){
					DBusCachedAvalonTask rsp;
					ws->dBusAccess(top->dBusAvalon_address  + beat * 4 ,0,4,((uint8_t*)&rsp.data),&rsp.error);
					rsps.push(rsp);
				}
			}
		}
	}

	virtual void postCycle(){
		if(!rsps.empty() && (!ws->dStall || VL_RANDOM_I_WIDTH(7) < 100)){
			DBusCachedAvalonTask rsp = rsps.front();
			rsps.pop();
			top->dBusAvalon_response = rsp.error ? 3 : 0;
			top->dBusAvalon_readData = rsp.data;
			top->dBusAvalon_readDataValid = 1;
		} else{
			top->dBusAvalon_readDataValid = 0;
			top->dBusAvalon_readData = VL_RANDOM_I_WIDTH(32);
			top->dBusAvalon_response = VL_RANDOM_I_WIDTH(2); //TODO
		}

		top->dBusAvalon_waitRequestn = (ws->dStall ? VL_RANDOM_I_WIDTH(7) < 100 : 1);
	}
};
#endif


#ifdef DEBUG_PLUGIN
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <netinet/tcp.h>

/** Returns true on success, or false if there was an error */
bool SetSocketBlockingEnabled(int fd, bool blocking)
{
   if (fd < 0) return false;

#ifdef WIN32
   unsigned long mode = blocking ? 0 : 1;
   return (ioctlsocket(fd, FIONBIO, &mode) == 0) ? true : false;
#else
   int flags = fcntl(fd, F_GETFL, 0);
   if (flags < 0) return false;
   flags = blocking ? (flags&~O_NONBLOCK) : (flags|O_NONBLOCK);
   return (fcntl(fd, F_SETFL, flags) == 0) ? true : false;
#endif
}

struct DebugPluginTask{
	bool wr;
	uint32_t address;
	uint32_t data;
};

class DebugPlugin : public SimElement{
public:
	Workspace *ws;
	VVexRiscv* top;

	int serverSocket, clientHandle;
	struct sockaddr_in serverAddr;
	struct sockaddr_storage serverStorage;
	socklen_t addr_size;
	char buffer[1024];
	uint32_t timeSpacer = 0;
	bool taskValid = false;
	DebugPluginTask task;


	DebugPlugin(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;

		#ifdef DEBUG_PLUGIN_EXTERNAL
			ws->mTimeCmp = ~0;
		#endif
		top->debugReset = 0;



		//---- Create the socket. The three arguments are: ----//
		// 1) Internet domain 2) Stream socket 3) Default protocol (TCP in this case) //
		serverSocket = socket(PF_INET, SOCK_STREAM, 0);
		assert(serverSocket != -1);
		SetSocketBlockingEnabled(serverSocket,0);
		int flag = 1;
		int result = setsockopt(serverSocket,            /* socket affected */
								 IPPROTO_TCP,     /* set option at TCP level */
								 TCP_NODELAY,     /* name of option */
								 (char *) &flag,  /* the cast is historical
														 cruft */
								 sizeof(int));    /* length of option value */

		//---- Configure settings of the server address struct ----//
		// Address family = Internet //
		serverAddr.sin_family = AF_INET;
		// Set port number, using htons function to use proper byte order //
		serverAddr.sin_port = htons(7893);
		// Set IP address to localhost //
		serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
		// Set all bits of the padding field to 0 //
		memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);

		//---- Bind the address struct to the socket ----//
		bind(serverSocket, (struct sockaddr *) &serverAddr, sizeof(serverAddr));

		//---- Listen on the socket, with 5 max connection requests queued ----//
		listen(serverSocket,1);

		//---- Accept call creates a new socket for the incoming connection ----//
		addr_size = sizeof serverStorage;

		clientHandle = -1;
	}

	virtual ~DebugPlugin(){
		if(clientHandle != -1) {
			shutdown(clientHandle,SHUT_RDWR);
			usleep(100);
		}
		if(serverSocket != -1) {
			close(serverSocket);
			usleep(100);
		}
	}

	virtual void onReset(){
		top->debugReset = 1;
	}



	virtual void postReset(){
		top->debugReset = 0;
	}

	void connectionReset(){
		printf("CONNECTION RESET\n");
		shutdown(clientHandle,SHUT_RDWR);
		clientHandle = -1;
	}


	virtual void preCycle(){

	}

	virtual void postCycle(){
		top->reset = top->debug_resetOut;
		if(timeSpacer == 0){
			if(clientHandle == -1){
				clientHandle = accept(serverSocket, (struct sockaddr *) &serverStorage, &addr_size);
				if(clientHandle != -1)
					printf("CONNECTED\n");
				timeSpacer = 1000;
			}


			if(clientHandle != -1 && taskValid == false){
				int requiredSize = 1 + 1 + 4 + 4;
				int n;
				timeSpacer = 20;
				if(ioctl(clientHandle,FIONREAD,&n) != 0){
					connectionReset();
				} else if(n >= requiredSize){
					if(requiredSize != read(clientHandle,buffer,requiredSize)){
						connectionReset();
					} else {
						bool wr = buffer[0];
						uint32_t size = buffer[1];
						uint32_t address = *((uint32_t*)(buffer + 2));
						uint32_t data = *((uint32_t*)(buffer + 6));

						if((address & ~ 0x4) == 0xF00F0000){
							assert(size == 2);
							timeSpacer = 100;

							taskValid = true;
							task.wr = wr;
							task.address = address;
							task.data = data;
						}
					}
				} else {
					int error = 0;
					socklen_t len = sizeof (error);
					int retval = getsockopt (clientHandle, SOL_SOCKET, SO_ERROR, &error, &len);
					if (retval != 0 || error != 0) {
						connectionReset();
					}
				}
			}
		} else {
			timeSpacer--;
		}
	}

	void sendRsp(uint32_t data){
		if(clientHandle != -1){
			if(send(clientHandle,&data,4,0) == -1) connectionReset();
		}
	}
};
#endif

#ifdef DEBUG_PLUGIN_STD
class DebugPluginStd : public DebugPlugin{
public:
	DebugPluginStd(Workspace* ws) : DebugPlugin(ws){

	}

	virtual void onReset(){
		DebugPlugin::onReset();
		top->debug_bus_cmd_valid = 0;
	}

	bool rspFire = false;

	virtual void preCycle(){
		DebugPlugin::preCycle();

		if(rspFire){
			sendRsp(top->debug_bus_rsp_data);
			rspFire = false;
		}

		if(top->debug_bus_cmd_valid && top->debug_bus_cmd_ready){
			taskValid = false;
			if(!top->debug_bus_cmd_payload_wr){
				rspFire = true;
			}
		}
	}

	virtual void postCycle(){
		DebugPlugin::postCycle();

		if(taskValid){
			top->debug_bus_cmd_valid = 1;
			top->debug_bus_cmd_payload_wr = task.wr;
			top->debug_bus_cmd_payload_address = task.address;
			top->debug_bus_cmd_payload_data = task.data;
		}else {
			top->debug_bus_cmd_valid = 0;
			top->debug_bus_cmd_payload_wr = VL_RANDOM_I_WIDTH(1);
			top->debug_bus_cmd_payload_address = VL_RANDOM_I_WIDTH(8);
			top->debug_bus_cmd_payload_data = VL_RANDOM_I_WIDTH(32);
		}
	}
};

#endif

#ifdef DEBUG_PLUGIN_AVALON
class DebugPluginAvalon : public DebugPlugin{
public:
	DebugPluginAvalon(Workspace* ws) : DebugPlugin(ws){

	}

	virtual void onReset(){
		DebugPlugin::onReset();
		top->debugBusAvalon_read = 0;
		top->debugBusAvalon_write = 0;
	}

	bool rspFire = false;

	virtual void preCycle(){
		DebugPlugin::preCycle();

		if(rspFire){
			sendRsp(top->debugBusAvalon_readData);
			rspFire = false;
		}

		if((top->debugBusAvalon_read || top->debugBusAvalon_write) && top->debugBusAvalon_waitRequestn){
			taskValid = false;
			if(top->debugBusAvalon_read){
				rspFire = true;
			}
		}
	}

	virtual void postCycle(){
		DebugPlugin::postCycle();

		if(taskValid){
			top->debugBusAvalon_write = task.wr;
			top->debugBusAvalon_read = !task.wr;
			top->debugBusAvalon_address = task.address;
			top->debugBusAvalon_writeData = task.data;
		}else {
			top->debugBusAvalon_write = 0;
			top->debugBusAvalon_read = 0;
			top->debugBusAvalon_address = VL_RANDOM_I_WIDTH(8);
			top->debugBusAvalon_writeData = VL_RANDOM_I_WIDTH(32);
		}
	}
};

#endif

#include "jtag.h"

#ifdef VEXRISCV_JTAG
class VexRiscvJtag : public SimElement{
public:
	Workspace *ws;
	VVexRiscv* top;

	VexRiscvJtag(Workspace* ws){
		this->ws = ws;
		this->top = ws->top;
	}

	virtual void onReset(){
	    top->debugReset = 1;
	}

	virtual void preCycle(){

	}

	virtual void postCycle(){
        top->debugReset = 0;
	}
};
#endif

void Workspace::fillSimELements(){
	#ifdef IBUS_SIMPLE
		simElements.push_back(new IBusSimple(this));
	#endif
	#ifdef IBUS_SIMPLE_AVALON
		simElements.push_back(new IBusSimpleAvalon(this));
	#endif
    #ifdef IBUS_SIMPLE_AHBLITE3
        simElements.push_back(new IBusSimpleAhbLite3(this));
    #endif


	#ifdef IBUS_CACHED
		simElements.push_back(new IBusCached(this));
	#endif
	#ifdef IBUS_CACHED_AVALON
		simElements.push_back(new IBusCachedAvalon(this));
	#endif
	#if defined(IBUS_CACHED_WISHBONE) || defined(IBUS_SIMPLE_WISHBONE)
		simElements.push_back(new IBusCachedWishbone(this));
	#endif

	#ifdef IBUS_TC
		simElements.push_back(new IBusTc(this));
	#endif

	#ifdef DBUS_SIMPLE
		simElements.push_back(new DBusSimple(this));
	#endif
	#ifdef DBUS_SIMPLE_AVALON
		simElements.push_back(new DBusSimpleAvalon(this));
	#endif
    #ifdef DBUS_SIMPLE_AHBLITE3
        simElements.push_back(new DBusSimpleAhbLite3(this));
    #endif
	#ifdef DBUS_CACHED
		simElements.push_back(new DBusCached(this));
	#endif
	#ifdef DBUS_CACHED_AVALON
		simElements.push_back(new DBusCachedAvalon(this));
	#endif
	#if defined(DBUS_CACHED_WISHBONE) || defined(DBUS_SIMPLE_WISHBONE)
		simElements.push_back(new DBusCachedWishbone(this));
	#endif
	#ifdef DEBUG_PLUGIN_STD
		simElements.push_back(new DebugPluginStd(this));
	#endif
	#ifdef DEBUG_PLUGIN_AVALON
		simElements.push_back(new DebugPluginAvalon(this));
	#endif
	#ifdef RISCV_JTAG
		simElements.push_back(new Jtag(&top->jtag_tms, &top->jtag_tdi, &top->jtag_tdo, &top->jtag_tck, 4));
        simElements.push_back(new VexRiscvJtag(this));
	#endif
    #ifdef VEXRISCV_JTAG
        simElements.push_back(new Jtag(&top->jtag_tms, &top->jtag_tdi, &top->jtag_tdo, &top->jtag_tck, 4));
        simElements.push_back(new VexRiscvJtag(this));
    #endif
}

mutex Workspace::staticMutex;
uint64_t Workspace::cycles = 0;
uint32_t Workspace::testsCounter = 0, Workspace::successCounter = 0;

#ifndef REF
#define testA1ReagFileWriteRef {1,10},{2,20},{3,40},{4,60}
#define testA2ReagFileWriteRef {5,1},{7,3}
uint32_t regFileWriteRefArray[][2] = {
	testA1ReagFileWriteRef,
	testA1ReagFileWriteRef,
	testA2ReagFileWriteRef,
	testA2ReagFileWriteRef
};

class TestA : public WorkspaceRegression{
public:


	uint32_t regFileWriteRefIndex = 0;

	TestA() : WorkspaceRegression("testA") {
		loadHex(string(REGRESSION_PATH) + "../../resources/hex/testA.hex");
	}

	virtual void checks(){
		if(top->VexRiscv->lastStageRegFileWrite_valid == 1 && top->VexRiscv->lastStageRegFileWrite_payload_address != 0){
			assertEq(top->VexRiscv->lastStageRegFileWrite_payload_address, regFileWriteRefArray[regFileWriteRefIndex][0]);
			assertEq(top->VexRiscv->lastStageRegFileWrite_payload_data, regFileWriteRefArray[regFileWriteRefIndex][1]);
			//printf("%d\n",i);

			regFileWriteRefIndex++;
			if(regFileWriteRefIndex == sizeof(regFileWriteRefArray)/sizeof(regFileWriteRefArray[0])){
				pass();
			}
		}
	}
};

class TestX28 : public WorkspaceRegression{
public:
	uint32_t refIndex = 0;
	uint32_t *ref;
	uint32_t refSize;

	TestX28(string name, uint32_t *ref, uint32_t refSize) : WorkspaceRegression(name) {
		this->ref = ref;
		this->refSize = refSize;
		loadHex(string(REGRESSION_PATH) + "../../resources/hex/" + name + ".hex");
	}

	virtual void checks(){
		if(top->VexRiscv->lastStageRegFileWrite_valid == 1 && top->VexRiscv->lastStageRegFileWrite_payload_address == 28){
			assertEq(top->VexRiscv->lastStageRegFileWrite_payload_data, ref[refIndex]);
			//printf("%d\n",i);

			refIndex++;
			if(refIndex == refSize){
				pass();
			}
		}
	}
};


class RiscvTest : public WorkspaceRegression{
public:
	RiscvTest(string name) : WorkspaceRegression(name) {
		loadHex(string(REGRESSION_PATH) + "../../resources/hex/" + name + ".hex");
		bootAt(0x800000bcu);
	}

	virtual void postReset() {
//		#ifdef CSR
//		top->VexRiscv->prefetch_PcManagerSimplePlugin_pcReg = 0x80000000u;
//		#else
//		#endif
	}

	virtual void checks(){
		if(top->VexRiscv->lastStageIsFiring && top->VexRiscv->lastStageInstruction == 0x00000013){
			uint32_t instruction;
			bool error;
			Workspace::mem.read(top->VexRiscv->lastStagePc, 4, (uint8_t*)&instruction);
			//printf("%x => %x\n", top->VexRiscv->lastStagePc, instruction );
			if(instruction == 0x00000073){
				uint32_t code = top->VexRiscv->RegFilePlugin_regFile[28];
				uint32_t code2 = top->VexRiscv->RegFilePlugin_regFile[3];
				if((code & 1) == 0 && (code2 & 1) == 0){
					cout << "Wrong error code"<< endl;
					fail();
				}
				if(code == 1 || code2 == 1){
					pass();
				}else{
					cout << "Error code " << code2/2 << endl;
					fail();
				}
			}
		}
	}

	virtual void iBusAccess(uint32_t addr, uint32_t *data, bool *error){
		WorkspaceRegression::iBusAccess(addr,data,error);
		if(*data == 0x0ff0000f) *data = 0x00000013;
		if(*data == 0x00000073) *data = 0x00000013;
	}
};
#endif
class Dhrystone : public WorkspaceRegression{
public:
	string hexName;
	Dhrystone(string name,string hexName,bool iStall, bool dStall) : WorkspaceRegression(name) {
		setIStall(iStall);
		setDStall(dStall);
		withRiscvRef();
		loadHex(string(REGRESSION_PATH) + "../../resources/hex/" + hexName + ".hex");
		this->hexName = hexName;
	}

	virtual void checks(){

	}

	virtual void pass(){
		FILE *refFile = fopen((hexName + ".logRef").c_str(), "r");
    	fseek(refFile, 0, SEEK_END);
    	uint32_t refSize = ftell(refFile);
    	fseek(refFile, 0, SEEK_SET);
    	char* ref = new char[refSize];
    	fread(ref, 1, refSize, refFile);
    	fclose(refFile);
    	

    	logTraces.flush();
    	logTraces.close();

		FILE *logFile = fopen((name + ".logTrace").c_str(), "r");
    	fseek(logFile, 0, SEEK_END);
    	uint32_t logSize = ftell(logFile);
    	fseek(logFile, 0, SEEK_SET);
    	char* log = new char[logSize];
    	fread(log, 1, logSize, logFile);
    	fclose(logFile);
    	
    	if(refSize > logSize || memcmp(log,ref,refSize))
    		fail();
		else
			Workspace::pass();
	}
};

class Compliance : public WorkspaceRegression{
public:
	string name;
	ofstream out32;
	int out32Counter = 0;
	Compliance(string name) : WorkspaceRegression(name) {
		withRiscvRef();
		loadHex(string(REGRESSION_PATH) + "../../resources/hex/" + name + ".elf.hex");
		out32.open (name + ".out32");
		this->name = name;
		if(name == "C.ADDI16SP" || name == "C.ADDI4SPN"){
    		top->VexRiscv->RegFilePlugin_regFile[2] = 0;
		}
	}


    virtual void dBusAccess(uint32_t addr,bool wr, uint32_t size, uint8_t *dataBytes, bool *error) {
        if(wr && addr == 0xF00FFF2C){
            uint32_t *data = (uint32_t*)dataBytes;
            out32 << hex << setw(8) << std::setfill('0') << *data << dec;
            if(++out32Counter % 4 == 0) out32 << "\n";
        }
    	WorkspaceRegression::dBusAccess(addr,wr,size,dataBytes,error);
    }

	virtual void checks(){

	}



	virtual void pass(){
		FILE *refFile = fopen((string(REGRESSION_PATH) + string("../../resources/ref/") + name + ".reference_output").c_str(), "r");
    	fseek(refFile, 0, SEEK_END);
    	uint32_t refSize = ftell(refFile);
    	fseek(refFile, 0, SEEK_SET);
    	char* ref = new char[refSize];
    	fread(ref, 1, refSize, refFile);
    	fclose(refFile);


    	out32.flush();
    	out32.close();

		FILE *logFile = fopen((name + ".out32").c_str(), "r");
    	fseek(logFile, 0, SEEK_END);
    	uint32_t logSize = ftell(logFile);
    	fseek(logFile, 0, SEEK_SET);
    	char* log = new char[logSize];
    	fread(log, 1, logSize, logFile);
    	fclose(logFile);

    	if(refSize > logSize || memcmp(log,ref,refSize)){
    	    cout << "Bad compliance check" << endl;
    		fail();
		} else
			Workspace::pass();
	}
};


#ifdef DEBUG_PLUGIN

#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include <netinet/tcp.h>

#define RISCV_SPINAL_FLAGS_RESET 1<<0
#define RISCV_SPINAL_FLAGS_HALT 1<<1
#define RISCV_SPINAL_FLAGS_PIP_BUSY 1<<2
#define RISCV_SPINAL_FLAGS_IS_IN_BREAKPOINT 1<<3
#define RISCV_SPINAL_FLAGS_STEP 1<<4
#define RISCV_SPINAL_FLAGS_PC_INC 1<<5

#define RISCV_SPINAL_FLAGS_RESET_SET 1<<16
#define RISCV_SPINAL_FLAGS_HALT_SET 1<<17

#define RISCV_SPINAL_FLAGS_RESET_CLEAR 1<<24
#define RISCV_SPINAL_FLAGS_HALT_CLEAR 1<<25

class DebugPluginTest : public WorkspaceRegression{
public:
	pthread_t clientThreadId;
	char buffer[1024];
	bool clientSuccess = false, clientFail = false;

	static void* clientThreadWrapper(void *debugModule){
		((DebugPluginTest*)debugModule)->clientThread();
		return NULL;
	}

	int clientSocket;
	void accessCmd(bool wr, uint32_t size, uint32_t address, uint32_t data){
		buffer[0] = wr;
		buffer[1] = size;
		*((uint32_t*) (buffer + 2)) = address;
		*((uint32_t*) (buffer + 6)) = data;
		send(clientSocket,buffer,10,0);
	}

	void writeCmd(uint32_t size, uint32_t address, uint32_t data){
		accessCmd(true, 2, address, data);
	}


	uint32_t readCmd(uint32_t size, uint32_t address){
		accessCmd(false, 2, address, VL_RANDOM_I_WIDTH(32));
		int error;
		if((error = recv(clientSocket, buffer, 4, 0)) != 4){
			printf("Should read 4 bytes, had %d", error);
			while(1);
		}

		return *((uint32_t*) buffer);
	}



	void clientThread(){
		struct sockaddr_in serverAddr;

		//---- Create the socket. The three arguments are: ----//
		// 1) Internet domain 2) Stream socket 3) Default protocol (TCP in this case) //
		clientSocket = socket(PF_INET, SOCK_STREAM, 0);
		int flag = 1;
		int result = setsockopt(clientSocket,            /* socket affected */
								 IPPROTO_TCP,     /* set option at TCP level */
								 TCP_NODELAY,     /* name of option */
								 (char *) &flag,  /* the cast is historical
														 cruft */
								 sizeof(int));    /* length of option value */

		//---- Configure settings of the server address struct ----//
		// Address family = Internet //
		serverAddr.sin_family = AF_INET;
		// Set port number, using htons function to use proper byte order //
		serverAddr.sin_port = htons(7893);
		// Set IP address to localhost //
		serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
		// Set all bits of the padding field to 0 //
		memset(serverAddr.sin_zero, '\0', sizeof serverAddr.sin_zero);

		//---- Connect the socket to the server using the address struct ----//
		socklen_t addr_size = sizeof serverAddr;
		int error = connect(clientSocket, (struct sockaddr *) &serverAddr, addr_size);
//		printf("!! %x\n",readCmd(2,0x8));
		uint32_t debugAddress = 0xF00F0000;
		uint32_t readValue;

		while(resetDone != true){usleep(100);}

		while((readCmd(2,debugAddress) & RISCV_SPINAL_FLAGS_HALT) == 0){usleep(100);}
		if((readValue = readCmd(2,debugAddress + 4)) != 0x8000000C){
			printf("wrong breakA PC %x\n",readValue);
			clientFail = true; return;
		}

		writeCmd(2, debugAddress + 4, 0x13 + (1 << 15)); //Read regfile
		if((readValue = readCmd(2,debugAddress + 4)) != 10){
			printf("wrong breakB PC %x\n",readValue);
			clientFail = true; return;
		}

		writeCmd(2, debugAddress + 4, 0x13 + (2 << 15)); //Read regfile
		if((readValue = readCmd(2,debugAddress + 4)) != 20){
			printf("wrong breakC PC %x\n",readValue);
			clientFail = true; return;
		}

		writeCmd(2, debugAddress + 4, 0x13 + (3 << 15)); //Read regfile
		if((readValue = readCmd(2,debugAddress + 4)) != 30){
			printf("wrong breakD PC %x\n",readValue);
			clientFail = true; return;
		}

		writeCmd(2, debugAddress + 4, 0x13 + (1 << 7) + (40 << 20)); //Write x1 with 40
		writeCmd(2, debugAddress + 4, 0x80000eb7); //Write x29 with 0x10
		writeCmd(2, debugAddress + 4, 0x010e8e93); //Write x29 with 0x10
		writeCmd(2, debugAddress + 4, 0x67 + (29 << 15)); //Branch x29
		writeCmd(2, debugAddress + 0, RISCV_SPINAL_FLAGS_HALT_CLEAR); //Run CPU

		while((readCmd(2,debugAddress) & RISCV_SPINAL_FLAGS_HALT) == 0){usleep(100);}
		if((readValue = readCmd(2,debugAddress + 4)) != 0x80000014){
			printf("wrong breakE PC 3 %x\n",readValue);
			clientFail = true; return;
		}


		writeCmd(2, debugAddress + 4, 0x13 + (3 << 15)); //Read regfile
		if((readValue = readCmd(2,debugAddress + 4)) != 60){
			printf("wrong x1 %x\n",readValue);
			clientFail = true; return;
		}


		writeCmd(2, debugAddress + 4, 0x80000eb7); //Write x29 with 0x10
		writeCmd(2, debugAddress + 4, 0x018e8e93); //Write x29 with 0x10
		writeCmd(2, debugAddress + 4, 0x67 + (29 << 15)); //Branch x29
		writeCmd(2, debugAddress + 0, RISCV_SPINAL_FLAGS_HALT_CLEAR); //Run CPU



		while((readCmd(2,debugAddress) & RISCV_SPINAL_FLAGS_HALT) == 0){usleep(100);}
		if((readValue = readCmd(2,debugAddress + 4)) != 0x80000024){
			printf("wrong breakF PC 3 %x\n",readValue);
			clientFail = true; return;
		}


		writeCmd(2, debugAddress + 4, 0x13 + (3 << 15)); //Read x3
		if((readValue = readCmd(2,debugAddress + 4)) != 171){
			printf("wrong x3 %x\n",readValue);
			clientFail = true; return;
		}


		clientSuccess = true;
	}


	DebugPluginTest() : WorkspaceRegression("DebugPluginTest") {
		loadHex(string(REGRESSION_PATH) + "../../resources/hex/debugPlugin.hex");
		 pthread_create(&clientThreadId, NULL, &clientThreadWrapper, this);
	}

	virtual ~DebugPluginTest(){
		if(clientSocket != -1) close(clientSocket);
	}

	virtual void checks(){
		if(clientSuccess) pass();
		if(clientFail) fail();
	}

    virtual void postReset(){
        Workspace::postReset();
        top->VexRiscv->DebugPlugin_debugUsed = 1;
    }
};

#endif


//#ifdef LITEX
//class LitexSoC : public Workspace{
//public:
//
//	LitexSoC(string name) : Workspace(name) {
//
//	}
//	virtual bool isDBusCheckedRegion(uint32_t address){ return true;}
//	virtual bool isPerifRegion(uint32_t addr) { return (addr & 0xF0000000) == 0xB0000000 || (addr & 0xE0000000) == 0xE0000000;}
//    virtual bool isMmuRegion(uint32_t addr) { return (addr & 0xFF000000) != 0x81000000;}
//
//    virtual void dBusAccess(uint32_t addr,bool wr, uint32_t size,uint32_t mask, uint32_t *data, bool *error) {
//        if(isPerifRegion(addr)) switch(addr){
//    		//TODO Emulate peripherals here
//    		case 0xFFFFFFE0: if(wr) fail(); else *data = mTime; break;
//    		case 0xFFFFFFE4: if(wr) fail(); else *data = mTime >> 32; break;
//    		case 0xFFFFFFE8: if(wr) mTimeCmp = (mTimeCmp & 0xFFFFFFFF00000000) | *data; else *data = mTimeCmp; break;
//    		case 0xFFFFFFEC: if(wr) mTimeCmp = (mTimeCmp & 0x00000000FFFFFFFF) | (((uint64_t)*data) << 32); else *data = mTimeCmp >> 32; break;
//    		case 0xFFFFFFF8:
//    		    if(wr){
//                    cout << (char)*data;
//                    logTraces << (char)*data;
//                    logTraces.flush();
//				} else fail();
//				break;
//    		case 0xFFFFFFFC: fail(); break; //Simulation end
//    		default: cout << "Unmapped peripheral access : addr=0x" << hex << addr << " wr=" << wr << " mask=0x" << mask << " data=0x" << data << dec << endl; fail(); break;
//    	}
//
//    	Workspace::dBusAccess(addr,wr,size,mask,data,error);
//    }
//};
//#endif



#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
termios stdinRestoreSettings;
void stdinNonBuffered(){
	static struct termios old, new1;
    tcgetattr(STDIN_FILENO, &old); // grab old terminal i/o settings
    new1 = old; // make new settings same as old settings
    new1.c_lflag &= ~ICANON; // disable buffered i/o
    new1.c_lflag &= ~ECHO;
    tcsetattr(STDIN_FILENO, TCSANOW, &new1); // use these new terminal i/o settings now
    setvbuf(stdin, NULL, _IONBF, 0);
    stdinRestoreSettings = old;
}


bool stdinNonEmpty(){
  struct timeval tv;
  fd_set fds;
  tv.tv_sec = 0;
  tv.tv_usec = 0;
  FD_ZERO(&fds);
  FD_SET(STDIN_FILENO, &fds);
  select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
  return (FD_ISSET(0, &fds));
}


void stdoutNonBuffered(){
    setvbuf(stdout, NULL, _IONBF, 0);
}

void stdinRestore(){
    tcsetattr(STDIN_FILENO, TCSANOW, &stdinRestoreSettings);
}



void my_handler(int s){
   printf("Caught signal %d\n",s);
   stdinRestore();
   exit(1);
}
#include <signal.h>

void captureCtrlC(){
    struct sigaction sigIntHandler;

    sigIntHandler.sa_handler = my_handler;
    sigemptyset(&sigIntHandler.sa_mask);
    sigIntHandler.sa_flags = 0;

    sigaction(SIGINT, &sigIntHandler, NULL);
}




#if defined(LINUX_SOC) || defined(LINUX_REGRESSION)
#include <queue>
class LinuxSoc : public Workspace{
public:
    queue <char> customCin;
    void pushCin(string m){
        for(char& c : m) {
            customCin.push(c);
        }
    }

	LinuxSoc(string name) : Workspace(name) {
	    #ifdef WITH_USER_IO
		stdinNonBuffered();
		captureCtrlC();
	    #endif
		stdoutNonBuffered();
	}

	virtual ~LinuxSoc(){
	    #ifdef WITH_USER_IO
	    stdinRestore();
	    #endif
	}
	virtual bool isDBusCheckedRegion(uint32_t address){ return true;}
	virtual bool isPerifRegion(uint32_t addr) { return (addr & 0xF0000000) == 0xF0000000 || (addr & 0xE0000000) == 0xE0000000;}
    virtual bool isMmuRegion(uint32_t addr) { return true; }



    virtual void dBusAccess(uint32_t addr,bool wr, uint32_t size,uint8_t *dataBytes, bool *error) {
        uint32_t *data = (uint32_t*)dataBytes;

        if(isPerifRegion(addr)) {
            switch(addr){
                case 0xFFFFFFE0: if(wr) fail(); else *data = mTime; break;
                case 0xFFFFFFE4: if(wr) fail(); else *data = mTime >> 32; break;
                case 0xFFFFFFE8: if(wr) mTimeCmp = (mTimeCmp & 0xFFFFFFFF00000000) | *data; else *data = mTimeCmp; break;
                case 0xFFFFFFEC: if(wr) mTimeCmp = (mTimeCmp & 0x00000000FFFFFFFF) | (((uint64_t)*data) << 32); else *data = mTimeCmp >> 32; break;
                case 0xFFFFFFF8:
                    if(wr){
                        char c = (char)*data;
                        cout << c;
                        logTraces << c;
                        logTraces.flush();
                        onStdout(c);
                    } else {
                        #ifdef WITH_USER_IO
                        if(stdinNonEmpty()){
                            char c;
                            read(0, &c, 1);
                            *data = c;
                        } else
                        #endif
                        if(!customCin.empty()){
                            *data = customCin.front();
                            customCin.pop();
                        } else {
                            *data = -1;
                        }
                    }
                    break;
                case 0xFFFFFFFC: fail(); break; //Simulation end
                default: cout << "Unmapped peripheral access : addr=0x" << hex << addr << " wr=" << wr << " mask=0x"  << " data=0x" << data << dec << endl; fail(); break;
    		}
    	}
        Workspace::dBusAccess(addr,wr,size,dataBytes,error);
    }

    virtual void onStdout(char c){

    }
};

class LinuxRegression: public LinuxSoc{
public:
    string pendingLine = "";
    bool pendingLineContain(string m) {
        return strstr(pendingLine.c_str(), m.c_str()) != NULL;
    }

    enum State{LOGIN, ECHO_FILE, HEXDUMP, HEXDUMP_CHECK, PASS};
    State state = LOGIN;
	LinuxRegression(string name) : LinuxSoc(name) {

	}

    ~LinuxRegression() {
    }


    virtual void onStdout(char c){
        pendingLine += c;
        switch(state){
        case LOGIN: if (pendingLineContain("buildroot login:")) { pushCin("root\n"); state = ECHO_FILE; } break;
        case ECHO_FILE: if (pendingLineContain("# ")) { pushCin("echo \"miaou\" > test.txt\n"); state = HEXDUMP; pendingLine = "";} break;
        case HEXDUMP: if (pendingLineContain("# ")) { pushCin("hexdump -C test.txt\n"); state = HEXDUMP_CHECK; pendingLine = "";} break;
        case HEXDUMP_CHECK: if (pendingLineContain("00000000  6d 69 61 6f 75 0a  ")) { pushCin(""); state = PASS; pendingLine = "";} break;
        case PASS: if (pendingLineContain("# ")) { pass(); } break;
        }
        if(c == '\n' || pendingLine.length() > 200) pendingLine = "";
    }
};

#endif

#ifdef LINUX_SOC_SMP

class LinuxSocSmp : public Workspace{
public:
    queue <char> customCin;
    void pushCin(string m){
        for(char& c : m) {
            customCin.push(c);
        }
    }

	LinuxSocSmp(string name) : Workspace(name) {
	    #ifdef WITH_USER_IO
		stdinNonBuffered();
		captureCtrlC();
	    #endif
		stdoutNonBuffered();
	}

	virtual ~LinuxSocSmp(){
	    #ifdef WITH_USER_IO
	    stdinRestore();
	    #endif
	}
	virtual bool isDBusCheckedRegion(uint32_t address){ return true;}
	virtual bool isPerifRegion(uint32_t addr) { return (addr & 0xF0000000) == 0xF0000000;}
    virtual bool isMmuRegion(uint32_t addr) { return true; }



    virtual void dBusAccess(uint32_t addr,bool wr, uint32_t size, uint8_t *dataBytes, bool *error) {
        uint32_t *data = (uint32_t*)dataBytes;
        if(isPerifRegion(addr)) switch(addr){
    		case 0xF0010000: if(wr && *data != 0) fail(); else *data = 0;  break;
    		case 0xF001BFF8: if(wr) fail(); else *data = mTime; break;
    		case 0xF001BFFC: if(wr) fail(); else *data = mTime >> 32; break;
    		case 0xF0014000: if(wr) mTimeCmp = (mTimeCmp & 0xFFFFFFFF00000000) | *data; else fail(); break;
    		case 0xF0014004: if(wr) mTimeCmp = (mTimeCmp & 0x00000000FFFFFFFF) | (((uint64_t)*data) << 32); else fail(); break;
    		case 0xF0000000:
    		    if(wr){
    		        char c = (char)*data;
                    cout << c;
                    logTraces << c;
                    logTraces.flush();
                    onStdout(c);
				}
            case 0xF0000004:
    		    if(!wr){
				    #ifdef WITH_USER_IO
					if(stdinNonEmpty()){
						char c;
						read(0, &c, 1);
						*data = c;
					} else
					#endif
					if(!customCin.empty()){
					    *data = customCin.front();
                        customCin.pop();
					} else {
						*data = -1;
					}
				}
				break;
    		default: cout << "Unmapped peripheral access : addr=0x" << hex << addr << " wr=" << wr << " data=0x" << data << dec << endl; fail(); break;
    	}
        Workspace::dBusAccess(addr,wr,size,dataBytes,error);
    }

    virtual void onStdout(char c){

    }
};

#endif

string riscvTestMain[] = {
	//"rv32ui-p-simple",
	"rv32ui-p-lui",
	"rv32ui-p-auipc",
	"rv32ui-p-jal",
	"rv32ui-p-jalr",
	"rv32ui-p-beq",
	"rv32ui-p-bge",
	"rv32ui-p-bgeu",
	"rv32ui-p-blt",
	"rv32ui-p-bltu",
	"rv32ui-p-bne",
	"rv32ui-p-add",
	"rv32ui-p-addi",
	"rv32ui-p-and",
	"rv32ui-p-andi",
	"rv32ui-p-or",
	"rv32ui-p-ori",
	"rv32ui-p-sll",
	"rv32ui-p-slli",
	"rv32ui-p-slt",
	"rv32ui-p-slti",
	"rv32ui-p-sra",
	"rv32ui-p-srai",
	"rv32ui-p-srl",
	"rv32ui-p-srli",
	"rv32ui-p-sub",
	"rv32ui-p-xor",
	"rv32ui-p-xori"
};

string riscvTestMemory[] = {
	"rv32ui-p-lb",
	"rv32ui-p-lbu",
	"rv32ui-p-lh",
	"rv32ui-p-lhu",
	"rv32ui-p-lw",
	"rv32ui-p-sb",
	"rv32ui-p-sh",
	"rv32ui-p-sw"
};


string riscvTestFloat[] = {
    "rv32uf-p-fmadd",
    "rv32uf-p-fadd",
    "rv32uf-p-fcmp",
    "rv32uf-p-fcvt_w",
    "rv32uf-p-ldst",
    "rv32uf-p-recoding",
    "rv32uf-p-fclass",
    "rv32uf-p-fcvt",
    "rv32uf-p-fdiv",
    "rv32uf-p-fmin",
    "rv32uf-p-move"
};


string riscvTestDouble[] = {
    "rv32ud-p-fmadd",
    "rv32ud-p-fadd",
    "rv32ud-p-fcvt",
    "rv32ud-p-recoding",
    "rv32ud-p-fclass",
    "rv32ud-p-fcvt_w",
    "rv32ud-p-fmin",
    "rv32ud-p-fcmp",
    "rv32ud-p-fdiv",
    "rv32ud-p-ldst"
};




string riscvTestMul[] = {
	"rv32um-p-mul",
	"rv32um-p-mulh",
	"rv32um-p-mulhsu",
	"rv32um-p-mulhu"
};

string riscvTestDiv[] = {
	"rv32um-p-div",
	"rv32um-p-divu",
	"rv32um-p-rem",
	"rv32um-p-remu"
};

string freeRtosTests[] = {
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1",
//    "test1","test1","test1","test1","test1","test1","test1","test1"

		"AltQTest", "AltBlock",  "AltPollQ", "blocktim", "countsem", "dead", "EventGroupsDemo", "flop", "integer", "QPeek",
		"QueueSet", "recmutex", "semtest", "TaskNotify", "crhook", "dynamic",
		"GenQTest", "PollQ", "QueueOverwrite", "QueueSetPolling", "sp_flop", "test1"
		//"BlockQ","BlockQ","BlockQ","BlockQ","BlockQ","BlockQ","BlockQ","BlockQ"
//		"flop"
//		 "flop", "sp_flop" // <- Simple test
		 // "AltBlckQ" ???

};


string zephyrTests[] = {
    "tests_kernel_stack_stack_api",
    "tests_kernel_context",
//    "tests_kernel_critical",  //Too long
    "tests_kernel_fifo_fifo_api",
    "tests_kernel_mbox_mbox_usage",
//    "tests_kernel_mem_pool_mem_pool_threadsafe", //Too long
    "tests_kernel_sleep"
//    "tests_kernel_timer_timer_api" //Lock like if the CPU is too slow, it will make it fail
};



string riscvComplianceMain[] = {
    "I-IO",
    "I-NOP-01",
    "I-LUI-01",
    "I-ADD-01",
    "I-ADDI-01",
    "I-AND-01",
    "I-ANDI-01",
    "I-SUB-01",
    "I-OR-01",
    "I-ORI-01",
    "I-XOR-01",
    "I-XORI-01",
    "I-SRA-01",
    "I-SRAI-01",
    "I-SRL-01",
    "I-SRLI-01",
    "I-SLL-01",
    "I-SLLI-01",
    "I-SLT-01",
    "I-SLTI-01",
    "I-SLTIU-01",
    "I-SLTU-01",
    "I-AUIPC-01",
    "I-BEQ-01",
    "I-BGE-01",
    "I-BGEU-01",
    "I-BLT-01",
    "I-BLTU-01",
    "I-BNE-01",
    "I-JAL-01",
    "I-JALR-01",
    "I-DELAY_SLOTS-01",
    "I-ENDIANESS-01",
    "I-RF_size-01",
    "I-RF_width-01",
    "I-RF_x0-01",
};



string complianceTestMemory[] = {
    "I-LB-01",
    "I-LBU-01",
    "I-LH-01",
    "I-LHU-01",
    "I-LW-01",
    "I-SB-01",
    "I-SH-01",
    "I-SW-01"
};


string complianceTestCsr[] = {
    "I-CSRRC-01",
    "I-CSRRCI-01",
    "I-CSRRS-01",
    "I-CSRRSI-01",
    "I-CSRRW-01",
    "I-CSRRWI-01",
    #ifndef COMPRESSED
    "I-MISALIGN_JMP-01", //Only apply for non RVC cores
    #endif
    "I-MISALIGN_LDST-01",
    "I-ECALL-01",
};


string complianceTestMul[] = {
    "MUL",
    "MULH",
    "MULHSU",
    "MULHU",
};

string complianceTestDiv[] = {
    "DIV",
    "DIVU",
    "REM",
    "REMU",
};


string complianceTestC[] = {
    "C.ADD",
    "C.ADDI16SP",
    "C.ADDI4SPN",
    "C.ADDI",
    "C.AND",
    "C.ANDI",
    "C.BEQZ",
    "C.BNEZ",
    "C.JAL",
    "C.JALR",
    "C.J",
    "C.JR",
    "C.LI",
    "C.LUI",
    "C.LW",
    "C.LWSP",
    "C.MV",
    "C.OR",
    "C.SLLI",
    "C.SRAI",
    "C.SRLI",
    "C.SUB",
    "C.SW",
    "C.SWSP",
    "C.XOR",
};






struct timespec timer_start(){
    struct timespec start_time;
    clock_gettime(CLOCK_REALTIME, &start_time); //CLOCK_PROCESS_CPUTIME_ID
    return start_time;
}

long timer_end(struct timespec start_time){
    struct timespec end_time;
    clock_gettime(CLOCK_REALTIME, &end_time);
    uint64_t diffInNanos = end_time.tv_sec*1e9 + end_time.tv_nsec -  start_time.tv_sec*1e9 - start_time.tv_nsec;
    return diffInNanos;
}

#define redo(count,that) for(uint32_t xxx = 0;xxx < count;xxx++) that
#include <pthread.h>
#include <queue>
#include <functional>
#include <thread>


static void multiThreading(queue<std::function<void()>> *lambdas, std::mutex *mutex){
    uint32_t counter = 0;
	while(true){
		mutex->lock();
		if(lambdas->empty()){
			mutex->unlock();
			break;
		}

        #ifdef SEED
            uint32_t seed = SEED + counter;
            counter++;
            srand48(seed);
            printf("MT_SEED=%d \n", seed);
        #endif
		std::function<void()> lambda = lambdas->front();
		lambdas->pop();
		mutex->unlock();

		lambda();
	}
}


static void multiThreadedExecute(queue<std::function<void()>> &lambdas){
    std::mutex mutex;
    if(THREAD_COUNT == 1){
        multiThreading(&lambdas, &mutex);
    } else {
        std::thread * t[THREAD_COUNT];
        for(int id = 0;id < THREAD_COUNT;id++){
            t[id] = new thread(multiThreading,&lambdas,&mutex);
        }
        for(int id = 0;id < THREAD_COUNT;id++){
            t[id]->join();
            delete t[id];
        }
	}
}

int main(int argc, char **argv, char **env) {
    #ifdef SEED
    srand48(SEED);
    #endif
	Verilated::randReset(2);
	Verilated::commandArgs(argc, argv);

	printf("BOOT\n");
	timespec startedAt = timer_start();


#ifdef LINUX_SOC_SMP
    {

	    LinuxSocSmp soc("linuxSmp");
	    #ifndef DEBUG_PLUGIN_EXTERNAL
	    soc.withRiscvRef();
		soc.loadBin(EMULATOR, 0x80000000);
		soc.loadBin(VMLINUX,  0x80400000);
		soc.loadBin(DTB,      0x80FF0000);
		soc.loadBin(RAMDISK,  0x81000000);
		#endif
		//soc.setIStall(true);
		//soc.setDStall(true);
		soc.bootAt(0x80000000);
		soc.run(0);
//		soc.run((496300000l + 2000000) / 2);
//		soc.run(438700000l/2);
        return -1;
    }
#endif




    //return 0;

//#ifdef LITEX
//	LitexSoC("linux")
//		.withRiscvRef()
//		->loadBin(EMULATOR, 0x80000000)
//		->loadBin(DTB,      0x81000000)
//		->loadBin(VMLINUX,  0xc0000000)
//		->loadBin(RAMDISK,  0xc2000000)
//		->setIStall(false) //TODO It currently improve speed but should be removed later
//		->setDStall(false)
//		->bootAt(0x80000000)
//		->run(0);
//#endif

//	{
//		static struct termios old, new1;
//	    tcgetattr(0, &old); /* grab old terminal i/o settings */
//	    new1 = old; /* make new settings same as old settings */
//	    new1.c_lflag &= ~ICANON; /* disable buffered i/o */
//	    new1.c_lflag &= ~ECHO;
//	    tcsetattr(0, TCSANOW, &new1); /* use these new terminal i/o settings now */
//	}
//
//	   std::string initialCommand;
//
//	   while(true){
//	     if(!inputAvailable()) {
//	       std::cout << "Waiting for input (Ctrl-C to cancel)..." << std::endl;
//	       sleep(1);
//	     } else {
//	     char c;
//	     read(0, &c, 1); printf("%d\n", c);
////	     std::getline(std::cin, initialCommand);
//	     }
//	   }
//

//	char c;
//    while (1) { read(0, &c, 1); printf("%d\n", c); }
//	while(true){
//		char c = getchar();
//		if(c > 0)
//		{
//			putchar(c);
//		} else {
//			putchar('*');
//			sleep(500);
//		}
//	}

#ifdef LINUX_SOC
    {

	    LinuxSoc soc("linux");
	    #ifndef DEBUG_PLUGIN_EXTERNAL
	    soc.withRiscvRef();
		soc.loadBin(EMULATOR, 0x80000000);
		soc.loadBin(VMLINUX,  0xC0000000);
		soc.loadBin(DTB,      0xC3000000);
		soc.loadBin(RAMDISK,  0xC2000000);
		#endif
		//soc.setIStall(true);
		//soc.setDStall(true);
		soc.bootAt(0x80000000);
		soc.run(0);
//		soc.run((496300000l + 2000000) / 2);
//		soc.run(438700000l/2);
        return -1;
    }
#endif





//    #ifdef MMU
//        redo(REDO,WorkspaceRegression("mmu").withRiscvRef()->loadHex("../raw/mmu/build/mmu.hex")->bootAt(0x80000000u)->run(50e3););
//    #endif
//     redo(REDO,WorkspaceRegression("deleg").withRiscvRef()->loadHex("../raw/deleg/build/deleg.hex")->bootAt(0x80000000u)->run(50e3););
//    return 0;


	for(int idx = 0;idx < 1;idx++){

		#if defined(DEBUG_PLUGIN_EXTERNAL) || defined(RUN_HEX)
		{
			WorkspaceRegression w("run");
			#ifdef RUN_HEX
			//w.loadHex("/home/spinalvm/hdl/zephyr/zephyrSpinalHdl/samples/synchronization/build/zephyr/zephyr.hex");
			w.loadHex(RUN_HEX);
			w.withRiscvRef();
			#endif
			w.setIStall(false);
			w.setDStall(false);

			#if defined(TRACE) || defined(TRACE_ACCESS)
				//w.setCyclesPerSecond(5e3);
				//printf("Speed reduced 5Khz\n");
			#endif
			w.run(0xFFFFFFFFFFFF);
			exit(0);
		}
		#endif


		#ifdef ISA_TEST

		//	redo(REDO,TestA().run();)
			for(const string &name : riscvComplianceMain){
				redo(REDO, Compliance(name).run();)
			}
			for(const string &name : complianceTestMemory){
				redo(REDO, Compliance(name).run();)
			}

			#ifdef COMPRESSED
            for(const string &name : complianceTestC){
                redo(REDO, Compliance(name).run();)
            }
			#endif

			#ifdef MUL
			for(const string &name : complianceTestMul){
				redo(REDO, Compliance(name).run();)
			}
			#endif
			#ifdef DIV
			for(const string &name : complianceTestDiv){
				redo(REDO, Compliance(name).run();)
			}
			#endif
			#if defined(CSR) && !defined(CSR_SKIP_TEST)
			for(const string &name : complianceTestCsr){
				redo(REDO, Compliance(name).run();)
			}
			#endif

            #ifdef FENCEI
            redo(REDO, Compliance("I-FENCE.I-01").run();)
			#endif
            #ifdef EBREAK
            redo(REDO, Compliance("I-EBREAK-01").run();)
			#endif

			for(const string &name : riscvTestMain){
				redo(REDO,RiscvTest(name).withRiscvRef()->run();)
			}
			for(const string &name : riscvTestMemory){
				redo(REDO,RiscvTest(name).withRiscvRef()->run();)
			}


			#ifdef MUL
			for(const string &name : riscvTestMul){
				redo(REDO,RiscvTest(name).withRiscvRef()->run();)
			}
			#endif
			#ifdef DIV
			for(const string &name : riscvTestDiv){
				redo(REDO,RiscvTest(name).withRiscvRef()->run();)
			}
			#endif

            #ifdef COMPRESSED
            redo(REDO,RiscvTest("rv32uc-p-rvc").withRiscvRef()->bootAt(0x800000FCu)->run());
            #endif

			#if defined(CSR) && !defined(CSR_SKIP_TEST)
			    #ifndef COMPRESSED
				    uint32_t machineCsrRef[] = {1,11,   2,0x80000003u,   3,0x80000007u,   4,0x8000000bu,   5,6,7,0x80000007u     ,
				    8,6,9,6,10,4,11,4,    12,13,0,   14,2,     15,5,16,17,1 };
				    redo(REDO,TestX28("../../cpp/raw/machineCsr/build/machineCsr",machineCsrRef, sizeof(machineCsrRef)/4).withRiscvRef()->setVcdName("machineCsr")->run(10e4);)
                #else
				    uint32_t machineCsrRef[] = {1,11,   2,0x80000003u,   3,0x80000007u,   4,0x8000000bu,   5,6,7,0x80000007u     ,
				    8,6,9,6,10,4,11,4,    12,13,   14,2,     15,5,16,17,1 };
				    redo(REDO,TestX28("../../cpp/raw/machineCsr/build/machineCsrCompressed",machineCsrRef, sizeof(machineCsrRef)/4).withRiscvRef()->setVcdName("machineCsrCompressed")->run(10e4);)
                #endif
			#endif
//			#ifdef MMU
//				uint32_t mmuRef[] = {1,2,3, 0x11111111, 0x11111111, 0x11111111, 0x22222222, 0x22222222, 0x22222222, 4, 0x11111111, 0x33333333, 0x33333333, 5,
//					13, 0xC4000000,0x33333333, 6,7,
//					1,2,3, 0x11111111, 0x11111111, 0x11111111, 0x22222222, 0x22222222, 0x22222222, 4, 0x11111111, 0x33333333, 0x33333333, 5,
//					13, 0xC4000000,0x33333333, 6,7};
//				redo(REDO,TestX28("mmu",mmuRef, sizeof(mmuRef)/4).noInstructionReadCheck()->run(4e4);)
//			#endif

            #ifdef IBUS_CACHED
                redo(REDO,WorkspaceRegression("icache").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../raw/icache/build/icache.hex")->bootAt(0x80000000u)->run(50e3););
            #endif
            #ifdef DBUS_CACHED
                redo(REDO,WorkspaceRegression("dcache").loadHex(string(REGRESSION_PATH) + "../raw/dcache/build/dcache.hex")->bootAt(0x80000000u)->run(2500e3););
            #endif

            #ifdef MMU
                redo(REDO,WorkspaceRegression("mmu").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../raw/mmu/build/mmu.hex")->bootAt(0x80000000u)->run(50e3););
            #endif
            #ifdef SUPERVISOR
                redo(REDO,WorkspaceRegression("deleg").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../raw/deleg/build/deleg.hex")->bootAt(0x80000000u)->run(50e3););
            #endif

			#ifdef DEBUG_PLUGIN
			#ifndef CONCURRENT_OS_EXECUTIONS
				redo(REDO,DebugPluginTest().run(1e6););
            #endif
			#endif
		#endif

		#ifdef CUSTOM_SIMD_ADD
			redo(REDO,WorkspaceRegression("custom_simd_add").loadHex(string(REGRESSION_PATH) + "../custom/simd_add/build/custom_simd_add.hex")->bootAt(0x00000000u)->run(50e3););
		#endif

		#ifdef CUSTOM_CSR
			redo(REDO,WorkspaceRegression("custom_csr").loadHex(string(REGRESSION_PATH) + "../custom/custom_csr/build/custom_csr.hex")->bootAt(0x00000000u)->run(50e3););
		#endif


		#ifdef LRSC
			redo(REDO,WorkspaceRegression("lrsc").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../raw/lrsc/build/lrsc.hex")->bootAt(0x00000000u)->run(10e3););
		#endif

		#ifdef PMP
			redo(REDO,WorkspaceRegression("pmp").loadHex(string(REGRESSION_PATH) + "../raw/pmp/build/pmp.hex")->bootAt(0x80000000u)->run(10e3););
		#endif

		#ifdef AMO
			redo(REDO,WorkspaceRegression("amo").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../raw/amo/build/amo.hex")->bootAt(0x00000000u)->run(10e3););
		#endif

        #ifdef RVF
        for(const string &name : riscvTestFloat){
            redo(REDO,RiscvTest(name).withRiscvRef()->bootAt(0x80000188u)->writeWord(0x80000184u, 0x00305073)->run();)
        }
        #endif
        #ifdef RVD
        for(const string &name : riscvTestDouble){
            redo(REDO,RiscvTest(name).withRiscvRef()->bootAt(0x80000188u)->writeWord(0x80000184u, 0x00305073)->run();)
        }
        #endif

		#ifdef DHRYSTONE
			Dhrystone("dhrystoneO3_Stall","dhrystoneO3",true,true).run(1.5e6);
			#if defined(COMPRESSED)
			    Dhrystone("dhrystoneO3C_Stall","dhrystoneO3C",true,true).run(1.5e6);
            #endif
			#if defined(MUL) && defined(DIV)
				Dhrystone("dhrystoneO3M_Stall","dhrystoneO3M",true,true).run(1.9e6);
				#if defined(COMPRESSED)
				    Dhrystone("dhrystoneO3MC_Stall","dhrystoneO3MC",true,true).run(1.9e6);
				#endif
			#endif
			#if defined(COMPRESSED)
			Dhrystone("dhrystoneO3C","dhrystoneO3C",false,false).run(1.9e6);
            #endif
			Dhrystone("dhrystoneO3","dhrystoneO3",false,false).run(1.9e6);
			#if defined(MUL) && defined(DIV)
				#if defined(COMPRESSED)
				    Dhrystone("dhrystoneO3MC","dhrystoneO3MC",false,false).run(1.9e6);
				#endif
				Dhrystone("dhrystoneO3M","dhrystoneO3M",false,false).run(1.9e6);
			#endif
		#endif

        #ifdef COREMARK
            for(int withStall = 1; true ;withStall--){
                string rv = "rv32i";
                #if defined(MUL) && defined(DIV)
                    rv += "m";
                #endif
                #if defined(COMPRESSED)
                    if(withStall == -2) break;
                    if(withStall != -1) rv += "c";
                #else
                    if(withStall == -1) break;
                #endif
                WorkspaceRegression("coremark_" + rv + (withStall  > 0 ? "_stall" : "_nostall")).withRiscvRef()
                ->loadBin(string(REGRESSION_PATH) + "../../resources/bin/coremark_" + rv + ".bin", 0x80000000)
                ->bootAt(0x80000000)
                ->setIStall(withStall > 0)
                ->setDStall(withStall > 0)
                ->run(50e6);
            }
        #endif



		#ifdef FREERTOS
		{
		    #ifdef SEED
            srand48(SEED);
            #endif
			//redo(1,WorkspaceRegression("freeRTOS_demo").loadHex("../../resources/hex/freeRTOS_demo.hex")->bootAt(0x80000000u)->run(100e6);)
			vector <std::function<void()>> tasks;

            /*for(int redo = 0;redo < 4;redo++)*/{
                for(const string &name : freeRtosTests){
                    tasks.push_back([=]() { WorkspaceRegression(name + "_rv32i_O0").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/freertos/" + name + "_rv32i_O0.hex")->bootAt(0x80000000u)->run(4e6*15);});
                    tasks.push_back([=]() { WorkspaceRegression(name + "_rv32i_O3").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/freertos/" + name + "_rv32i_O3.hex")->bootAt(0x80000000u)->run(4e6*15);});
                    #ifdef COMPRESSED
//                        tasks.push_back([=]() { WorkspaceRegression(name + "_rv32ic_O0").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/freertos/" + name + "_rv32ic_O0.hex")->bootAt(0x80000000u)->run(5e6*15);});
                        tasks.push_back([=]() { WorkspaceRegression(name + "_rv32ic_O3").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/freertos/" + name + "_rv32ic_O3.hex")->bootAt(0x80000000u)->run(4e6*15);});
                    #endif
                    #if defined(MUL) && defined(DIV)
//                        #ifdef COMPRESSED
//                            tasks.push_back([=]() { WorkspaceRegression(name + "_rv32imac_O3").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/freertos/" + name + "_rv32imac_O3.hex")->bootAt(0x80000000u)->run(4e6*15);});
//                        #else
                            tasks.push_back([=]() { WorkspaceRegression(name + "_rv32im_O3").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/freertos/" + name + "_rv32im_O3.hex")->bootAt(0x80000000u)->run(4e6*15);});
//                        #endif
                    #endif
                }
			}

            while(tasks.size() > FREERTOS_COUNT){
                tasks.erase(tasks.begin() + (VL_RANDOM_I_WIDTH(32)%tasks.size()));
            }


            queue <std::function<void()>> tasksSelected(std::deque<std::function<void()>>(tasks.begin(), tasks.end()));
			multiThreadedExecute(tasksSelected);
        }
		#endif

        #ifdef ZEPHYR
        {
            #ifdef SEED
            srand48(SEED);
            #endif
            //redo(1,WorkspaceRegression("freeRTOS_demo").loadHex("../../resources/hex/freeRTOS_demo.hex")->bootAt(0x80000000u)->run(100e6);)
            vector <std::function<void()>> tasks;

            /*for(int redo = 0;redo < 4;redo++)*/{
                for(const string &name : zephyrTests){
                    #ifdef COMPRESSED
                        tasks.push_back([=]() { ZephyrRegression(name + "_rv32ic").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/VexRiscvRegressionData/sim/zephyr/" + name + "_rv32ic.hex")->bootAt(0x80000000u)->run(180e6);});
                    #else
                        tasks.push_back([=]() { ZephyrRegression(name + "_rv32i").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/VexRiscvRegressionData/sim/zephyr/" + name + "_rv32i.hex")->bootAt(0x80000000u)->run(180e6);});
                    #endif
                    #if defined(MUL) && defined(DIV)
                            tasks.push_back([=]() { ZephyrRegression(name + "_rv32im").withRiscvRef()->loadHex(string(REGRESSION_PATH) + "../../resources/VexRiscvRegressionData/sim/zephyr/" + name + "_rv32im.hex")->bootAt(0x80000000u)->run(180e6);});
                    #endif
                }
            }

            while(tasks.size() > ZEPHYR_COUNT){
                tasks.erase(tasks.begin() + (VL_RANDOM_I_WIDTH(32)%tasks.size()));
            }


            queue <std::function<void()>> tasksSelected(std::deque<std::function<void()>>(tasks.begin(), tasks.end()));
            multiThreadedExecute(tasksSelected);
        }
        #endif

		#if defined(LINUX_REGRESSION)
            {

        	    LinuxRegression soc("linux");
        	    #ifndef DEBUG_PLUGIN_EXTERNAL
        	    soc.withRiscvRef();
        		soc.loadBin(string(REGRESSION_PATH) + EMULATOR, 0x80000000);
        		soc.loadBin(string(REGRESSION_PATH) + VMLINUX,  0xC0000000);
        		soc.loadBin(string(REGRESSION_PATH) + DTB,      0xC3000000);
        		soc.loadBin(string(REGRESSION_PATH) + RAMDISK,  0xC2000000);
        		#endif
        		//soc.setIStall(true);
        		//soc.setDStall(true);
        		soc.bootAt(0x80000000);
        		soc.run(153995602l*9);
//        		soc.run((470000000l + 2000000) / 2);
//        		soc.run(438700000l/2);
            }
        #endif

	}

	uint64_t duration = timer_end(startedAt);
	cout << endl << "****************************************************************" << endl;
	cout << "Had simulate " << Workspace::cycles << " clock cycles in " << duration*1e-9 << " s (" << Workspace::cycles / (duration*1e-6) << " Khz)" << endl;
	if(Workspace::successCounter == Workspace::testsCounter)
		cout << "REGRESSION SUCCESS " << Workspace::successCounter << "/" << Workspace::testsCounter << endl;
	else
		cout<< "REGRESSION FAILURE " << Workspace::testsCounter - Workspace::successCounter << "/"  << Workspace::testsCounter << endl;
	cout << "****************************************************************" << endl << endl;


	exit(0);
}
