Where to Start to Design a RISC-V CPU Core?

In my latest post, I tried to answer the question of is it worth to design a RISC-V core from scratch? The answer was yes for educational and learning purposes in my opinion. So, when someone wants to design a CPU, what is first to do and where to look at? Let’s find out together.

First of all, there needs a specification of the ISA. Which instructions will be supported? This is I think the most important question. In order to find an answer, I looked at riscv international webpage and found a page called “Specifications”:

https://riscv.org/technical/specifications/

Here we see 2 specifications:

Volume 1, Unprivileged Specification version 20191213

Volume 2, Privileged Specification version 20211203

In Volume 1, Unprivileged Specification document, in the Introduction section, it is written that:

“The RISC-V manual is structured in two volumes. This volume covers the design of the base unprivileged instructions, including optional unprivileged ISA extensions. Unprivileged instructions are those that are generally usable in all privilege modes in all privileged architectures, though behavior might vary depending on privilege mode and privilege architecture. The second volume provides the design of the first (“classic”) privileged architecture.”

In RISC-V, there are modes, or privilege levels and they are defined in Privileged Specification as:

User/Application              : U

Supervisor                        : S

Machine                           : M

Machine mode has the higher privilege to access HW resources, while user mode has the lowest. Unprivileged Specification defines general-purpose registers (GPR) and unprivileged instructions, while Privileged Specification defines control-status registers (CSR) and privileged instructions.

Privileged Specification has information about interrupts, counters, traps, timer, security, physical memory attributes and protection. For the HW point of view, in order to implement a RISC-V CPU core, we need to look at Unprivileged Specification, where base instruction set, RV32I is defined. Going into details of the Privileged Specification is beyond the scope of our aim of designing a simple RISC-V CPU core. You can access RISC-V Privileged Specification from RISC-V Specifications page. You can find detailed information about RISC-V privileges with example RISC-V assembly codes in the link below:

https://danielmangum.com/posts/risc-v-bytes-privilege-levels/

Now let’s delve into the Unprivileged Specification of the RISC-V. Up to Chapter 2 – RV32I Base Instruction Set, Version 2.1, detailed information regarding to some aspects of RISC-V is given. In the first part of the Chapter 2, registers are described, there are 32 general purpose registers in RISC-V, from x0 to x31, while x0 is hardwired with all bits equal to zero. The specification also told us program counter (PC) register, which holds the address of the current instruction. You need to support CSR registers in order to comply with Privileged Specification, but we won’t for the first phase of our CPU design.

Then the instruction formats are defined in the specification and at first, there are 6 instruction types in RV32I:

R-type

I-type

S-type

U-type

B-type

J-type

The fields of the instruction types are given as:

Instruction set listings are given in Chapter 24 of the specification. Here is the RV32I instruction list from the specification:

This github repo riscv-card gives a more detailed instruction list:

https://github.com/jameslzhu/riscv-card/blob/master/riscv-card.pdf

Specification has 40 instructions, whereas the github repo has 39, excluding FENCE instruction. RV32I base instruction set talks about as FENCE, ECALL and EBREAK as:

RV32I was designed to be sufficient to form a compiler target and to support modern operating system environments. The ISA was also designed to reduce the hardware required in a minimal implementation. RV32I contains 40 unique instructions, though a simple implementation might cover the ECALL/EBREAK instructions with a single SYSTEM hardware instruction that always traps and might be able to implement the FENCE instruction as a NOP, reducing base instruction count to 38 total

The ECALL and EBREAK instructions, which are related to transferring the control of the hart to OS or debugger. In our CPU implementation, in the first stage, we will implement FENCE as NOP, combine ECALL and EBREAK instructions as a trap. I don’t know how or what functionality to implement for this trap for the time being, but I will go into that subject in later posts.

Inputs and outputs need to be specified in the early stage of the CPU design. In conventional RISC-V CPU designs, we usually see that there is an instruction memory interface, data memory interface, interrupt interface and debug interface.

Let’s analyze one of the open-source RISC-V CPU implementations I/O ports.

Let’s look at cv32e40p. This RISC-V core is designed in PULP team and now maintained by OpenHW group:

https://github.com/openhwgroup/cv32e40p

cv32e40p has a detailed documentation page and we can infer I/O properties both from this page or from the source code itself. Let’s look at the documentation page:

https://docs.openhwgroup.org/projects/cv32e40p-user-manual/en/latest/integration.html

We see there are certain parameters, most related to floating point unit (FPU), supporting floating-point extension of the RISC-V ISA. By default, cv32e40p does not support RV32F extension, but if you want to enable it, you can set FPU parameter. Now let’s focus on I/O ports. cv32e40p core separated ports to different sets:

Clock and reset

– rst_ni

– clk_i

– scan_cg_en_i

For this set of signals, I won’t include in my CPU design scan_cg_en_i signal. It’s function is scan clock gate enable and it is a DFT (design for test) related signal. During normal operation this signal must be 0. Normally, you want to add scan-chain for I/O pin testing or FF testing inside your IC. We will implement our CPU into FPGA, no need for any kind of scan-chain.

Special control signals

– fetch_enable_i

– pulp_clock_en_i

– core_sleep_o

Fetch enable signal seems legit. It is a good thing to control fetching instructions from the memory. I can add this port to my CPU design. However, I won’t implement a sleep unit such as cv32e40p has, so clock enable or core sleep ports are unnecessary.

Configuration

– boot_addr_i

– mtvec_addr_i

– dm_halt_addr_i

– dm_exception_addr_i

– hart_id_i

Boot address and mtvec (machine trap vector) address can be used as input signals. They can also be defined as constants. I will decide it when I am implementing the CPU. I won’t implement a debug module, so the design won’t include debug mode halt or debug mode exception address signals. Hart id is not that important also for a simple CPU implementation.

Instruction memory interface

– instr_addr_o

– instr_req_o

– instr_gnt_i

– instr_rvalid_i

– instr_rdata_i

I can change instruction fetch interface from the design of cv32e40p. I am thinking of using a Block RAM as instruction memory, actually we can say ROM, since I don’t plan to support booting from an external memory or interface. In that case, instruction memory can’t be read-only. But for a simple implementation, the BRAM can be used as a ROM and an initialization file is used to load instruction memory with appropriate RISC-V RV32I instructions.

Therefore, if BRAM is utilized, we only need address. When the CPU update PC (program counter) and so instruction memory address, the instruction memory will update the data output accordingly. No need for a handshake protocol for this simple CPU implementation.

Data memory interface

– data_addr_o

– data_req_o

– data_gnt_i

– data_we_o

– data_be_o

– data_wdata_o

– data_rvalid_i

– data_rdata_i

For data memory, we also need a write function. According to BRAM interface, a write enable, write address and write data are needed. We will also need byte enable (BE) port, since RV32I has SB, SH and SW instructions. We can combine read and write address port, since we won’t support simultaneous write and read in data memory.

Interrupt interface

– irq_i

– irq_ack_o

– irq_id_o

In our simple design, we won’t support interrupt at the first phase. Maybe later but not on the first implementation. No need for interrupt signals.

Debug interface

– debug_req_i

– debug_havereset_o

– debug_running_o

– debug_halted_o

In our simple design, we won’t support debug at the first phase. Maybe later but not on the first implementation. No need for debug signals.

To summarize for I/O ports, I plan to use these ports in the CPU:

clk
rstn
fetch_en_i
boot_addr_i [31:0] (not for sure, will be decided later)
mtvec_addr_i [31:0] (not for sure, will be decided later)
instr_addr_o [31:0]
instr_data_i [31:0]
data_addr_o [31:0]
data_wdata_o [31:0]
data_be_o [3:0]
data_we_o 
data_rdata_i [31:0]

In this post, we see that to start a CPU design, one first need to decide which instruction set and extensions will be supported? What will be the I/O ports? For RISC-V RV32I, 38 instructions need to be supported, excluding FENCE, merging ECALL and EBREAK. I will analyze deeply these 38 instructions and how they will be implemented in the HW. I will also try to do a preliminary architecture design for the CPU, which blocks will be designed and what will be their functionalities. Let me say that I am not aiming an industrial spec CPU, which supports RISC-V Privileged specification. I am only considering unprivileged specification and RV32I base instruction set. So, I won’t implement any CSR and support CSR access instructions, or interrupt and debug functionalities. You need to implement these if you want to design a commercial RISC-V CPU. I am only aiming the design a simple CPU for training and educational purposes.

Hope to see you next time with architecture design of the CPU!

Regards,

Mehmet Burak AYKENAR

You can connect me via LinkedIn: Just sent me an invitation

https://tr.linkedin.com/in/mehmet-burak-aykenar-73326419a

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir