11. Control and status registers

Control and status registers are registers that are not part of the general-purpose registers x0-x31, but instead have "special" functions. That is, they control something or return the status of something. To read or write them, there are special instructions defined by the "Zicsr" extension.

The RISC-V specification is a bit awkward in the sense that it's a bit arbitrarily split into "unprivileged" and "privileged" volumes. Since we're implementing a very simple processor, we only have a single privilege level. But this doesn't mean that we don't have to consider the privileged architecture!

It's probably best explained in the RISC-V unprivileged architecture:

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.

Another confusing aspect is that for any hardware implementation, the "Zicsr" extension for control and status registers is mandatory. So, the Zicsr extension, which describes the instructions to access CSRs, is described in the unprivileged part of the manuals, while the CSRs themselves are described in the privileged part of the manuals.

The "Zicsr" extension defines 6 instructions:

  • CSRRW rd, csr, rs (read/write CSR) puts the value of the CSR csr into the general-purpose register rd, and the value of the general-purpose register rs into csr. CSRRWI is similar but uses an immediate instead of rs.
  • CSRRS rd, csr, rs (read and set bits in CSR) puts the value of the CSR csr into the general-purpose register rd, and sets the high bits of the general-purpose register rs in csr. CSRRS is similar but uses an immediate instead of rs.
  • CSRRC rd, csr, rs (read and clear bits in CSR) puts the value of the CSR csr into the general-purpose register rd, and clears the high bits of the general-purpose register rs in csr. CSRRS is similar but uses an immediate instead of rs.

The RISC-V assembly programmer's manual defines various pseudoinstructions:

  • CSRR rs, csr is an alias for CSRRS rd, csr, x0 and can be used to read a CSR.
  • CSRW csr, rs is an alias for CSRRW x0, csr, rs and can be used to write a CSR. Similarly, CSRWI csr, imm is an alias for CSRRWI x0, csr, imm.
  • CSRS csr, rs is an alias for CSRRS x0, csr, rs and can be used to set bits in a CSR. Similarly, CSRSI csr, imm is an alias for CSRRSI x0, csr, imm.
  • CSRC csr, rs is an alias for CSRRC x0, csr, rs and can be used to clear bits in a CSR. Similarly, CSRCI csr, imm is an alias for CSRRCI x0, csr, imm.

There are two kinds of CSRs: read-only CSRs, and read-write CSRs. Obviously, read-only CSRs can only be read. Read-only CSRs have an address with their most significant bits (bits 10 and 11) set to 1.

Trying to write to them will result in an "illegal instruction" exception.

The manual defines three types of fields within read-write CSRs:

  • Writes preserve, reads ignore values (WPRI): these fields are reserved for future use and software should ignore the values from these fields. Writes should preserve the value of these fields. For forward compatibility, implementations that do not furnish these fields must make them read-only zero.
  • Write legal, read legal values (WLRL): fields of this type should only be written with legal values (what is a legal value should be specified per CSR). Implementations are permitted but not required to raise an illegal-instruction exception if an instruction attempts to write a non-supported value to a WLRL field. Implementations can return arbitrary bit patterns on the read of a WLRL field when the last write was of an illegal value, but the value returned should deterministically depend on the illegal written value and the value of the field prior to the write.
  • Write any, read legal values (WARL): Fields of this type allow any value to be written to them, but will always return a legal value when read.

Fields in read-write CSRs can also be read-only, which simply means writes to these fields are ignored (no exception happens). For some reason this doesn't get a fancy acronym (maybe read-only CSRs are considered a special case of WARL CSRs?).

For fields of the last type (WARL), the manual notes the following:

Assuming that writing the CSR has no other side effects, the range of supported values can be determined by attempting to write a desired setting then reading to see if the value was retained.

We need to read the manuals carefully to see which CSRs we need to implement for our minimal implementation. The easiest way to do this is to read chapter 3 of the privileged part of the RISC-V manual. I've gone ahead and did the research for you:

  • mvendorid
  • marchid
  • mimpid
  • mhartid
  • mconfigptr
  • mstatus
  • misa
  • mtvec
  • mstatush
  • mscratch
  • mepc
  • mcause
  • mtval
  • mip
  • mcycle
  • minstret
  • mhpmcounter3..mhpmcounter31
  • mcycleh
  • minstreth
  • mhpmcounter3h..mhpmcounter31h
  • mhpmevent3..mhpmevent31
  • mhpmevent3h..mhpmevent31h

(I give this list as a reference, to find relevant information it's still necessary to search what the manual says about the CSRs.)

So, let's get going. First we add some placeholders in the decoder.

src/core/decode_write.vhd CHANGED
@@ -302,6 +302,22 @@ begin
302
  -- ECALL
303
  elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
304
  -- EBREAK
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  elsif opcode = "1111111" and funct3 = "000" then
306
  -- LED (custom instruction): set the LEDs to the 8 least significant bits of rs1
307
  v_decode_output.operation := OP_LED;
 
302
  -- ECALL
303
  elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
304
  -- EBREAK
305
+ elsif opcode = "1110011" then
306
+ if funct3 = "001" then
307
+ -- TODO: CSRRW
308
+ elsif funct3 = "010" then
309
+ -- TODO: CSRRS
310
+ elsif funct3 = "011" then
311
+ -- TODO: CSRRC
312
+ elsif funct3 = "101" then
313
+ -- TODO: CSRRWI
314
+ elsif funct3 = "110" then
315
+ -- TODO: CSRRSI
316
+ elsif funct3 = "111" then
317
+ -- TODO: CSRRCI
318
+ else
319
+ v_decode_output.is_invalid := '1';
320
+ end if;
321
  elsif opcode = "1111111" and funct3 = "000" then
322
  -- LED (custom instruction): set the LEDs to the 8 least significant bits of rs1
323
  v_decode_output.operation := OP_LED;

In the decoder we'll set the operation to one of OP_CSRRW, OP_CSRRS, OP_CSRRC. We'll put the value of rs1 (or the immediate value, for the I-variants of the instructions) in the first operand and the address of the CSR register into the second operand.

src/core/decode_write.vhd CHANGED
@@ -303,18 +303,33 @@ begin
303
  elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
304
  -- EBREAK
305
  elsif opcode = "1110011" then
 
 
 
306
  if funct3 = "001" then
307
- -- TODO: CSRRW
 
 
308
  elsif funct3 = "010" then
309
- -- TODO: CSRRS
 
 
310
  elsif funct3 = "011" then
311
- -- TODO: CSRRC
 
 
312
  elsif funct3 = "101" then
313
- -- TODO: CSRRWI
 
 
314
  elsif funct3 = "110" then
315
- -- TODO: CSRRSI
 
 
316
  elsif funct3 = "111" then
317
- -- TODO: CSRRCI
 
 
318
  else
319
  v_decode_output.is_invalid := '1';
320
  end if;
 
303
  elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
304
  -- EBREAK
305
  elsif opcode = "1110011" then
306
+ v_decode_output.operand2 := "00000000000000000000" & i_imm; -- store CSR register in operand 2
307
+ v_decode_output.destination_reg := rd;
308
+
309
  if funct3 = "001" then
310
+ -- CSRRW
311
+ v_decode_output.operation := OP_CSRRW;
312
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
313
  elsif funct3 = "010" then
314
+ -- CSRRS
315
+ v_decode_output.operation := OP_CSRRS;
316
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
317
  elsif funct3 = "011" then
318
+ -- CSRRC
319
+ v_decode_output.operation := OP_CSRRC;
320
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
321
  elsif funct3 = "101" then
322
+ -- CSRRWI
323
+ v_decode_output.operation := OP_CSRRW;
324
+ v_decode_output.operand1 := "000000000000000000000000000" & rs1;
325
  elsif funct3 = "110" then
326
+ -- CSRRSI
327
+ v_decode_output.operation := OP_CSRRS;
328
+ v_decode_output.operand1 := "000000000000000000000000000" & rs1;
329
  elsif funct3 = "111" then
330
+ -- CSRRCI
331
+ v_decode_output.operation := OP_CSRRC;
332
+ v_decode_output.operand1 := "000000000000000000000000000" & rs1;
333
  else
334
  v_decode_output.is_invalid := '1';
335
  end if;
src/core/types.vhd CHANGED
@@ -29,6 +29,9 @@ package core_types is
29
  OP_LW,
30
  OP_LBU,
31
  OP_LHU,
 
 
 
32
  OP_LED
33
  );
