This chip is a reduced version of the one developed by the authors in the following subjects of the MIRI-HPC master of the UPC:
Elpis is a 5-stage pipelined and multi-cycle processor implemented from scratch based on RISC-V architecture, mixed with some MIPS ideas. However, due to the limitations of the tools this Elpis is lighter than our initial Elpis core. Anyway, the version that we present has the following characteristics:
The supported instructions by Elpis are: ADD, SUB, MUL, LDB, LDW, STB, STW, BEQ, JUMP, IRET, MOVE, TLBWRITE, ADDI, OR, ORI, AND, ANDI, XOR, XORI, SLL, SRL, SRA, SLLI, SRLI, SRAI, BGE, BLT, BNE, MOVR, ECALL, READ, PRINT. The instruction set encoding is mainly based on RISCV32, but there are some differences respecting the pipeline management where we present new instructions not existing in RISC-V:
0x0000007F
, sets the PSW to 0 (user mode) and jumps to the PC that RM0 holds. This instruction is only permitted to execute if PSW=1 (privileged mode).jump x9
, where its decoding will result in an OPCODE [6:0] = 1101111 and destination register = [19:15]. As for the rest, the other bits are ignored.mov x7, rm2
, where its decoding will result in an OPCODE [6:0] = 0101111, funct7 [31:25]= 0000000, source register (special register) = [21:20] and destination register (regular register) = [11:7]. As for the rest, the other bits are ignored.movr rm4 x15
, where its decoding will result in an OPCODE [6:0] = 0101111, funct7 [31:25] = 0000001, source register (regular register) = [21:20] and destination register (special register) = [11:7]. As for the rest, the other bits are ignored.ecall 6
is codified as 0x00600073
, otherwise if is an ecall 7
is codified as 0x00700073
. The code 6 is used in case we want to send data to PicoRV, and the code 7 is used in case we want to receive data from PicoRV.0x0200007D
. However, this instruction is only legal if privileged mode is enabled. On the other hand, if user mode is enabled it should be executed through an ecall instruction (ecall 6
), which would result in an equivalent functionality.0x0400007D
. However, this instruction is only legal if privileged mode is enabled. On the other hand, if user mode is enabled it should be executed through an ecall instruction (ecall 7
), which would result in an equivalent functionality.First of all, we need the following function in order to load the instructions and data into Elpis:
void elpis_load_memory(uint32_t* program_data, uint32_t* program_addr) { int i, continue_reading; continue_reading = 1; i = 0; reg_la3_data = 0x00000004; reg_la3_data = 0x00000005; reg_la3_data = 0x00000004; while (continue_reading) { if (program_data[i] == ((uint32_t) 0xFFFFFFFF)) { continue_reading = 0; }else { reg_la0_data = program_addr[i]; reg_la1_data = program_data[i]; } reg_la3_data = 0x00000005; reg_la3_data = 0x00000004; i++; } reg_la3_data = 0x00000001; reg_la3_data = 0x00000000; }
Inside the main()
function we perform the following steps:
void main(){ ... // Configuring LA probes // outputs from the cpu are inputs for my project denoted for been 0 // inputs to the cpu are outpus for my project denoted for been 1 reg_la0_oenb = reg_la0_iena = 0x00000000; // [31:0] reg_la1_oenb = reg_la1_iena = 0x00000000; // [63:32] reg_la2_oenb = reg_la2_iena = 0x00000000; // [95:64] reg_la3_oenb = reg_la3_iena = 0x00000000; // [127:96] // Flag start of the test reg_mprj_datal = 0xAB400000; // Elpis OS information uint32_t OS_DATA[30]; OS_DATA[0] = 0x00502023; OS_DATA[1] = 0x00602223; OS_DATA[2] = 0x00702423; OS_DATA[3] = 0x00802623; OS_DATA[4] = 0x00902823; OS_DATA[5] = 0x00400413; OS_DATA[6] = 0x00500493; OS_DATA[7] = 0x00600293; OS_DATA[8] = 0x00700313; OS_DATA[9] = 0x002003af; OS_DATA[10] = 0x00838c63; OS_DATA[11] = 0x00938a63; OS_DATA[12] = 0x00538c63; OS_DATA[13] = 0x00638e63; OS_DATA[14] = 0x00038e63; OS_DATA[15] = 0x02000863; OS_DATA[16] = 0x0000002e; OS_DATA[17] = 0x00000863; OS_DATA[18] = 0x0200007D; OS_DATA[19] = 0x00000463; OS_DATA[20] = 0x0400007D; OS_DATA[21] = 0x00002283; OS_DATA[22] = 0x00402303; OS_DATA[23] = 0x00802383; OS_DATA[24] = 0x00c02403; OS_DATA[25] = 0x01002483; OS_DATA[26] = 0x0000007F; OS_DATA[27] = 0x00000033; OS_DATA[28] = 0x00002050; OS_DATA[29] = 0xFFFFFFFF; uint32_t OS_ADDR[30]; OS_ADDR[0] = 0x00000010; OS_ADDR[1] = 0x00000011; OS_ADDR[2] = 0x00000012; OS_ADDR[3] = 0x00000013; OS_ADDR[4] = 0x00000014; OS_ADDR[5] = 0x00000015; OS_ADDR[6] = 0x00000016; OS_ADDR[7] = 0x00000017; OS_ADDR[8] = 0x00000018; OS_ADDR[9] = 0x00000019; OS_ADDR[10] = 0x0000001a; OS_ADDR[11] = 0x0000001b; OS_ADDR[12] = 0x0000001c; OS_ADDR[13] = 0x0000001d; OS_ADDR[14] = 0x0000001e; OS_ADDR[15] = 0x0000001f; OS_ADDR[16] = 0x00000020; OS_ADDR[17] = 0x00000021; OS_ADDR[18] = 0x00000022; OS_ADDR[19] = 0x00000023; OS_ADDR[20] = 0x00000024; OS_ADDR[21] = 0x00000025; OS_ADDR[22] = 0x00000026; OS_ADDR[23] = 0x00000027; OS_ADDR[24] = 0x00000028; OS_ADDR[25] = 0x00000029; OS_ADDR[26] = 0x0000002a; OS_ADDR[27] = 0x0000002b; OS_ADDR[28] = 0x00000005; OS_ADDR[29] = 0xFFFFFFFF; // Elpis user program // Sending data to Elpis example uint32_t USER_DATA[4]; USER_DATA[0] = 0x00200093; USER_DATA[1] = 0x0210022F; USER_DATA[2] = 0x00600073; USER_DATA[3] = 0xFFFFFFFF; uint32_t USER_ADDR[4]; USER_ADDR[0] = 0x00000040; USER_ADDR[1] = 0x00000041; USER_ADDR[2] = 0x00000042; USER_ADDR[3] = 0xFFFFFFFF; // Loading Elpis memory elpis_load_memory(OS_DATA, OS_ADDR); elpis_load_memory(USER_DATA, USER_ADDR); reg_la3_oenb = reg_la3_iena = 0x00000001; // Recovering fast clock not controlled by the user // Reset of Elpis and start of computation at Elpis reg_la3_data = 0x00000002; reg_la3_data = 0x00000000; // Start of sending data to PicoRV section (if needed) // Check bit 100 to be active while (reg_la3_data != 0x00000010); // Check bit 100 has the right data if (reg_wb_reads == 0x00000002){ print("OK\n\n"); } else { print("ERROR\n\n"); } // End of sending data to PicoRV section (if needed) // Start of receiving data from PicoRV section (if needed) reg_la2_data = 0x00000002; // Setting the value that will be read at Elpis reg_la3_data = 0x00000008; // Notice Elpis that the values has been sent reg_la3_data = 0x00000000; // Stop notifiying Elpis that the value has been sent // End of receiving data from PicoRV section (if needed) }
We provide a set of tests that verifies all the described functionality.
The tests that we are providing in RTL are the following:
testBasicOps: This test checks the basic behaviour of the CPU.
testRead: This test checks that PicoRV is able to send data to Elpis.
testPrint: This test checks that Elpis is able to send data to PicoRV.
testBytes: This test checks that Elpis performs correctly loads and stores and in byte-level.
testMul3: This test checks that Elpis is able to do multiplications properly.
The tests that we are providing in GL are the following: