This lesson starts at commit 52ad976f12300a37998296b9861bfa7bccabac4a.

6. Implementing more instructions

After the hopefully fun diversion in the last lesson, we are back to work. There are 40-ish instructions in the RISC-V base instruction set, and we have only implemented two so far. Let's bang out some more instructions!

Let's grab the RV32/64G Instruction Set Listings of the RISC-V docs.

First, let's just add variables for all the bit fields we don't have yet.

src/core/decode_write.vhd CHANGED
@@ -31,8 +31,14 @@ begin
31
  variable funct7: std_logic_vector(6 downto 0);
32
  variable rs1, rs2, rd : std_logic_vector(4 downto 0);
33
 
 
 
34
  variable i_imm: std_logic_vector(11 downto 0);
35
  variable i_imm_s: std_logic_vector(31 downto 0);
 
 
 
 
36
 
37
  variable v_decode_output: decode_output_t;
38
  begin
@@ -51,7 +57,14 @@ begin
51
  funct7 := decode_input.instr(31 downto 25);
52
  rd := decode_input.instr(11 downto 7);
53
 
 
54
  i_imm := decode_input.instr(31 downto 20);
 
 
 
 
 
 
55
  i_imm_s := std_logic_vector(resize(signed(i_imm), 32));
56
 
57
  v_decode_output := DEFAULT_DECODE_OUTPUT;
 
31
  variable funct7: std_logic_vector(6 downto 0);
32
  variable rs1, rs2, rd : std_logic_vector(4 downto 0);
33
 
34
+ variable b_imm: std_logic_vector(12 downto 0);
35
+ variable b_imm_s: std_logic_vector(31 downto 0);
36
  variable i_imm: std_logic_vector(11 downto 0);
37
  variable i_imm_s: std_logic_vector(31 downto 0);
38
+ variable j_imm: std_logic_vector(20 downto 0);
39
+ variable j_imm_s: std_logic_vector(31 downto 0);
40
+ variable s_imm: std_logic_vector(11 downto 0);
41
+ variable u_imm: std_logic_vector(31 downto 0);
42
 
43
  variable v_decode_output: decode_output_t;
44
  begin
 
57
  funct7 := decode_input.instr(31 downto 25);
58
  rd := decode_input.instr(11 downto 7);
59
 
60
+ b_imm := decode_input.instr(31) & decode_input.instr(7) & decode_input.instr(30 downto 25) & decode_input.instr(11 downto 8) & "0";
61
  i_imm := decode_input.instr(31 downto 20);
62
+ j_imm := decode_input.instr(31) & decode_input.instr(19 downto 12) & decode_input.instr(20) & decode_input.instr(30 downto 21) & "0";
63
+ s_imm := decode_input.instr(31 downto 25) & decode_input.instr(11 downto 7);
64
+ u_imm := decode_input.instr(31 downto 12) & "000000000000";
65
+
66
+ -- sign extension
67
+ b_imm_s := std_logic_vector(resize(signed(b_imm), 32));
68
  i_imm_s := std_logic_vector(resize(signed(i_imm), 32));
69
 
70
  v_decode_output := DEFAULT_DECODE_OUTPUT;

Now, let's remove the decoding logic we currently have and just start implementing the instructions from the RV32/64G Instruction Set Listings.

src/core/decode_write.vhd CHANGED
@@ -73,30 +73,7 @@ begin
73
  v_decode_output.is_active := '1';
74
  v_decode_output.is_invalid := '0';
75
 
76
- if opcode = "0010011" and funct3 = "000" then
77
- -- ADDI rd, rs, imm (I-type): sets rd to the sum of rs1 and the sign-extended immediate
78
- v_decode_output.operation := OP_ADD;
79
- v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
80
- v_decode_output.operand2 := i_imm_s;
81
- v_decode_output.destination_reg := rd;
82
- elsif opcode = "0110011" and funct3 = "000" and funct7 = "0000000" then
83
- -- ADD rd, rs1, rs2 (R-type): sets rd to the sum of rs1 and rs2
84
- v_decode_output.operation := OP_ADD;
85
- v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
86
- v_decode_output.operand2 := reg(to_integer(unsigned(rs2)));
87
- v_decode_output.destination_reg := rd;
88
- elsif opcode = "1111111" and funct3 = "000" then
89
- -- LED rs1: set the LEDs to the 8 least significant bits of rs1
90
- v_decode_output.operation := OP_LED;
91
- v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
92
- v_decode_output.operand2 := (others => '0');
93
- v_decode_output.destination_reg := (others => '0');
94
- elsif opcode = "1111111" and funct3 = "001" then
95
- -- HANG
96
- v_decode_output := DEFAULT_DECODE_OUTPUT;
97
- else
98
- v_decode_output.is_invalid := '1';
99
- end if;
100
  else
101
  decode_output <= DEFAULT_DECODE_OUTPUT;
102
  end if;
 
73
  v_decode_output.is_active := '1';
74
  v_decode_output.is_invalid := '0';
75
 
76
+ -- TODO: implement instruction decoding
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  else
78
  decode_output <= DEFAULT_DECODE_OUTPUT;
79
  end if;

So we start with LUI and AUIPC. In Chapter 2 (RV32I Base Integer Instruction Set) of the RISC-V unprivileged architecture document, we find this:

Descriptions and bits for LUI and AUIPC instructions

In chapter 35, we see that the opcode field is 0110111 for LUI and 0010111 for AUIPC.

src/core/decode_write.vhd CHANGED
@@ -73,7 +73,13 @@ begin
73
  v_decode_output.is_active := '1';
74
  v_decode_output.is_invalid := '0';
75
 
76
- -- TODO: implement instruction decoding
 
 
 
 
 
 
77
  else
78
  decode_output <= DEFAULT_DECODE_OUTPUT;
79
  end if;
 
73
  v_decode_output.is_active := '1';
74
  v_decode_output.is_invalid := '0';
75
 
76
+ if opcode = "0110111" then
77
+ -- TODO: LUI
78
+ elsif opcode = "0010111" then
79
+ -- TODO: AUIPC
80
+ else
81
+ v_decode_output.is_invalid := '1';
82
+ end if;
83
  else
84
  decode_output <= DEFAULT_DECODE_OUTPUT;
85
  end if;

So, we start with LUI. We need to place the immediate value in the rd register. This is easy enough with the existing infrastructure.

src/core/decode_write.vhd CHANGED
@@ -74,7 +74,11 @@ begin
74
  v_decode_output.is_invalid := '0';
75
 
76
  if opcode = "0110111" then
77
- -- TODO: LUI
 
 
 
 
78
  elsif opcode = "0010111" then
79
  -- TODO: AUIPC
80
  else
 
74
  v_decode_output.is_invalid := '0';
75
 
76
  if opcode = "0110111" then
77
+ -- LUI
78
+ v_decode_output.operation := OP_ADD;
79
+ v_decode_output.operand1 := (others => '0');
80
+ v_decode_output.operand2 := u_imm;
81
+ v_decode_output.destination_reg := rd;
82
  elsif opcode = "0010111" then
83
  -- TODO: AUIPC
84
  else

AUIPC is similar, but the it adds the immediate to the address of the instruction. This address is in the pc register in the fetch stage, but not yet available in the decode stage, so we need to pass it in the output of the fetch stage.

src/core/constants.vhd CHANGED
@@ -7,7 +7,8 @@ use work.core_types.all;
7
  package core_constants is
8
  constant DEFAULT_FETCH_OUTPUT: fetch_output_t := (
9
  is_active => '0',
10
- instr => (others => '0')
 
11
  );
12
 
13
  constant DEFAULT_DECODE_OUTPUT: decode_output_t := (
 
7
  package core_constants is
8
  constant DEFAULT_FETCH_OUTPUT: fetch_output_t := (
9
  is_active => '0',
10
+ instr => (others => '0'),
11
+ pc => (others => '0')
12
  );
13
 
14
  constant DEFAULT_DECODE_OUTPUT: decode_output_t := (
src/core/fetch.vhd CHANGED
@@ -34,6 +34,7 @@ begin
34
 
35
  output.is_active <= '1';
36
  output.instr <= imem(to_integer(pc(5 downto 2)));
 
37
  else
38
  output <= DEFAULT_FETCH_OUTPUT;
39
  end if;
 
34
 
35
  output.is_active <= '1';
36
  output.instr <= imem(to_integer(pc(5 downto 2)));
37
+ output.pc <= std_logic_vector(pc);
38
  else
39
  output <= DEFAULT_FETCH_OUTPUT;
40
  end if;
src/core/types.vhd CHANGED
@@ -8,6 +8,7 @@ package core_types is
8
  type fetch_output_t is record
9
  is_active: std_logic;
10
  instr: std_logic_vector(31 downto 0);
 
11
  end record fetch_output_t;
12
 
13
  type decode_output_t is record
 
8
  type fetch_output_t is record
9
  is_active: std_logic;
10
  instr: std_logic_vector(31 downto 0);
11
+ pc: std_logic_vector(31 downto 0);
12
  end record fetch_output_t;
13
 
14
  type decode_output_t is record

Now, we can implement AUIPC similar to LUI in the decode stage.

src/core/decode_write.vhd CHANGED
@@ -80,7 +80,11 @@ begin
80
  v_decode_output.operand2 := u_imm;
81
  v_decode_output.destination_reg := rd;
82
  elsif opcode = "0010111" then
83
- -- TODO: AUIPC
 
 
 
 
84
  else
85
  v_decode_output.is_invalid := '1';
86
  end if;
 
80
  v_decode_output.operand2 := u_imm;
81
  v_decode_output.destination_reg := rd;
82
  elsif opcode = "0010111" then
83
+ -- AUIPC
84
+ v_decode_output.operation := OP_ADD;
85
+ v_decode_output.operand1 := decode_input.pc;
86
+ v_decode_output.operand2 := u_imm;
87
+ v_decode_output.destination_reg := rd;
88
  else
89
  v_decode_output.is_invalid := '1';
90
  end if;

Now, JAL and JALR are control flow instructions, which mean we'll have to somehow change the value of the pc register in the fetch stage. This requires changes in at least 3 modules as well as some testing. This is a bit too much work for this lesson; we'll just work on the "low-hanging fruit" now, and will return to the instructions that require more work later.

src/core/decode_write.vhd CHANGED
@@ -85,6 +85,10 @@ begin
85
  v_decode_output.operand1 := decode_input.pc;
86
  v_decode_output.operand2 := u_imm;
87
  v_decode_output.destination_reg := rd;
 
 
 
 
88
  else
89
  v_decode_output.is_invalid := '1';
90
  end if;
 
85
  v_decode_output.operand1 := decode_input.pc;
86
  v_decode_output.operand2 := u_imm;
87
  v_decode_output.destination_reg := rd;
88
+ elsif opcode = "1101111" then
89
+ -- TODO: JAL
90
+ elsif opcode = "1100111" and funct3 = "000" then
91
+ -- TODO: JALR
92
  else
93
  v_decode_output.is_invalid := '1';
94
  end if;

Now, BEQ, BNE, BLT, BGE, BLTU, and BGEU are control flow instructions as well, so we skip them too.

src/core/decode_write.vhd CHANGED
@@ -89,6 +89,22 @@ begin
89
  -- TODO: JAL
90
  elsif opcode = "1100111" and funct3 = "000" then
91
  -- TODO: JALR
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  else
93
  v_decode_output.is_invalid := '1';
94
  end if;
 
89
  -- TODO: JAL
90
  elsif opcode = "1100111" and funct3 = "000" then
91
  -- TODO: JALR
92
+ elsif opcode = "1100011" then
93
+ if funct3 = "000" then
94
+ -- TODO: BEQ
95
+ elsif funct3 = "001" then
96
+ -- TODO: BNE
97
+ elsif funct3 = "100" then
98
+ -- TODO: BLT
99
+ elsif funct3 = "101" then
100
+ -- TODO: BGE
101
+ elsif funct3 = "110" then
102
+ -- TODO: BLTU
103
+ elsif funct3 = "111" then
104
+ -- TODO: BGEU
105
+ else
106
+ v_decode_output.is_invalid := '1';
107
+ end if;
108
  else
109
  v_decode_output.is_invalid := '1';
110
  end if;

We'll also skip LB, LH, LW, LBU, LHU, SB, SH, SW because these are memory operations and we haven't implemented memory yet. So far, this is going excellent.

src/core/decode_write.vhd CHANGED
@@ -105,6 +105,30 @@ begin
105
  else
106
  v_decode_output.is_invalid := '1';
107
  end if;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  else
109
  v_decode_output.is_invalid := '1';
110
  end if;
 
105
  else
106
  v_decode_output.is_invalid := '1';
107
  end if;
108
+ elsif opcode = "0000011" then
109
+ if funct3 = "000" then
110
+ -- TODO: LB
111
+ elsif funct3 = "001" then
112
+ -- TODO: LH
113
+ elsif funct3 = "010" then
114
+ -- TODO: LW
115
+ elsif funct3 = "100" then
116
+ -- TODO: LBU
117
+ elsif funct3 = "101" then
118
+ -- TODO: LHU
119
+ else
120
+ v_decode_output.is_invalid := '1';
121
+ end if;
122
+ elsif opcode = "0100011" then
123
+ if funct3 = "000" then
124
+ -- TODO: SB
125
+ elsif funct3 = "001" then
126
+ -- TODO: SH
127
+ elsif funct3 = "010" then
128
+ -- TODO: SW
129
+ else
130
+ v_decode_output.is_invalid := '1';
131
+ end if;
132
  else
133
  v_decode_output.is_invalid := '1';
134
  end if;

Now, we arrive at a bunch of instructions that have the same opcode (but a different value for the funct3 field): ADDI, SLTI, SLTIU, XORI, ORI, ANDI.

First, we want to recognize these instructions.

src/core/decode_write.vhd CHANGED
@@ -129,6 +129,22 @@ begin
129
  else
130
  v_decode_output.is_invalid := '1';
131
  end if;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  else
133
  v_decode_output.is_invalid := '1';
134
  end if;
 
129
  else
130
  v_decode_output.is_invalid := '1';
131
  end if;
132
+ elsif opcode = "0010011" then
133
+ if funct3 = "000" then
134
+ -- TODO: ADDI
135
+ elsif funct3 = "010" then
136
+ -- TODO: SLTI
137
+ elsif funct3 = "" then
138
+ -- TODO: SLTIU
139
+ elsif funct3 = "" then
140
+ -- TODO: XORI
141
+ elsif funct3 = "" then
142
+ -- TODO: ORI
143
+ elsif funct3 = "" then
144
+ -- TODO: ANDI
145
+ else
146
+ v_decode_output.is_invalid := '1';
147
+ end if;
148
  else
149
  v_decode_output.is_invalid := '1';
150
  end if;

We have implemented ADDI before, so we can just add that code back.

src/core/decode_write.vhd CHANGED
@@ -131,7 +131,11 @@ begin
131
  end if;
132
  elsif opcode = "0010011" then
133
  if funct3 = "000" then
134
- -- TODO: ADDI
 
 
 
 
135
  elsif funct3 = "010" then
136
  -- TODO: SLTI
137
  elsif funct3 = "" then
 
131
  end if;
132
  elsif opcode = "0010011" then
133
  if funct3 = "000" then
134
+ -- ADDI
135
+ v_decode_output.operation := OP_ADD;
136
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
137
+ v_decode_output.operand2 := i_imm_s;
138
+ v_decode_output.destination_reg := rd;
139
  elsif funct3 = "010" then
140
  -- TODO: SLTI
141
  elsif funct3 = "" then

The other instructions are very similar, but the exact operation that is executed in the execute stage is slightly different. So we can structure the code a bit differently to take advantage of the similarity.

src/core/decode_write.vhd CHANGED
@@ -130,12 +130,13 @@ begin
130
  v_decode_output.is_invalid := '1';
131
  end if;
132
  elsif opcode = "0010011" then
 
 
 
 
133
  if funct3 = "000" then
134
  -- ADDI
135
  v_decode_output.operation := OP_ADD;
136
- v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
137
- v_decode_output.operand2 := i_imm_s;
138
- v_decode_output.destination_reg := rd;
139
  elsif funct3 = "010" then
140
  -- TODO: SLTI
141
  elsif funct3 = "" then
 
130
  v_decode_output.is_invalid := '1';
131
  end if;
132
  elsif opcode = "0010011" then
133
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
134
+ v_decode_output.operand2 := i_imm_s;
135
+ v_decode_output.destination_reg := rd;
136
+
137
  if funct3 = "000" then
138
  -- ADDI
139
  v_decode_output.operation := OP_ADD;
 
 
 
140
  elsif funct3 = "010" then
141
  -- TODO: SLTI
142
  elsif funct3 = "" then

Now we add a couple of operations so that we can decode SLTI, SLTIU, XORI, ORI, ANDI.

src/core/decode_write.vhd CHANGED
@@ -138,15 +138,20 @@ begin
138
  -- ADDI
139
  v_decode_output.operation := OP_ADD;
140
  elsif funct3 = "010" then
141
- -- TODO: SLTI
142
- elsif funct3 = "" then
143
- -- TODO: SLTIU
144
- elsif funct3 = "" then
145
- -- TODO: XORI
146
- elsif funct3 = "" then
147
- -- TODO: ORI
148
- elsif funct3 = "" then
149
- -- TODO: ANDI
 
 
 
 
 
150
  else
151
  v_decode_output.is_invalid := '1';
152
  end if;
 
138
  -- ADDI
139
  v_decode_output.operation := OP_ADD;
140
  elsif funct3 = "010" then
141
+ -- SLTI
142
+ v_decode_output.operation := OP_SLT;
143
+ elsif funct3 = "011" then
144
+ -- SLTIU
145
+ v_decode_output.operation := OP_SLTU;
146
+ elsif funct3 = "100" then
147
+ -- XORI
148
+ v_decode_output.operation := OP_XOR;
149
+ elsif funct3 = "110" then
150
+ -- ORI
151
+ v_decode_output.operation := OP_OR;
152
+ elsif funct3 = "111" then
153
+ -- ANDI
154
+ v_decode_output.operation := OP_AND;
155
  else
156
  v_decode_output.is_invalid := '1';
157
  end if;
src/core/types.vhd CHANGED
@@ -3,7 +3,7 @@ use ieee.std_logic_1164.all;
3
 
4
 
5
  package core_types is
6
- type operation_t is (OP_ADD, OP_LED);
7
 
8
  type fetch_output_t is record
9
  is_active: std_logic;
 
3
 
4
 
5
  package core_types is
6
+ type operation_t is (OP_ADD, OP_SLT, OP_SLTU, OP_XOR, OP_OR, OP_AND, OP_LED);
7
 
8
  type fetch_output_t is record
9
  is_active: std_logic;

Now we still have to implement these operations in the execute module.

OP_SLT compares two operands as signed numbers, and sets 1 when the first operand is less than the second.

src/core/execute.vhd CHANGED
@@ -29,6 +29,12 @@ begin
29
  if input.is_active = '1' and input.is_invalid = '0' then
30
  if input.operation = OP_ADD then
31
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
 
 
 
 
 
 
32
  elsif input.operation = OP_LED then
33
  led <= input.operand1(7 downto 0);
34
  else
 
29
  if input.is_active = '1' and input.is_invalid = '0' then
30
  if input.operation = OP_ADD then
31
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
32
+ elsif input.operation = OP_SLT then
33
+ if signed(input.operand1) < signed(input.operand2) then
34
+ v_output.result := std_logic_vector(to_unsigned(1, 32));
35
+ else
36
+ v_output.result := (others => '0');
37
+ end if;
38
  elsif input.operation = OP_LED then
39
  led <= input.operand1(7 downto 0);
40
  else

OP_SLTU is similar but works on unsigned operands.

src/core/execute.vhd CHANGED
@@ -35,6 +35,12 @@ begin
35
  else
36
  v_output.result := (others => '0');
37
  end if;
 
 
 
 
 
 
38
  elsif input.operation = OP_LED then
39
  led <= input.operand1(7 downto 0);
40
  else
 
35
  else
36
  v_output.result := (others => '0');
37
  end if;
38
+ elsif input.operation = OP_SLTU then
39
+ if unsigned(input.operand1) < unsigned(input.operand2) then
40
+ v_output.result := std_logic_vector(to_unsigned(1, 32));
41
+ else
42
+ v_output.result := (others => '0');
43
+ end if;
44
  elsif input.operation = OP_LED then
45
  led <= input.operand1(7 downto 0);
46
  else

OP_XOR, OP_OR, and OP_AND are similar and simple to implement.

src/core/execute.vhd CHANGED
@@ -41,6 +41,12 @@ begin
41
  else
42
  v_output.result := (others => '0');
43
  end if;
 
 
 
 
 
 
44
  elsif input.operation = OP_LED then
45
  led <= input.operand1(7 downto 0);
46
  else
 
41
  else
42
  v_output.result := (others => '0');
43
  end if;
44
+ elsif input.operation = OP_XOR then
45
+ v_output.result := input.operand1 xor input.operand2;
46
+ elsif input.operation = OP_OR then
47
+ v_output.result := input.operand1 or input.operand2;
48
+ elsif input.operation = OP_AND then
49
+ v_output.result := input.operand1 and input.operand2;
50
  elsif input.operation = OP_LED then
51
  led <= input.operand1(7 downto 0);
52
  else

Now, SLLI, SRLI, and SRAI are similar to eachother but different from the instructions we just implemented, but for some reason all share the same opcode. I'll put these three instructions above the ones we just implemented, so that we can use slightly less logic.

src/core/decode_write.vhd CHANGED
@@ -129,6 +129,16 @@ begin
129
  else
130
  v_decode_output.is_invalid := '1';
131
  end if;
 
 
 
 
 
 
 
 
 
 
132
  elsif opcode = "0010011" then
133
  v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
134
  v_decode_output.operand2 := i_imm_s;
 
129
  else
130
  v_decode_output.is_invalid := '1';
131
  end if;
132
+ elsif opcode = "0010011" and funct3 = "001" and funct7 = "0000000" then
133
+ -- TODO: SLLI
134
+ elsif opcode = "0010011" and funct3 = "101" then
135
+ if funct7 = "0000000" then
136
+ -- TODO: SRLI
137
+ elsif funct7 = "0000001" then
138
+ -- TODO: SRAI
139
+ else
140
+ v_decode_output.is_invalid := '1';
141
+ end if;
142
  elsif opcode = "0010011" then
143
  v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
144
  v_decode_output.operand2 := i_imm_s;

Again, we add operations to implement the decoding of these instructions.

src/core/decode_write.vhd CHANGED
@@ -130,12 +130,22 @@ begin
130
  v_decode_output.is_invalid := '1';
131
  end if;
132
  elsif opcode = "0010011" and funct3 = "001" and funct7 = "0000000" then
133
- -- TODO: SLLI
 
 
 
 
134
  elsif opcode = "0010011" and funct3 = "101" then
 
 
 
 
135
  if funct7 = "0000000" then
136
- -- TODO: SRLI
 
137
  elsif funct7 = "0000001" then
138
- -- TODO: SRAI
 
139
  else
140
  v_decode_output.is_invalid := '1';
141
  end if;
 
130
  v_decode_output.is_invalid := '1';
131
  end if;
132
  elsif opcode = "0010011" and funct3 = "001" and funct7 = "0000000" then
133
+ -- SLLI
134
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
135
+ v_decode_output.operand2 := "000000000000000000000000000" & rd;
136
+ v_decode_output.destination_reg := rd;
137
+ v_decode_output.operation := OP_SLL;
138
  elsif opcode = "0010011" and funct3 = "101" then
139
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
140
+ v_decode_output.operand2 := "000000000000000000000000000" & rd;
141
+ v_decode_output.destination_reg := rd;
142
+
143
  if funct7 = "0000000" then
144
+ -- SRLI
145
+ v_decode_output.operation := OP_SRL;
146
  elsif funct7 = "0000001" then
147
+ -- SRAI
148
+ v_decode_output.operation := OP_SRA;
149
  else
150
  v_decode_output.is_invalid := '1';
151
  end if;
src/core/types.vhd CHANGED
@@ -3,7 +3,7 @@ use ieee.std_logic_1164.all;
3
 
4
 
5
  package core_types is
6
- type operation_t is (OP_ADD, OP_SLT, OP_SLTU, OP_XOR, OP_OR, OP_AND, OP_LED);
7
 
8
  type fetch_output_t is record
9
  is_active: std_logic;
 
3
 
4
 
5
  package core_types is
6
+ type operation_t is (OP_ADD, OP_SLT, OP_SLTU, OP_XOR, OP_OR, OP_AND, OP_SLL, OP_SRL, OP_SRA, OP_LED);
7
 
8
  type fetch_output_t is record
9
  is_active: std_logic;

Then we implement the operations themselves. The shift instructions are a bit weird. The manual mentions

The operand to be shifted is in rs1, and the shift amount is encoded in the lower 5 bits of the I-immediate field.

This means that you can shift by at most 31 bits. Janky as hell in my opinion, but we're just implementing the spec, not making it.

A nice way of implementing shifts by a variable number of bits, say n, is two do the shift as a sequence of shifts of powers of two. If n is 5-bits, and the most significant bit of n is set, we shift by 16 bits. If the next bit is set, we shift by 8 bits, etc., etc. At the end, we'll have shifted by n bits.

src/core/execute.vhd CHANGED
@@ -21,6 +21,8 @@ begin
21
 
22
  process (clk)
23
  variable v_output: execute_output_t;
 
 
24
  begin
25
  if rising_edge(clk) then
26
  v_output := DEFAULT_EXECUTE_OUTPUT;
@@ -47,6 +49,61 @@ begin
47
  v_output.result := input.operand1 or input.operand2;
48
  elsif input.operation = OP_AND then
49
  v_output.result := input.operand1 and input.operand2;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  elsif input.operation = OP_LED then
51
  led <= input.operand1(7 downto 0);
52
  else
 
21
 
22
  process (clk)
23
  variable v_output: execute_output_t;
24
+ variable v_sign: std_logic_vector(31 downto 0);
25
+
26
  begin
27
  if rising_edge(clk) then
28
  v_output := DEFAULT_EXECUTE_OUTPUT;
 
49
  v_output.result := input.operand1 or input.operand2;
50
  elsif input.operation = OP_AND then
51
  v_output.result := input.operand1 and input.operand2;
52
+ elsif input.operation = OP_SLL then
53
+ v_output.result := input.operand1;
54
+
55
+ if input.operand2(4) = '1' then
56
+ v_output.result := v_output.result(15 downto 0) & "0000000000000000";
57
+ end if;
58
+ if input.operand2(3) = '1' then
59
+ v_output.result := v_output.result(23 downto 0) & "00000000";
60
+ end if;
61
+ if input.operand2(2) = '1' then
62
+ v_output.result := v_output.result(27 downto 0) & "0000";
63
+ end if;
64
+ if input.operand2(1) = '1' then
65
+ v_output.result := v_output.result(29 downto 0) & "00";
66
+ end if;
67
+ if input.operand2(0) = '1' then
68
+ v_output.result := v_output.result(30 downto 0) & "0";
69
+ end if;
70
+ elsif input.operation = OP_SRL then
71
+ v_output.result := input.operand1;
72
+
73
+ if input.operand2(4) = '1' then
74
+ v_output.result := "0000000000000000" & v_output.result(31 downto 16);
75
+ end if;
76
+ if input.operand2(3) = '1' then
77
+ v_output.result := "00000000" & v_output.result(31 downto 8);
78
+ end if;
79
+ if input.operand2(2) = '1' then
80
+ v_output.result := "0000" & v_output.result(31 downto 4);
81
+ end if;
82
+ if input.operand2(1) = '1' then
83
+ v_output.result := "00" & v_output.result(31 downto 2);
84
+ end if;
85
+ if input.operand2(0) = '1' then
86
+ v_output.result := "0" & v_output.result(31 downto 1);
87
+ end if;
88
+ elsif input.operation = OP_SRA then
89
+ v_output.result := input.operand1;
90
+ v_sign := (others => input.operand1(31));
91
+
92
+ if input.operand2(4) = '1' then
93
+ v_output.result := v_sign(15 downto 0) & v_output.result(31 downto 16);
94
+ end if;
95
+ if input.operand2(3) = '1' then
96
+ v_output.result := v_sign(7 downto 0) & v_output.result(31 downto 8);
97
+ end if;
98
+ if input.operand2(2) = '1' then
99
+ v_output.result := v_sign(3 downto 0) & v_output.result(31 downto 4);
100
+ end if;
101
+ if input.operand2(1) = '1' then
102
+ v_output.result := v_sign(2 downto 0) & v_output.result(31 downto 3);
103
+ end if;
104
+ if input.operand2(0) = '1' then
105
+ v_output.result := v_sign(1 downto 0) & v_output.result(31 downto 2);
106
+ end if;
107
  elsif input.operation = OP_LED then
108
  led <= input.operand1(7 downto 0);
109
  else

This is a big change, but it's (almost) the same verbose code. In fact, we can merge the implementations of the two right shifts (SRL and SRA).

src/core/execute.vhd CHANGED
@@ -67,27 +67,14 @@ begin
67
  if input.operand2(0) = '1' then
68
  v_output.result := v_output.result(30 downto 0) & "0";
69
  end if;
70
- elsif input.operation = OP_SRL then
71
  v_output.result := input.operand1;
72
 
73
- if input.operand2(4) = '1' then
74
- v_output.result := "0000000000000000" & v_output.result(31 downto 16);
75
- end if;
76
- if input.operand2(3) = '1' then
77
- v_output.result := "00000000" & v_output.result(31 downto 8);
78
- end if;
79
- if input.operand2(2) = '1' then
80
- v_output.result := "0000" & v_output.result(31 downto 4);
81
- end if;
82
- if input.operand2(1) = '1' then
83
- v_output.result := "00" & v_output.result(31 downto 2);
84
- end if;
85
- if input.operand2(0) = '1' then
86
- v_output.result := "0" & v_output.result(31 downto 1);
87
  end if;
88
- elsif input.operation = OP_SRA then
89
- v_output.result := input.operand1;
90
- v_sign := (others => input.operand1(31));
91
 
92
  if input.operand2(4) = '1' then
93
  v_output.result := v_sign(15 downto 0) & v_output.result(31 downto 16);
 
67
  if input.operand2(0) = '1' then
68
  v_output.result := v_output.result(30 downto 0) & "0";
69
  end if;
70
+ elsif input.operation = OP_SRL or input.operation = OP_SRA then
71
  v_output.result := input.operand1;
72
 
73
+ if input.operation = OP_SRL then
74
+ v_sign := (others => '0');
75
+ else
76
+ v_sign := (others => input.operand1(31));
 
 
 
 
 
 
 
 
 
 
77
  end if;
 
 
 
78
 
79
  if input.operand2(4) = '1' then
80
  v_output.result := v_sign(15 downto 0) & v_output.result(31 downto 16);

Moving on; almost all of the instructions with opcode 0110011 are register-register versions of instructions we already implemented. The SUB instruction is the only exception.

As usual I'll add placeholders first.

src/core/decode_write.vhd CHANGED
@@ -175,6 +175,30 @@ begin
175
  else
176
  v_decode_output.is_invalid := '1';
177
  end if;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  else
179
  v_decode_output.is_invalid := '1';
180
  end if;
 
175
  else
176
  v_decode_output.is_invalid := '1';
177
  end if;
178
+ elsif opcode = "0110011" then
179
+ if funct7 = "0000000" and funct3 = "000" then
180
+ -- TODO: ADD
181
+ elsif funct7 = "0100000" and funct3 = "000" then
182
+ -- TODO: SUB
183
+ elsif funct7 = "0000000" and funct3 = "001" then
184
+ -- TODO: SLL
185
+ elsif funct7 = "0000000" and funct3 = "010" then
186
+ -- TODO: SLT
187
+ elsif funct7 = "0000000" and funct3 = "011" then
188
+ -- TODO: SLTU
189
+ elsif funct7 = "0000000" and funct3 = "100" then
190
+ -- TODO: XOR
191
+ elsif funct7 = "0000000" and funct3 = "101" then
192
+ -- TODO: SRL
193
+ elsif funct7 = "0100000" and funct3 = "101" then
194
+ -- TODO: SRA
195
+ elsif funct7 = "0000000" and funct3 = "110" then
196
+ -- TODO: OR
197
+ elsif funct7 = "0000000" and funct3 = "111" then
198
+ -- TODO: AND
199
+ else
200
+ v_decode_output.is_invalid := '1';
201
+ end if;
202
  else
203
  v_decode_output.is_invalid := '1';
204
  end if;
src/core/types.vhd CHANGED
@@ -3,7 +3,7 @@ use ieee.std_logic_1164.all;
3
 
4
 
5
  package core_types is
6
- type operation_t is (OP_ADD, OP_SLT, OP_SLTU, OP_XOR, OP_OR, OP_AND, OP_SLL, OP_SRL, OP_SRA, OP_LED);
7
 
8
  type fetch_output_t is record
9
  is_active: std_logic;
 
3
 
4
 
5
  package core_types is
6
+ type operation_t is (OP_ADD, OP_SLT, OP_SLTU, OP_XOR, OP_OR, OP_AND, OP_SLL, OP_SRL, OP_SRA, OP_SUB, OP_LED);
7
 
8
  type fetch_output_t is record
9
  is_active: std_logic;

Now we'll add the implementation.

src/core/decode_write.vhd CHANGED
@@ -176,26 +176,40 @@ begin
176
  v_decode_output.is_invalid := '1';
177
  end if;
178
  elsif opcode = "0110011" then
 
 
 
 
179
  if funct7 = "0000000" and funct3 = "000" then
180
- -- TODO: ADD
 
181
  elsif funct7 = "0100000" and funct3 = "000" then
182
- -- TODO: SUB
 
183
  elsif funct7 = "0000000" and funct3 = "001" then
184
- -- TODO: SLL
 
185
  elsif funct7 = "0000000" and funct3 = "010" then
186
- -- TODO: SLT
 
187
  elsif funct7 = "0000000" and funct3 = "011" then
188
- -- TODO: SLTU
 
189
  elsif funct7 = "0000000" and funct3 = "100" then
190
- -- TODO: XOR
 
191
  elsif funct7 = "0000000" and funct3 = "101" then
192
- -- TODO: SRL
 
193
  elsif funct7 = "0100000" and funct3 = "101" then
194
- -- TODO: SRA
 
195
  elsif funct7 = "0000000" and funct3 = "110" then
196
- -- TODO: OR
 
197
  elsif funct7 = "0000000" and funct3 = "111" then
198
- -- TODO: AND
 
199
  else
200
  v_decode_output.is_invalid := '1';
201
  end if;
 
176
  v_decode_output.is_invalid := '1';
177
  end if;
178
  elsif opcode = "0110011" then
179
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
180
+ v_decode_output.operand2 := reg(to_integer(unsigned(rs2)));
181
+ v_decode_output.destination_reg := rd;
182
+
183
  if funct7 = "0000000" and funct3 = "000" then
184
+ -- ADD
185
+ v_decode_output.operation := OP_ADD;
186
  elsif funct7 = "0100000" and funct3 = "000" then
187
+ -- SUB
188
+ v_decode_output.operation := OP_SUB;
189
  elsif funct7 = "0000000" and funct3 = "001" then
190
+ -- SLL
191
+ v_decode_output.operation := OP_SLL;
192
  elsif funct7 = "0000000" and funct3 = "010" then
193
+ -- SLT
194
+ v_decode_output.operation := OP_SLT;
195
  elsif funct7 = "0000000" and funct3 = "011" then
196
+ -- SLTU
197
+ v_decode_output.operation := OP_SLTU;
198
  elsif funct7 = "0000000" and funct3 = "100" then
199
+ -- XOR
200
+ v_decode_output.operation := OP_XOR;
201
  elsif funct7 = "0000000" and funct3 = "101" then
202
+ -- SRL
203
+ v_decode_output.operation := OP_SRL;
204
  elsif funct7 = "0100000" and funct3 = "101" then
205
+ -- SRA
206
+ v_decode_output.operation := OP_SRA;
207
  elsif funct7 = "0000000" and funct3 = "110" then
208
+ -- OR
209
+ v_decode_output.operation := OP_OR;
210
  elsif funct7 = "0000000" and funct3 = "111" then
211
+ -- AND
212
+ v_decode_output.operation := OP_AND;
213
  else
214
  v_decode_output.is_invalid := '1';
215
  end if;

Now, we just have to implement OP_SUB in the decoder.

src/core/execute.vhd CHANGED
@@ -31,6 +31,8 @@ begin
31
  if input.is_active = '1' and input.is_invalid = '0' then
32
  if input.operation = OP_ADD then
33
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
 
 
34
  elsif input.operation = OP_SLT then
35
  if signed(input.operand1) < signed(input.operand2) then
36
  v_output.result := std_logic_vector(to_unsigned(1, 32));
 
31
  if input.is_active = '1' and input.is_invalid = '0' then
32
  if input.operation = OP_ADD then
33
  v_output.result := std_logic_vector(unsigned(input.operand1) + unsigned(input.operand2));
34
+ elsif input.operation = OP_SUB then
35
+ v_output.result := std_logic_vector(unsigned(input.operand1) - unsigned(input.operand2));
36
  elsif input.operation = OP_SLT then
37
  if signed(input.operand1) < signed(input.operand2) then
38
  v_output.result := std_logic_vector(to_unsigned(1, 32));

OK, phew. Just a couple of oddball instructions left. FENCE is used for memory ordering. We don't even have memory yet, so for now we'll make this a NOP.

src/core/decode_write.vhd CHANGED
@@ -213,6 +213,10 @@ begin
213
  else
214
  v_decode_output.is_invalid := '1';
215
  end if;
 
 
 
 
216
  else
217
  v_decode_output.is_invalid := '1';
218
  end if;
 
213
  else
214
  v_decode_output.is_invalid := '1';
215
  end if;
216
+ elsif funct3 = "000" and opcode = "0001111" then
217
+ -- FENCE
218
+ v_decode_output := DEFAULT_DECODE_OUTPUT;
219
+ v_decode_output.is_active := '1';
220
  else
221
  v_decode_output.is_invalid := '1';
222
  end if;

Now, FENCE.TSO and PAUSE are special cases of FENCE, which we already handle. So we can skip them; We'll look if we can do something better later.

ECALL and EBREAK are traps, which we have not implemented yet. So I'll add the logic to be able to easily decode them later, but otherwise ignore them.

src/core/decode_write.vhd CHANGED
@@ -217,6 +217,10 @@ begin
217
  -- FENCE
218
  v_decode_output := DEFAULT_DECODE_OUTPUT;
219
  v_decode_output.is_active := '1';
 
 
 
 
220
  else
221
  v_decode_output.is_invalid := '1';
222
  end if;
 
217
  -- FENCE
218
  v_decode_output := DEFAULT_DECODE_OUTPUT;
219
  v_decode_output.is_active := '1';
220
+ elsif i_imm = "000000000000" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
221
+ -- ECALL
222
+ elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
223
+ -- EBREAK
224
  else
225
  v_decode_output.is_invalid := '1';
226
  end if;

Now that we're done, let's add back our custom "LED" and "HANG" instructions.

src/core/decode_write.vhd CHANGED
@@ -221,6 +221,15 @@ begin
221
  -- ECALL
222
  elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
223
  -- EBREAK
 
 
 
 
 
 
 
 
 
224
  else
225
  v_decode_output.is_invalid := '1';
226
  end if;
 
221
  -- ECALL
222
  elsif i_imm = "000000000001" and rs1 = "00000" and funct3 = "000" and rd = "00000" and opcode = "1110011" then
223
  -- EBREAK
224
+ elsif opcode = "1111111" and funct3 = "000" then
225
+ -- LED (custom instruction): set the LEDs to the 8 least significant bits of rs1
226
+ v_decode_output.operation := OP_LED;
227
+ v_decode_output.operand1 := reg(to_integer(unsigned(rs1)));
228
+ v_decode_output.operand2 := (others => '0');
229
+ v_decode_output.destination_reg := (others => '0');
230
+ elsif opcode = "1111111" and funct3 = "001" then
231
+ -- HANG (custom instruction): stops execution of the CPU
232
+ v_decode_output := DEFAULT_DECODE_OUTPUT;
233
  else
234
  v_decode_output.is_invalid := '1';
235
  end if;

Phew, we added a lot of instructions! We added the decoding for all instructions and actually implemented about half of all the instructions in the basic RV32 ISA. Not bad for a single lesson.

Did we forget anything? Testing, you say?