/
cpu.rs
258 lines (242 loc) · 10.3 KB
/
cpu.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
pub const REGISTERS_COUNT: usize = 32;
pub struct Cpu {
pub regs: [i32; REGISTERS_COUNT],
pub pc: usize,
}
fn set_memory8(index: usize, mem: &mut Vec<u8>, val: u8) {
mem[index] = val
}
fn set_memory16(index: usize, mem: &mut Vec<u8>, val: u16) {
mem[index] = (val & 0xFF) as u8;
mem[index + 1] = ((val & 0xFF00) >> 8) as u8;
}
fn set_memory32(index: usize, mem: &mut Vec<u8>, val: u32) {
mem[index] = (val & 0xFF) as u8;
mem[index + 1] = ((val & 0xFF00) >> 8) as u8;
mem[index + 2] = ((val & 0xFF0000) >> 16) as u8;
mem[index + 3] = ((val & 0xFF000000) >> 24) as u8;
}
fn get_memory8(index: usize, mem: &Vec<u8>) -> u8 {
mem[index]
}
fn get_memory16(index: usize, mem: &Vec<u8>) -> u16 {
// little endian
return (mem[index] as u16)
| ((mem[index + 1] as u16) << 8);
}
fn get_memory32(index: usize, mem: &Vec<u8>) -> u32 {
// little endian
return (mem[index] as u32)
| ((mem[index + 1] as u32) << 8)
| ((mem[index + 2] as u32) << 16)
| ((mem[index + 3] as u32) << 24);
}
impl Cpu {
pub fn new() -> Cpu {
Cpu {
regs: [0; REGISTERS_COUNT],
pc: 0,
}
}
pub fn start(&mut self, mem: &mut Vec<u8>) {
let size = mem.len();
while self.pc < size {
let binary = self.fetch(mem);
self.execute(binary, mem);
self.pc += 4;
}
}
fn fetch(&mut self, mem: &Vec<u8>) -> u32 {
get_memory32(self.pc, mem)
}
// This function is public because it's called from a unit test.
pub fn execute(&mut self, binary: u32, mem: &mut Vec<u8>) {
let opcode = binary & 0x0000007F;
let rd = ((binary & 0x00000F80) >> 7) as usize;
let rs1 = ((binary & 0x000F8000) >> 15) as usize;
let rs2 = ((binary & 0x01F00000) >> 20) as usize;
let funct3 = (binary & 0x00007000) >> 12;
let funct7 = (binary & 0xFE000000) >> 25;
let regs = &mut self.regs;
match opcode {
0x03 => { // I-type
let offset = ((binary & 0xFFF00000) as i32) >> 20;
let addr = (regs[rs1] + offset) as usize;
match funct3 {
0x0 => regs[rd] = (get_memory8(addr, mem) as i8) as i32, // lb
0x1 => regs[rd] = (get_memory16(addr, mem) as i16) as i32, // lh
0x2 => regs[rd] = get_memory32(addr, mem) as i32, // lw
0x4 => regs[rd] = (get_memory8(addr, mem) as i32) & 0xFF, // lbu
0x5 => regs[rd] = (get_memory16(addr, mem) as i32) & 0xFFFF, // lhu
_ => {},
}
},
0x0F => { // I-type
// fence instructions are not supportted yet because this emulator executes a
// binary sequentially on a single thread.
// fence i is a part of the Zifencei extension.
match funct3 {
0x0 => {}, // fence
0x1 => {}, // fence.i
_ => {},
}
}
0x13 => { // I-type
let imm = ((binary & 0xFFF00000) as i32) >> 20;
let shamt = (binary & 0x01F00000) >> 20;
match funct3 {
0x0 => regs[rd] = regs[rs1] + imm, // addi
0x1 => regs[rd] = ((regs[rs1] as u32) << shamt) as i32, // slli
0x2 => regs[rd] = if regs[rs1] < imm { 1 } else { 0 }, // slti
0x3 => regs[rd] = if (regs[rs1] as u32) < (imm as u32) { 1 } else { 0 }, // sltiu
0x4 => regs[rd] = regs[rs1] ^ imm, // xori
0x5 => {
match funct7 {
0x00 => regs[rd] = ((regs[rs1] as u32) >> shamt) as i32, // srli
0x20 => regs[rd] = regs[rs1] >> shamt, // srai
_ => {},
}
}
0x6 => regs[rd] = regs[rs1] | imm, // ori
0x7 => regs[rd] = regs[rs1] & imm, // andi
_ => {},
}
},
0x17 => { // U-type
// AUIPC forms a 32-bit offset from the 20-bit U-immediate, filling
// in the lowest 12 bits with zeros.
let imm = (binary & 0xFFFFF000) as i32;
regs[rd] = (self.pc as i32) + imm; // auipc
},
0x23 => { // S-type
let imm11_5 = ((binary & 0xFE000000) as i32) >> 25;
let imm4_0 = (binary & 0x00000F80) >> 7;
let offset = ((imm11_5 << 5) as u32
| imm4_0) as i32;
let addr = (regs[rs1] + offset) as usize;
match funct3 {
0x0 => set_memory8(addr, mem, regs[rs2] as u8), // sb
0x1 => set_memory16(addr, mem, regs[rs2] as u16), // sh
0x2 => set_memory32(addr, mem, regs[rs2] as u32), // sw
_ => {},
}
},
0x33 => { // R-type
match (funct3, funct7) {
(0x0, 0x00) => regs[rd] = regs[rs1] + regs[rs2], // add
(0x0, 0x20) => regs[rd] = regs[rs1] - regs[rs2], // sub
(0x1, 0x00) => regs[rd] = ((regs[rs1] as u32) << (regs[rs2] as u32)) as i32, // sll
(0x2, 0x00) => regs[rd] = if regs[rs1] < regs[rs2] { 1 } else { 0 }, // slt
(0x3, 0x00) => regs[rd] = if (regs[rs1] as u32) < (regs[rs2] as u32) { 1 } else { 0 }, // sltu
(0x4, 0x00) => regs[rd] = regs[rs1] ^ regs[rs2], // xor
(0x5, 0x00) => regs[rd] = ((regs[rs1] as u32) >> (regs[rs2] as u32)) as i32, // srl
(0x5, 0x20) => regs[rd] = regs[rs1] >> (regs[rs2] as u32), // sra
(0x6, 0x00) => regs[rd] = regs[rs1] | regs[rs2], // or
(0x7, 0x00) => regs[rd] = regs[rs1] & regs[rs2], // and
_ => {},
};
},
0x37 => { // U-type
// LUI places the U-immediate value in the top 20 bits of the destination
// register rd, filling in the lowest 12 bits with zeros.
regs[rd] = (binary & 0xFFFFF000) as i32; // lui
},
0x63 => { // B-type
let imm12 = ((binary & 0x80000000) as i32) >> 31;
let imm10_5 = (binary & 0x7E000000) >> 25;
let imm4_1 = (binary & 0x00000F00) >> 8;
let imm11 = (binary & 0x00000080) >> 7;
let offset = ((imm12 << 12) as u32
| (imm11 << 11)
| (imm10_5 << 5)
| (imm4_1 << 1)) as i32;
match funct3 {
0x0 => {
// beq
if regs[rs1] == regs[rs2] {
self.pc = ((self.pc as i32) + offset) as usize;
}
},
0x1 => {
// bne
if regs[rs1] != regs[rs2] {
self.pc = ((self.pc as i32) + offset) as usize;
}
},
0x4 => {
// blt
if regs[rs1] < regs[rs2] {
self.pc = ((self.pc as i32) + offset) as usize;
}
},
0x5 => {
// bge
if regs[rs1] >= regs[rs2] {
self.pc = ((self.pc as i32) + offset) as usize;
}
},
0x6 => {
// bltu
if (regs[rs1] as u32) < (regs[rs2] as u32) {
self.pc = ((self.pc as i32) + offset) as usize;
}
},
0x7 => {
// bgeu
if (regs[rs1] as u32) >= (regs[rs2] as u32) {
self.pc = ((self.pc as i32) + offset) as usize;
}
},
_ => {},
}
},
0x67 => { // I-type
// jalr
regs[rd] = (self.pc as i32) + 4;
let imm = ((binary & 0xFFF00000) as i32) >> 20;
self.pc = ((regs[rs1] + imm) & !1) as usize;
},
0x6F => { // J-type
// jal
regs[rd] = (self.pc as i32) + 4;
let imm20 = ((binary & 0x80000000) as i32) >> 31;
let imm10_1 = (binary & 0x7FE00000) >> 21;
let imm11 = (binary & 0x100000) >> 20;
let imm19_12 = (binary & 0xFF000) >> 12;
let offset = ((imm20 << 20) as u32
| (imm19_12 << 12)
| (imm11 << 11)
| (imm10_1 << 1)) as i32;
let tmp = (self.pc as i32) + offset;
self.pc = tmp as usize;
},
0x73 => { // I-type
let funct12 = ((binary & 0xFFF00000) as i32) >> 20;
let _csr = ((binary & 0xFFF00000) as i32) >> 20;
match funct3 {
0x0 => {
match funct12 {
// TODO: implement ecall and ebreak
// ecall makes a request of the execution environment by raising an
// environment call exception.
// ebreak makes a request of the debugger by raising a breakpoint
// exception.
0x0 => {}, // ecall
0x1 => {}, // ebreak
_ => {},
}
},
// TODO: implement RV32/RV64 Zicsr Standard Extension
0x1 => {}, // csrrw
0x2 => {}, // csrrs
0x3 => {}, // csrrc
0x5 => {}, // csrrwi
0x6 => {}, // csrrsi
0x7 => {}, // csrrci
_ => {},
}
},
_ => {},
}
}
}