34
 
 
29
  OP_LW,
30
  OP_LBU,
31
  OP_LHU,
32
+ OP_CSRRW,
33
+ OP_CSRRS,
34
+ OP_CSRRC,
35
  OP_LED
36
  );
37
 

We'll also add constants for the mandatory CSRs.

src/core/constants.vhd CHANGED
@@ -30,4 +30,41 @@ package core_constants is
30
  mem_size => SIZE_WORD,
31
  mem_addr => "00"
32
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  end package core_constants;
 
30
  mem_size => SIZE_WORD,
31
  mem_addr => "00"
32
  );
33
+
34
+ -- privileged CSRs
35
+ constant CSR_MVENDORID: std_logic_vector(11 downto 0) := X"F11";
36
+ constant CSR_MARCHID: std_logic_vector(11 downto 0) := X"F12";
37
+ constant CSR_MIMPID: std_logic_vector(11 downto 0) := X"F13";
38
+ constant CSR_MHARTID: std_logic_vector(11 downto 0) := X"F14";
39
+ constant CSR_MCONFIGPTR: std_logic_vector(11 downto 0) := X"F15";
40
+
41
+ constant CSR_MSTATUS: std_logic_vector(11 downto 0) := X"300";
42
+ constant CSR_MISA: std_logic_vector(11 downto 0) := X"301";
43
+ constant CSR_MIE: std_logic_vector(11 downto 0) := X"304";
44
+ constant CSR_MTVEC: std_logic_vector(11 downto 0) := X"305";
45
+ constant CSR_MSTATUSH: std_logic_vector(11 downto 0) := X"310";
46
+
47
+ constant CSR_MSCRATCH: std_logic_vector(11 downto 0) := X"340";
48
+ constant CSR_MEPC: std_logic_vector(11 downto 0) := X"341";
49
+ constant CSR_MCAUSE: std_logic_vector(11 downto 0) := X"342";
50
+ constant CSR_MTVAL: std_logic_vector(11 downto 0) := X"343";
51
+ constant CSR_MIP: std_logic_vector(11 downto 0) := X"344";
52
+
53
+ constant CSR_MCYCLE: std_logic_vector(11 downto 0) := X"B00";
54
+ constant CSR_MINSTRET: std_logic_vector(11 downto 0) := X"B02";
55
+ constant CSR_MHPMCOUNTER3: std_logic_vector(11 downto 0) := X"B03";
56
+ -- ...
57
+ constant CSR_MHPMCOUNTER31: std_logic_vector(11 downto 0) := X"B1F";
58
+ constant CSR_MCYCLEH: std_logic_vector(11 downto 0) := X"B80";
59
+ constant CSR_MINSTRETH: std_logic_vector(11 downto 0) := X"B82";
60
+ constant CSR_MHPMCOUNTER3H: std_logic_vector(11 downto 0) := X"B83";
61
+ -- ...
62
+ constant CSR_MHPMCOUNTER31H: std_logic_vector(11 downto 0) := X"B9F";
63
+
64
+ constant CSR_MHPMEVENT3: std_logic_vector(11 downto 0) := X"323";
65
+ -- ...
66
+ constant CSR_MHPMEVENT31: std_logic_vector(11 downto 0) := X"33f";
67
+ constant CSR_MHPMEVENT3H: std_logic_vector(11 downto 0) := X"723";
68
+ -- ...
69
+ constant CSR_MHPMEVENT31H: std_logic_vector(11 downto 0) := X"73f";
70
  end package core_constants;

To reduce the amount of duplicated code (and opportunity for bugs) a bit, I chose an implementation where the final value of a CSR is determined as

csr_new_value := (csr_value or set_bits) and clear_bits;
-- somehow make csr_new_value a legal value
csr_value <= csr_new_value

So the 1 bits in set_bits are set, and the 0 bits in clear_bits are cleared.

CSRRW can now be implemented by setting

set_bits := value;
clear_bits := value;

CSRRS as

set_bits := value;
clear_bits := (others => '0');

and CSRRC as

set_bits := (others => '0');
clear_bits := not value;
src/core/execute.vhd CHANGED
@@ -32,6 +32,8 @@ begin
32
  variable v_jump_address: std_logic_vector(31 downto 0);
33
  variable v_mem_req: mem_req_t;
34
 
 
 
35
  begin
36
  if rising_edge(clk) then
37
  v_output := DEFAULT_EXECUTE_OUTPUT;
@@ -194,6 +196,21 @@ begin
194
  else
195
  v_output.mem_size := SIZE_WORD;
196
  end if;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  elsif input.operation = OP_LED then
198
  led <= input.operand1(7 downto 0);
199
  else
 
32
  variable v_jump_address: std_logic_vector(31 downto 0);
33
  variable v_mem_req: mem_req_t;
34
 
35
+ variable csr_set_bits, csr_clear_bits: std_logic_vector(31 downto 0);
36
+
37
  begin
38
  if rising_edge(clk) then
39
  v_output := DEFAULT_EXECUTE_OUTPUT;
 
196
  else
197
  v_output.mem_size := SIZE_WORD;
198
  end if;
199
+ elsif input.operation = OP_CSRRW or input.operation = OP_CSRRS or input.operation = OP_CSRRC then
200
+ if input.operation = OP_CSRRW then
201
+ csr_set_bits := input.operand1;
202
+ csr_clear_bits := input.operand1;
203
+ elsif input.operation = OP_CSRRS then
204
+ csr_set_bits := input.operand1;
205
+ csr_clear_bits := (others => '1');
206
+ elsif input.operation = OP_CSRRC then
207
+ csr_clear_bits := not input.operand1;
208
+ else
209
+ assert false report "Unhandled CSR operation in execute stage" severity failure;
210
+ end if;
211
+
212
+ -- TODO: implementations for different registers
213
+
214
  elsif input.operation = OP_LED then
215
  led <= input.operand1(7 downto 0);
216
  else

Now, we are almost ready to start implementing CSRs. But first, a question: What does the RISC-V specification consider a write? The answer is:

  • Any CSRRW operations
  • CSRRS and CSRRC operations with rs set to x0
  • CSRRSI and CSRRCI operations with the immediate value set to 0

This means that CSRRS and CSRRC operations with a non-x0 register holding 0 are still considered writes, even though they don't change the value of the CSR. Annoyingly, this means we need to determine in the decode stage if the operation is a "read-only" operation. In the execute stage, we only have the value, and we need to know where the value came from in order to know if a write is performed (in case we might need to raise an exception).

src/core/constants.vhd CHANGED
@@ -18,7 +18,8 @@ package core_constants is
18
  operand1 => (others => '0'),
19
  operand2 => (others => '0'),
20
  operand3 => (others => '0'),
21
- destination_reg => (others => '0')
 
22
  );
23
 
24
  constant DEFAULT_EXECUTE_OUTPUT: execute_output_t := (
 
18
  operand1 => (others => '0'),
19
  operand2 => (others => '0'),
20
  operand3 => (others => '0'),
21
+ destination_reg => (others => '0'),
22
+ csr_read_only => '0'
23
  );
24
 
25
  constant DEFAULT_EXECUTE_OUTPUT: execute_output_t := (
src/core/decode_write.vhd CHANGED
@@ -314,10 +314,16 @@ begin
314
  -- CSRRS
315
  v_decode_output.operation := OP_CSRRS;
316
  v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
 
 
 
317
  elsif funct3 = "011" then
318
  -- CSRRC
319
  v_decode_output.operation := OP_CSRRC;
320
  v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
 
 
 
321
  elsif funct3 = "101" then
322
  -- CSRRWI
323
  v_decode_output.operation := OP_CSRRW;
@@ -326,10 +332,16 @@ begin
326
  -- CSRRSI
327
  v_decode_output.operation := OP_CSRRS;
328
  v_decode_output.operand1 := "000000000000000000000000000" & rs1;
 
 
 
329
  elsif funct3 = "111" then
330
  -- CSRRCI
331
  v_decode_output.operation := OP_CSRRC;
332
  v_decode_output.operand1 := "000000000000000000000000000" & rs1;
 
 
 
333
  else
334
  v_decode_output.is_invalid := '1';
335
  end if;
 
314
  -- CSRRS
315
  v_decode_output.operation := OP_CSRRS;
316
  v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
317
+ if rs1 = "00000" then
318
+ v_decode_output.csr_read_only := '1';
319
+ end if;
320
  elsif funct3 = "011" then
321
  -- CSRRC
322
  v_decode_output.operation := OP_CSRRC;
323
  v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
324
+ if rs1 = "00000" then
325
+ v_decode_output.csr_read_only := '1';
326
+ end if;
327
  elsif funct3 = "101" then
328
  -- CSRRWI
329
  v_decode_output.operation := OP_CSRRW;
 
332
  -- CSRRSI
333
  v_decode_output.operation := OP_CSRRS;
334
  v_decode_output.operand1 := "000000000000000000000000000" & rs1;
335
+ if rs1 = "00000" then
336
+ v_decode_output.csr_read_only := '1';
337
+ end if;
338
  elsif funct3 = "111" then
339
  -- CSRRCI
340
  v_decode_output.operation := OP_CSRRC;
341
  v_decode_output.operand1 := "000000000000000000000000000" & rs1;
342
+ if rs1 = "00000" then
343
+ v_decode_output.csr_read_only := '1';
344
+ end if;
345
  else
346
  v_decode_output.is_invalid := '1';
347
  end if;
src/core/types.vhd CHANGED
@@ -49,6 +49,7 @@ package core_types is
49
  operand2: std_logic_vector(31 downto 0);
50
  operand3: std_logic_vector(31 downto 0);
51
  destination_reg: std_logic_vector(4 downto 0);
 
52
  end record decode_output_t;
53
 
54
  type read_size_t is (SIZE_WORD, SIZE_HALFWORD, SIZE_BYTE);
 
49
  operand2: std_logic_vector(31 downto 0);
50
  operand3: std_logic_vector(31 downto 0);
51
  destination_reg: std_logic_vector(4 downto 0);
52
+ csr_read_only: std_logic;
53
  end record decode_output_t;
54
 
55
  type read_size_t is (SIZE_WORD, SIZE_HALFWORD, SIZE_BYTE);

The mvendorid, marchid, mimpid, mhartid, and mconfigptr CSRs can legally be set to zero. However, I do want to have the ability to easily fill them later, so I will define some constants for them and simply return those.

src/core/constants.vhd CHANGED
@@ -68,4 +68,11 @@ package core_constants is
68
  constant CSR_MHPMEVENT3H: std_logic_vector(11 downto 0) := X"723";
69
  -- ...
70
  constant CSR_MHPMEVENT31H: std_logic_vector(11 downto 0) := X"73f";
 
 
 
 
 
 
 
71
  end package core_constants;
 
68
  constant CSR_MHPMEVENT3H: std_logic_vector(11 downto 0) := X"723";
69
  -- ...
70
  constant CSR_MHPMEVENT31H: std_logic_vector(11 downto 0) := X"73f";
71
+
72
+ -- hardwired values for some CSRs
73
+ constant MVENDORID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
74
+ constant MARCHID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
75
+ constant MIMPID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
76
+ constant MHARTID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
77
+ constant MCONFIGPTR_VALUE: std_logic_vector(31 downto 0) := X"00000000";
78
  end package core_constants;
src/core/execute.vhd CHANGED
@@ -209,8 +209,26 @@ begin
209
  assert false report "Unhandled CSR operation in execute stage" severity failure;
210
  end if;
211
 
212
- -- TODO: implementations for different registers
213
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
  elsif input.operation = OP_LED then
215
  led <= input.operand1(7 downto 0);
216
  else
 
209
  assert false report "Unhandled CSR operation in execute stage" severity failure;
210
  end if;
211
 
212
+ -- TODO: implementations for CSR read-write registers
213
+
214
+ if input.csr_read_only = '1' then
215
+ -- read-only CSRs
216
+ if input.operand2(11 downto 0) = CSR_MVENDORID then
217
+ v_output.result := MVENDORID_VALUE;
218
+ elsif input.operand2(11 downto 0) = CSR_MARCHID then
219
+ v_output.result := MARCHID_VALUE;
220
+ elsif input.operand2(11 downto 0) = CSR_MIMPID then
221
+ v_output.result := MIMPID_VALUE;
222
+ elsif input.operand2(11 downto 0) = CSR_MHARTID then
223
+ v_output.result := MHARTID_VALUE;
224
+ elsif input.operand2(11 downto 0) = CSR_MCONFIGPTR then
225
+ v_output.result := MCONFIGPTR_VALUE;
226
+ else
227
+ -- TODO: exception; trying to read non-existent CSR
228
+ end if;
229
+ else
230
+ -- TODO: exception; trying to write to non-existent or read-only CSR
231
+ end if;
232
  elsif input.operation = OP_LED then
233
  led <= input.operand1(7 downto 0);
234
  else

Now, on to mstatus. This is a complicated CSR, but for a minimal implementation we only need the mie and mpie bits as read-write; most other bits/fields can be read-only zero. The only exception is mpp, which holds the previous privilige mode. Since our implementation only has the "machine" privilege mode, which corresponds to 11, so we hardcode mpp to 11.

src/core/execute.vhd CHANGED
@@ -23,6 +23,7 @@ end execute;
23
 
24
 
25
  architecture rtl of execute is
 
26
  begin
27
 
28
  process (clk)
@@ -211,7 +212,11 @@ begin
211
 
212
  -- TODO: implementations for CSR read-write registers
213
 
214
- if input.csr_read_only = '1' then
 
 
 
 
215
  -- read-only CSRs
216
  if input.operand2(11 downto 0) = CSR_MVENDORID then
217
  v_output.result := MVENDORID_VALUE;
 
23
 
24
 
25
  architecture rtl of execute is
26
+ signal mstatus_mpie, mstatus_mie: std_logic := '0';
27
  begin
28
 
29
  process (clk)
 
212
 
213
  -- TODO: implementations for CSR read-write registers
214
 
215
+ if input.operand2(11 downto 0) = CSR_MSTATUS then
216
+ v_output.result := "000000000000000000011000" & mstatus_mpie & "000" & mstatus_mie & "000";
217
+ mstatus_mie <= (mstatus_mie or csr_set_bits(3)) and csr_clear_bits(3);
218
+ mstatus_mpie <= (mstatus_mpie or csr_set_bits(7)) and csr_clear_bits(7);
219
+ elsif input.csr_read_only = '1' then
220
  -- read-only CSRs
221
  if input.operand2(11 downto 0) = CSR_MVENDORID then
222
  v_output.result := MVENDORID_VALUE;

On to misa. This CSR indicates what the bit width of the ISA is (32 or 64), and which extensions are active. It is a WARL CSR, meaning writes are allowed, but only legal values are returned on reading. The idea behind is that you can enable extensions by writing a one to the bit in misa that corresponds to that extension. If the bit stays zero, this means the implementation does not support the extension.

We are not implementing any extensions. For the RV32I subset, we return x40000100 (the encoding is explained in the manual in the section for the misa CSR). I load this value from a constant so that we can easily change it if we do implement more extensions later.

src/core/constants.vhd CHANGED
@@ -75,4 +75,6 @@ package core_constants is
75
  constant MIMPID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
76
  constant MHARTID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
77
  constant MCONFIGPTR_VALUE: std_logic_vector(31 downto 0) := X"00000000";
 
 
78
  end package core_constants;
 
75
  constant MIMPID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
76
  constant MHARTID_VALUE: std_logic_vector(31 downto 0) := X"00000000";
77
  constant MCONFIGPTR_VALUE: std_logic_vector(31 downto 0) := X"00000000";
78
+
79
+ constant MISA_VALUE: std_logic_vector(31 downto 0) := X"40000100"; -- 32-bit RVI
80
  end package core_constants;
src/core/execute.vhd CHANGED
@@ -216,6 +216,8 @@ begin
216
  v_output.result := "000000000000000000011000" & mstatus_mpie & "000" & mstatus_mie & "000";
217
  mstatus_mie <= (mstatus_mie or csr_set_bits(3)) and csr_clear_bits(3);
218
  mstatus_mpie <= (mstatus_mpie or csr_set_bits(7)) and csr_clear_bits(7);
 
 
219
  elsif input.csr_read_only = '1' then
220
  -- read-only CSRs
221
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
216
  v_output.result := "000000000000000000011000" & mstatus_mpie & "000" & mstatus_mie & "000";
217
  mstatus_mie <= (mstatus_mie or csr_set_bits(3)) and csr_clear_bits(3);
218
  mstatus_mpie <= (mstatus_mpie or csr_set_bits(7)) and csr_clear_bits(7);
219
+ elsif input.operand2(11 downto 0) = CSR_MISA then
220
+ v_output.result := MISA_VALUE;
221
  elsif input.csr_read_only = '1' then
222
  -- read-only CSRs
223
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mie CSR contains "interrupt enable" bits for all the interrupts. There are 16 interrupts mandated by RISC-V, spanning the lower 16 bits of mie. The rest of them are custom and for now I make them read-only zeros.

(Note that we don't implement the functionality of any CSRs yet, just the reading and writing of them.)

src/core/execute.vhd CHANGED
@@ -24,6 +24,7 @@ end execute;
24
 
25
  architecture rtl of execute is
26
  signal mstatus_mpie, mstatus_mie: std_logic := '0';
 
27
  begin
28
 
29
  process (clk)
@@ -218,6 +219,9 @@ begin
218
  mstatus_mpie <= (mstatus_mpie or csr_set_bits(7)) and csr_clear_bits(7);
219
  elsif input.operand2(11 downto 0) = CSR_MISA then
220
  v_output.result := MISA_VALUE;
 
 
 
221
  elsif input.csr_read_only = '1' then
222
  -- read-only CSRs
223
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
24
 
25
  architecture rtl of execute is
26
  signal mstatus_mpie, mstatus_mie: std_logic := '0';
27
+ signal mie: std_logic_vector(15 downto 0) := (others => '0');
28
  begin
29
 
30
  process (clk)
 
219
  mstatus_mpie <= (mstatus_mpie or csr_set_bits(7)) and csr_clear_bits(7);
220
  elsif input.operand2(11 downto 0) = CSR_MISA then
221
  v_output.result := MISA_VALUE;
222
+ elsif input.operand2(11 downto 0) = CSR_MIE then
223
+ v_output.result := x"0000" & mie;
224
+ mie <= (mie or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
225
  elsif input.csr_read_only = '1' then
226
  -- read-only CSRs
227
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mtvec CSR has to do with the address of the interrupts handler. The lower two bits define a "mode"; only 00 and 01 are allowed.

src/core/execute.vhd CHANGED
@@ -25,6 +25,8 @@ end execute;
25
  architecture rtl of execute is
26
  signal mstatus_mpie, mstatus_mie: std_logic := '0';
27
  signal mie: std_logic_vector(15 downto 0) := (others => '0');
 
 
28
  begin
29
 
30
  process (clk)
@@ -222,6 +224,10 @@ begin
222
  elsif input.operand2(11 downto 0) = CSR_MIE then
223
  v_output.result := x"0000" & mie;
224
  mie <= (mie or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
 
 
 
 
225
  elsif input.csr_read_only = '1' then
226
  -- read-only CSRs
227
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
25
  architecture rtl of execute is
26
  signal mstatus_mpie, mstatus_mie: std_logic := '0';
27
  signal mie: std_logic_vector(15 downto 0) := (others => '0');
28
+ signal mtvec_address: std_logic_vector(29 downto 0) := (others => '0');
29
+ signal mtvec_mode: std_logic := '0';
30
  begin
31
 
32
  process (clk)
 
224
  elsif input.operand2(11 downto 0) = CSR_MIE then
225
  v_output.result := x"0000" & mie;
226
  mie <= (mie or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
227
+ elsif input.operand2(11 downto 0) = CSR_MTVEC then
228
+ v_output.result := mtvec_address & "0" & mtvec_mode;
229
+ mtvec_address <= (mtvec_address or csr_set_bits(31 downto 2)) and csr_clear_bits(31 downto 2);
230
+ mtvec_mode <= (mtvec_mode or csr_set_bits(0)) and csr_clear_bits(0);
231
  elsif input.csr_read_only = '1' then
232
  -- read-only CSRs
233
  if input.operand2(11 downto 0) = CSR_MVENDORID then

For our implementation, the mstatush register can be read-only zero.

src/core/execute.vhd CHANGED
@@ -228,6 +228,8 @@ begin
228
  v_output.result := mtvec_address & "0" & mtvec_mode;
229
  mtvec_address <= (mtvec_address or csr_set_bits(31 downto 2)) and csr_clear_bits(31 downto 2);
230
  mtvec_mode <= (mtvec_mode or csr_set_bits(0)) and csr_clear_bits(0);
 
 
231
  elsif input.csr_read_only = '1' then
232
  -- read-only CSRs
233
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
228
  v_output.result := mtvec_address & "0" & mtvec_mode;
229
  mtvec_address <= (mtvec_address or csr_set_bits(31 downto 2)) and csr_clear_bits(31 downto 2);
230
  mtvec_mode <= (mtvec_mode or csr_set_bits(0)) and csr_clear_bits(0);
231
+ elsif input.operand2(11 downto 0) = CSR_MSTATUSH then
232
+ v_output.result := (others => '0');
233
  elsif input.csr_read_only = '1' then
234
  -- read-only CSRs
235
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mscratch CSR is a read-write scratch CSR that can be used for anything.

src/core/execute.vhd CHANGED
@@ -27,6 +27,7 @@ architecture rtl of execute is
27
  signal mie: std_logic_vector(15 downto 0) := (others => '0');
28
  signal mtvec_address: std_logic_vector(29 downto 0) := (others => '0');
29
  signal mtvec_mode: std_logic := '0';
 
30
  begin
31
 
32
  process (clk)
@@ -230,6 +231,9 @@ begin
230
  mtvec_mode <= (mtvec_mode or csr_set_bits(0)) and csr_clear_bits(0);
231
  elsif input.operand2(11 downto 0) = CSR_MSTATUSH then
232
  v_output.result := (others => '0');
 
 
 
233
  elsif input.csr_read_only = '1' then
234
  -- read-only CSRs
235
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
27
  signal mie: std_logic_vector(15 downto 0) := (others => '0');
28
  signal mtvec_address: std_logic_vector(29 downto 0) := (others => '0');
29
  signal mtvec_mode: std_logic := '0';
30
+ signal mscratch: std_logic_vector(31 downto 0) := (others => '0');
31
  begin
32
 
33
  process (clk)
 
231
  mtvec_mode <= (mtvec_mode or csr_set_bits(0)) and csr_clear_bits(0);
232
  elsif input.operand2(11 downto 0) = CSR_MSTATUSH then
233
  v_output.result := (others => '0');
234
+ elsif input.operand2(11 downto 0) = CSR_MSCRATCH then
235
+ v_output.result := mscratch;
236
+ mscratch <= (mscratch or csr_set_bits) and csr_clear_bits;
237
  elsif input.csr_read_only = '1' then
238
  -- read-only CSRs
239
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mepc CSR holds the address of an instruction that was being executed when a trap (exception or interrupt) occurred. Again, we don't implement that functionality now, only the reading and writing.

src/core/execute.vhd CHANGED
@@ -28,6 +28,7 @@ architecture rtl of execute is
28
  signal mtvec_address: std_logic_vector(29 downto 0) := (others => '0');
29
  signal mtvec_mode: std_logic := '0';
30
  signal mscratch: std_logic_vector(31 downto 0) := (others => '0');
 
31
  begin
32
 
33
  process (clk)
@@ -234,6 +235,9 @@ begin
234
  elsif input.operand2(11 downto 0) = CSR_MSCRATCH then
235
  v_output.result := mscratch;
236
  mscratch <= (mscratch or csr_set_bits) and csr_clear_bits;
 
 
 
237
  elsif input.csr_read_only = '1' then
238
  -- read-only CSRs
239
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
28
  signal mtvec_address: std_logic_vector(29 downto 0) := (others => '0');
29
  signal mtvec_mode: std_logic := '0';
30
  signal mscratch: std_logic_vector(31 downto 0) := (others => '0');
31
+ signal mepc: std_logic_vector(29 downto 0) := (others => '0');
32
  begin
33
 
34
  process (clk)
 
235
  elsif input.operand2(11 downto 0) = CSR_MSCRATCH then
236
  v_output.result := mscratch;
237
  mscratch <= (mscratch or csr_set_bits) and csr_clear_bits;
238
+ elsif input.operand2(11 downto 0) = CSR_MEPC then
239
+ v_output.result := mepc & "00";
240
+ mepc <= (mepc or csr_set_bits(31 downto 2)) and csr_clear_bits(31 downto 2);
241
  elsif input.csr_read_only = '1' then
242
  -- read-only CSRs
243
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mcause CSR is written when a trap happens and holds a code indicating the cause of the trap. The MSB holds a bit that indicates if the trap was an interrupt. Codes up to 64 are defined, so I just use 6 bits for the code.

src/core/execute.vhd CHANGED
@@ -29,6 +29,8 @@ architecture rtl of execute is
29
  signal mtvec_mode: std_logic := '0';
30
  signal mscratch: std_logic_vector(31 downto 0) := (others => '0');
31
  signal mepc: std_logic_vector(29 downto 0) := (others => '0');
 
 
32
  begin
33
 
34
  process (clk)
@@ -238,6 +240,10 @@ begin
238
  elsif input.operand2(11 downto 0) = CSR_MEPC then
239
  v_output.result := mepc & "00";
240
  mepc <= (mepc or csr_set_bits(31 downto 2)) and csr_clear_bits(31 downto 2);
 
 
 
 
241
  elsif input.csr_read_only = '1' then
242
  -- read-only CSRs
243
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
29
  signal mtvec_mode: std_logic := '0';
30
  signal mscratch: std_logic_vector(31 downto 0) := (others => '0');
31
  signal mepc: std_logic_vector(29 downto 0) := (others => '0');
32
+ signal mcause_int: std_logic := '0';
33
+ signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
34
  begin
35
 
36
  process (clk)
 
240
  elsif input.operand2(11 downto 0) = CSR_MEPC then
241
  v_output.result := mepc & "00";
242
  mepc <= (mepc or csr_set_bits(31 downto 2)) and csr_clear_bits(31 downto 2);
243
+ elsif input.operand2(11 downto 0) = CSR_MCAUSE then
244
+ v_output.result := mcause_int & "0000000000000000000000000" & mcause_code;
245
+ mcause_int <= (mcause_int or csr_set_bits(31)) and csr_clear_bits(31);
246
+ mcause_code <= (mcause_code or csr_set_bits(5 downto 0)) and csr_clear_bits(5 downto 0);
247
  elsif input.csr_read_only = '1' then
248
  -- read-only CSRs
249
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mtval CSR is written when a trap happens and contains extra information about the trap, when applicable. What exactly is in mtval is dependent on the trap. For example, for illegal memory accesses, mtval holds the address of the memory for which an access was attempted.

src/core/execute.vhd CHANGED
@@ -31,6 +31,7 @@ architecture rtl of execute is
31
  signal mepc: std_logic_vector(29 downto 0) := (others => '0');
32
  signal mcause_int: std_logic := '0';
33
  signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
 
34
  begin
35
 
36
  process (clk)
@@ -244,6 +245,9 @@ begin
244
  v_output.result := mcause_int & "0000000000000000000000000" & mcause_code;
245
  mcause_int <= (mcause_int or csr_set_bits(31)) and csr_clear_bits(31);
246
  mcause_code <= (mcause_code or csr_set_bits(5 downto 0)) and csr_clear_bits(5 downto 0);
 
 
 
247
  elsif input.csr_read_only = '1' then
248
  -- read-only CSRs
249
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
31
  signal mepc: std_logic_vector(29 downto 0) := (others => '0');
32
  signal mcause_int: std_logic := '0';
33
  signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
34
+ signal mtval: std_logic_vector(31 downto 0) := (others => '0');
35
  begin
36
 
37
  process (clk)
 
245
  v_output.result := mcause_int & "0000000000000000000000000" & mcause_code;
246
  mcause_int <= (mcause_int or csr_set_bits(31)) and csr_clear_bits(31);
247
  mcause_code <= (mcause_code or csr_set_bits(5 downto 0)) and csr_clear_bits(5 downto 0);
248
+ elsif input.operand2(11 downto 0) = CSR_MTVAL then
249
+ v_output.result := mtval;
250
+ mtval <= (mtval or csr_set_bits) and csr_clear_bits;
251
  elsif input.csr_read_only = '1' then
252
  -- read-only CSRs
253
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mip CSR is a lot like mie, but holds pending bits to indicate that interrupts are pending.

src/core/execute.vhd CHANGED
@@ -32,6 +32,7 @@ architecture rtl of execute is
32
  signal mcause_int: std_logic := '0';
33
  signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
34
  signal mtval: std_logic_vector(31 downto 0) := (others => '0');
 
35
  begin
36
 
37
  process (clk)
@@ -248,6 +249,9 @@ begin
248
  elsif input.operand2(11 downto 0) = CSR_MTVAL then
249
  v_output.result := mtval;
250
  mtval <= (mtval or csr_set_bits) and csr_clear_bits;
 
 
 
251
  elsif input.csr_read_only = '1' then
252
  -- read-only CSRs
253
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
32
  signal mcause_int: std_logic := '0';
33
  signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
34
  signal mtval: std_logic_vector(31 downto 0) := (others => '0');
35
+ signal mip: std_logic_vector(15 downto 0) := (others => '0');
36
  begin
37
 
38
  process (clk)
 
249
  elsif input.operand2(11 downto 0) = CSR_MTVAL then
250
  v_output.result := mtval;
251
  mtval <= (mtval or csr_set_bits) and csr_clear_bits;
252
+ elsif input.operand2(11 downto 0) = CSR_MIP then
253
+ v_output.result := x"0000" & mip;
254
+ mip <= (mip or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
255
  elsif input.csr_read_only = '1' then
256
  -- read-only CSRs
257
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mcycle CSR holds the number of cycles that the processor went through. The RISC-V specification implies you can also write to it (although it does a really poor job of actually specifying what should happen).

src/core/execute.vhd CHANGED
@@ -33,6 +33,7 @@ architecture rtl of execute is
33
  signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
34
  signal mtval: std_logic_vector(31 downto 0) := (others => '0');
35
  signal mip: std_logic_vector(15 downto 0) := (others => '0');
 
36
  begin
37
 
38
  process (clk)
@@ -252,6 +253,9 @@ begin
252
  elsif input.operand2(11 downto 0) = CSR_MIP then
253
  v_output.result := x"0000" & mip;
254
  mip <= (mip or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
 
 
 
255
  elsif input.csr_read_only = '1' then
256
  -- read-only CSRs
257
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
33
  signal mcause_code: std_logic_vector(5 downto 0) := (others => '0');
34
  signal mtval: std_logic_vector(31 downto 0) := (others => '0');
35
  signal mip: std_logic_vector(15 downto 0) := (others => '0');
36
+ signal mcycle: std_logic_vector(31 downto 0) := (others => '0');
37
  begin
38
 
39
  process (clk)
 
253
  elsif input.operand2(11 downto 0) = CSR_MIP then
254
  v_output.result := x"0000" & mip;
255
  mip <= (mip or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
256
+ elsif input.operand2(11 downto 0) = CSR_MCYCLE then
257
+ v_output.result := mcycle;
258
+ mcycle <= (mcycle or csr_set_bits) and csr_clear_bits;
259
  elsif input.csr_read_only = '1' then
260
  -- read-only CSRs
261
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The minstret CSR is very similar but counts retired instructions.

src/core/execute.vhd CHANGED
@@ -34,6 +34,7 @@ architecture rtl of execute is
34
  signal mtval: std_logic_vector(31 downto 0) := (others => '0');
35
  signal mip: std_logic_vector(15 downto 0) := (others => '0');
36
  signal mcycle: std_logic_vector(31 downto 0) := (others => '0');
 
37
  begin
38
 
39
  process (clk)
@@ -256,6 +257,9 @@ begin
256
  elsif input.operand2(11 downto 0) = CSR_MCYCLE then
257
  v_output.result := mcycle;
258
  mcycle <= (mcycle or csr_set_bits) and csr_clear_bits;
 
 
 
259
  elsif input.csr_read_only = '1' then
260
  -- read-only CSRs
261
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
34
  signal mtval: std_logic_vector(31 downto 0) := (others => '0');
35
  signal mip: std_logic_vector(15 downto 0) := (others => '0');
36
  signal mcycle: std_logic_vector(31 downto 0) := (others => '0');
37
+ signal minstret: std_logic_vector(31 downto 0) := (others => '0');
38
  begin
39
 
40
  process (clk)
 
257
  elsif input.operand2(11 downto 0) = CSR_MCYCLE then
258
  v_output.result := mcycle;
259
  mcycle <= (mcycle or csr_set_bits) and csr_clear_bits;
260
+ elsif input.operand2(11 downto 0) = CSR_MINSTRET then
261
+ v_output.result := minstret;
262
+ minstret <= (minstret or csr_set_bits) and csr_clear_bits;
263
  elsif input.csr_read_only = '1' then
264
  -- read-only CSRs
265
  if input.operand2(11 downto 0) = CSR_MVENDORID then

There are 29 counters, mhpmcounter3 up to mhpmcounter31, which are mandatory to implement, but it's legal to implement them as read-only zero.

src/core/execute.vhd CHANGED
@@ -260,6 +260,8 @@ begin
260
  elsif input.operand2(11 downto 0) = CSR_MINSTRET then
261
  v_output.result := minstret;
262
  minstret <= (minstret or csr_set_bits) and csr_clear_bits;
 
 
263
  elsif input.csr_read_only = '1' then
264
  -- read-only CSRs
265
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
260
  elsif input.operand2(11 downto 0) = CSR_MINSTRET then
261
  v_output.result := minstret;
262
  minstret <= (minstret or csr_set_bits) and csr_clear_bits;
263
+ elsif unsigned(CSR_MHPMCOUNTER3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31) then
264
+ v_output.result := (others => '0');
265
  elsif input.csr_read_only = '1' then
266
  -- read-only CSRs
267
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mcycleh and minstreth CSRs are like the mcycle and minstret CSRs but hold the upper 32 bits.

src/core/execute.vhd CHANGED
@@ -35,6 +35,8 @@ architecture rtl of execute is
35
  signal mip: std_logic_vector(15 downto 0) := (others => '0');
36
  signal mcycle: std_logic_vector(31 downto 0) := (others => '0');
37
  signal minstret: std_logic_vector(31 downto 0) := (others => '0');
 
 
38
  begin
39
 
40
  process (clk)
@@ -262,6 +264,12 @@ begin
262
  minstret <= (minstret or csr_set_bits) and csr_clear_bits;
263
  elsif unsigned(CSR_MHPMCOUNTER3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31) then
264
  v_output.result := (others => '0');
 
 
 
 
 
 
265
  elsif input.csr_read_only = '1' then
266
  -- read-only CSRs
267
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
35
  signal mip: std_logic_vector(15 downto 0) := (others => '0');
36
  signal mcycle: std_logic_vector(31 downto 0) := (others => '0');
37
  signal minstret: std_logic_vector(31 downto 0) := (others => '0');
38
+ signal mcycleh: std_logic_vector(31 downto 0) := (others => '0');
39
+ signal minstreth: std_logic_vector(31 downto 0) := (others => '0');
40
  begin
41
 
42
  process (clk)
 
264
  minstret <= (minstret or csr_set_bits) and csr_clear_bits;
265
  elsif unsigned(CSR_MHPMCOUNTER3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31) then
266
  v_output.result := (others => '0');
267
+ elsif input.operand2(11 downto 0) = CSR_MCYCLEH then
268
+ v_output.result := mcycleh;
269
+ mcycleh <= (mcycleh or csr_set_bits) and csr_clear_bits;
270
+ elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
271
+ v_output.result := minstreth;
272
+ minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
273
  elsif input.csr_read_only = '1' then
274
  -- read-only CSRs
275
  if input.operand2(11 downto 0) = CSR_MVENDORID then

The mphmcounters also have variants that hold the higher bits.

src/core/execute.vhd CHANGED
@@ -270,6 +270,8 @@ begin
270
  elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
271
  v_output.result := minstreth;
272
  minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
 
 
273
  elsif input.csr_read_only = '1' then
274
  -- read-only CSRs
275
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
270
  elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
271
  v_output.result := minstreth;
272
  minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
273
+ elsif unsigned(CSR_MHPMCOUNTER3H) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31H) then
274
+ v_output.result := (others => '0');
275
  elsif input.csr_read_only = '1' then
276
  -- read-only CSRs
277
  if input.operand2(11 downto 0) = CSR_MVENDORID then

Finally, we have mhpmevent and mhpmeventh counters, which can be read-only zero.

src/core/execute.vhd CHANGED
@@ -272,6 +272,10 @@ begin
272
  minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
273
  elsif unsigned(CSR_MHPMCOUNTER3H) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31H) then
274
  v_output.result := (others => '0');
 
 
 
 
275
  elsif input.csr_read_only = '1' then
276
  -- read-only CSRs
277
  if input.operand2(11 downto 0) = CSR_MVENDORID then
 
272
  minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
273
  elsif unsigned(CSR_MHPMCOUNTER3H) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31H) then
274
  v_output.result := (others => '0');
275
+ elsif unsigned(CSR_MHPMEVENT3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMEVENT31) then
276
+ v_output.result := (others => '0');
277
+ elsif unsigned(CSR_MHPMEVENT3H) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMEVENT31H) then
278
+ v_output.result := (others => '0');
279
  elsif input.csr_read_only = '1' then
280
  -- read-only CSRs
281
  if input.operand2(11 downto 0) = CSR_MVENDORID then

Now, we have defined most CSRs that need to exist. Most of them are either read-only registers, or have to do with interrupts, and we'll look at them later. Exceptions are mcycle (and mcycleh) and minstret (and minstreth). These are incrementing counters, but we need to overwrite them when writing to them. This is most easily implemented by using variables.

src/core/execute.vhd CHANGED
@@ -45,8 +45,10 @@ begin
45
  variable v_jump: std_logic;
46
  variable v_jump_address: std_logic_vector(31 downto 0);
47
  variable v_mem_req: mem_req_t;
 
48
 
49
  variable csr_set_bits, csr_clear_bits: std_logic_vector(31 downto 0);
 
50
 
51
  begin
52
  if rising_edge(clk) then
@@ -56,6 +58,10 @@ begin
56
  v_jump := '0';
57
  v_jump_address := (others => '0');
58
 
 
 
 
 
59
  if input.is_active = '1' and input.is_invalid = '0' then
60
  if input.operation = OP_ADD then
61
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
@@ -258,7 +264,7 @@ begin
258
  mip <= (mip or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
259
  elsif input.operand2(11 downto 0) = CSR_MCYCLE then
260
  v_output.result := mcycle;
261
- mcycle <= (mcycle or csr_set_bits) and csr_clear_bits;
262
  elsif input.operand2(11 downto 0) = CSR_MINSTRET then
263
  v_output.result := minstret;
264
  minstret <= (minstret or csr_set_bits) and csr_clear_bits;
@@ -266,7 +272,7 @@ begin
266
  v_output.result := (others => '0');
267
  elsif input.operand2(11 downto 0) = CSR_MCYCLEH then
268
  v_output.result := mcycleh;
269
- mcycleh <= (mcycleh or csr_set_bits) and csr_clear_bits;
270
  elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
271
  v_output.result := minstreth;
272
  minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
@@ -309,6 +315,9 @@ begin
309
 
310
  jump <= v_jump;
311
  jump_address <= v_jump_address(31 downto 1) & "0";
 
 
 
312
  end if;
313
  end process;
314
 
 
45
  variable v_jump: std_logic;
46
  variable v_jump_address: std_logic_vector(31 downto 0);
47
  variable v_mem_req: mem_req_t;
48
+ variable v_mcycle_next, v_mcycleh_next: std_logic_vector(31 downto 0);
49
 
50
  variable csr_set_bits, csr_clear_bits: std_logic_vector(31 downto 0);
51
+ variable v_temp: unsigned(63 downto 0);
52
 
53
  begin
54
  if rising_edge(clk) then
 
58
  v_jump := '0';
59
  v_jump_address := (others => '0');
60
 
61
+ v_temp := unsigned(mcycleh & mcycle) + 1;
62
+ v_mcycle_next := std_logic_vector(v_temp(31 downto 0));
63
+ v_mcycleh_next := std_logic_vector(v_temp(63 downto 32));
64
+
65
  if input.is_active = '1' and input.is_invalid = '0' then
66
  if input.operation = OP_ADD then
67
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
 
264
  mip <= (mip or csr_set_bits(15 downto 0)) and csr_clear_bits(15 downto 0);
265
  elsif input.operand2(11 downto 0) = CSR_MCYCLE then
266
  v_output.result := mcycle;
267
+ v_mcycle_next := (mcycle or csr_set_bits) and csr_clear_bits;
268
  elsif input.operand2(11 downto 0) = CSR_MINSTRET then
269
  v_output.result := minstret;
270
  minstret <= (minstret or csr_set_bits) and csr_clear_bits;
 
272
  v_output.result := (others => '0');
273
  elsif input.operand2(11 downto 0) = CSR_MCYCLEH then
274
  v_output.result := mcycleh;
275
+ v_mcycleh_next := (mcycleh or csr_set_bits) and csr_clear_bits;
276
  elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
277
  v_output.result := minstreth;
278
  minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
 
315
 
316
  jump <= v_jump;
317
  jump_address <= v_jump_address(31 downto 1) & "0";
318
+
319
+ mcycle <= v_mcycle_next;
320
+ mcycleh <= v_mcycleh_next;
321
  end if;
322
  end process;
323
 

For minstret and minstreth the approach is similar, but we need to know when an instruction retires. I implement this by connecting the pipeline_ready signal from the writeback stage to the execute stage, and only incrementing the retired instruction count when this signal is high.

src/core.vhd CHANGED
@@ -57,6 +57,7 @@ architecture rtl of core is
57
  port (
58
  clk: in std_logic;
59
  input: in decode_output_t;
 
60
  output: out execute_output_t;
61
  mem_req: out mem_req_t;
62
  jump: out std_logic := '0';
@@ -78,7 +79,7 @@ begin
78
 
79
  decode_write_inst: decode_write port map(clk => clk, decode_input => fetch_output, decode_output => decode_output, write_input => memory_output, mem_res => mem_res_1, pipeline_ready => pipeline_ready);
80
 
81
- execute_inst: execute port map(clk => clk, input => decode_output, output => execute_output, mem_req => mem_req_1, jump => jump, jump_address => jump_address, led => led);
82
 
83
  memory_inst: memory port map(clk => clk, input => execute_output, output => memory_output);
84
 
 
57
  port (
58
  clk: in std_logic;
59
  input: in decode_output_t;
60
+ instr_retire: in std_logic;
61
  output: out execute_output_t;
62
  mem_req: out mem_req_t;
63
  jump: out std_logic := '0';
 
79
 
80
  decode_write_inst: decode_write port map(clk => clk, decode_input => fetch_output, decode_output => decode_output, write_input => memory_output, mem_res => mem_res_1, pipeline_ready => pipeline_ready);
81
 
82
+ execute_inst: execute port map(clk => clk, input => decode_output, instr_retire => pipeline_ready, output => execute_output, mem_req => mem_req_1, jump => jump, jump_address => jump_address, led => led);
83
 
84
  memory_inst: memory port map(clk => clk, input => execute_output, output => memory_output);
85
 
src/core/execute.vhd CHANGED
@@ -13,6 +13,7 @@ entity execute is
13
  port (
14
  clk: in std_logic;
15
  input: in decode_output_t;
 
16
  output: out execute_output_t := DEFAULT_EXECUTE_OUTPUT;
17
  mem_req: out mem_req_t := DEFAULT_MEM_REQ;
18
  jump: out std_logic := '0';
@@ -46,6 +47,7 @@ begin
46
  variable v_jump_address: std_logic_vector(31 downto 0);
47
  variable v_mem_req: mem_req_t;
48
  variable v_mcycle_next, v_mcycleh_next: std_logic_vector(31 downto 0);
 
49
 
50
  variable csr_set_bits, csr_clear_bits: std_logic_vector(31 downto 0);
51
  variable v_temp: unsigned(63 downto 0);
@@ -62,6 +64,14 @@ begin
62
  v_mcycle_next := std_logic_vector(v_temp(31 downto 0));
63
  v_mcycleh_next := std_logic_vector(v_temp(63 downto 32));
64
 
 
 
 
 
 
 
 
 
65
  if input.is_active = '1' and input.is_invalid = '0' then
66
  if input.operation = OP_ADD then
67
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
@@ -267,7 +277,7 @@ begin
267
  v_mcycle_next := (mcycle or csr_set_bits) and csr_clear_bits;
268
  elsif input.operand2(11 downto 0) = CSR_MINSTRET then
269
  v_output.result := minstret;
270
- minstret <= (minstret or csr_set_bits) and csr_clear_bits;
271
  elsif unsigned(CSR_MHPMCOUNTER3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31) then
272
  v_output.result := (others => '0');
273
  elsif input.operand2(11 downto 0) = CSR_MCYCLEH then
@@ -275,7 +285,7 @@ begin
275
  v_mcycleh_next := (mcycleh or csr_set_bits) and csr_clear_bits;
276
  elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
277
  v_output.result := minstreth;
278
- minstreth <= (minstreth or csr_set_bits) and csr_clear_bits;
279
  elsif unsigned(CSR_MHPMCOUNTER3H) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31H) then
280
  v_output.result := (others => '0');
281
  elsif unsigned(CSR_MHPMEVENT3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMEVENT31) then
@@ -318,6 +328,9 @@ begin
318
 
319
  mcycle <= v_mcycle_next;
320
  mcycleh <= v_mcycleh_next;
 
 
 
321
  end if;
322
  end process;
323
 
 
13
  port (
14
  clk: in std_logic;
15
  input: in decode_output_t;
16
+ instr_retire: in std_logic;
17
  output: out execute_output_t := DEFAULT_EXECUTE_OUTPUT;
18
  mem_req: out mem_req_t := DEFAULT_MEM_REQ;
19
  jump: out std_logic := '0';
 
47
  variable v_jump_address: std_logic_vector(31 downto 0);
48
  variable v_mem_req: mem_req_t;
49
  variable v_mcycle_next, v_mcycleh_next: std_logic_vector(31 downto 0);
50
+ variable v_minstret_next, v_minstreth_next: std_logic_vector(31 downto 0);
51
 
52
  variable csr_set_bits, csr_clear_bits: std_logic_vector(31 downto 0);
53
  variable v_temp: unsigned(63 downto 0);
 
64
  v_mcycle_next := std_logic_vector(v_temp(31 downto 0));
65
  v_mcycleh_next := std_logic_vector(v_temp(63 downto 32));
66
 
67
+ v_temp := unsigned(minstreth & minstret);
68
+ if instr_retire = '1' then
69
+ v_temp := v_temp + 1;
70
+ end if;
71
+
72
+ v_minstret_next := std_logic_vector(v_temp(31 downto 0));
73
+ v_minstreth_next := std_logic_vector(v_temp(63 downto 32));
74
+
75
  if input.is_active = '1' and input.is_invalid = '0' then
76
  if input.operation = OP_ADD then
77
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
 
277
  v_mcycle_next := (mcycle or csr_set_bits) and csr_clear_bits;
278
  elsif input.operand2(11 downto 0) = CSR_MINSTRET then
279
  v_output.result := minstret;
280
+ v_minstret_next := (minstret or csr_set_bits) and csr_clear_bits;
281
  elsif unsigned(CSR_MHPMCOUNTER3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31) then
282
  v_output.result := (others => '0');
283
  elsif input.operand2(11 downto 0) = CSR_MCYCLEH then
 
285
  v_mcycleh_next := (mcycleh or csr_set_bits) and csr_clear_bits;
286
  elsif input.operand2(11 downto 0) = CSR_MINSTRETH then
287
  v_output.result := minstreth;
288
+ v_minstreth_next := (minstreth or csr_set_bits) and csr_clear_bits;
289
  elsif unsigned(CSR_MHPMCOUNTER3H) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMCOUNTER31H) then
290
  v_output.result := (others => '0');
291
  elsif unsigned(CSR_MHPMEVENT3) <= unsigned(input.operand2(11 downto 0)) and unsigned(input.operand2(11 downto 0)) <= unsigned(CSR_MHPMEVENT31) then
 
328
 
329
  mcycle <= v_mcycle_next;
330
  mcycleh <= v_mcycleh_next;
331
+
332
+ minstret <= v_minstret_next;
333
+ minstreth <= v_minstreth_next;
334
  end if;
335
  end process;
336