rv32-rs/src/lib.rs
src/lib.rsRaw
1//! Implementation of the RV32I ISA. Each opcode has a comment explaining it.
2use anyhow::{anyhow, Result};
3
4pub struct RV32 {
5 regs: [u32; 32], // registers
6 mem: Vec<u8>, // memory
7 pc: u32,
8}
9
10impl RV32 {
11 pub fn new(mem: Vec<u8>) -> Self {
12 Self {
13 regs: [0; 32],
14 pc: 0,
15 mem,
16 }
17 }
18 pub fn set_register(&mut self, index: usize, value: u32) {
19 assert!(index < 32, "register index must <32");
20 if index != 0 {
21 self.regs[index] = value;
22 }
23 }
24 // validate the access
25 fn range(&self, address: u32, size: usize) -> Result<std::ops::Range<usize>> {
26 if !address.is_multiple_of(size as u32) {
27 return Err(anyhow!("misaligned at address: {}", address));
28 }
29 let start = address as usize;
30 let end = start
31 .checked_add(size)
32 .filter(|&end| end <= self.mem.len())
33 .ok_or(anyhow!("access fault, address: {}", address))?;
34 Ok(start..end)
35 }
36 fn read(&self, address: u32, size: usize) -> Result<u32> {
37 let range = self.range(address, size)?;
38 let mut bytes = [0; 4];
39 bytes[..size].copy_from_slice(&self.mem[range]);
40 Ok(u32::from_le_bytes(bytes))
41 }
42 pub fn step(&mut self) -> Result<()> {
43 let instruction = self.read(self.pc, 4)?;
44 let mut next_pc = self.pc.wrapping_add(4);
45
46 // R-type instruction:
47 // R-type:
48 // 31 25 24 20 19 15 14 12 11 7 6 0
49 // ┌───────────┬────────────┬────────────┬────────┬────────┬─────────┐
50 // │ funct7 │ rs2 │ rs1 │ funct3 │ rd │ opcode │
51 // └───────────┴────────────┴────────────┴────────┴────────┴─────────┘
52 let rd = ((instruction >> 7) & 31) as usize;
53 let funct3 = (instruction >> 12) & 7;
54 let rs1 = ((instruction >> 15) & 31) as usize;
55 let rs2 = ((instruction >> 20) & 31) as usize;
56 let funct7 = instruction >> 25;
57 // I-type:
58 // 31 20 19 15 14 12 11 7 6 0
59 // ┌────────────────────────┬────────────┬────────┬────────┬─────────┐
60 // │ imm[11:0] │ rs1 │ funct3 │ rd │ opcode │
61 // └────────────────────────┴────────────┴────────┴────────┴─────────┘
62 // S-type:
63 // 31 25 24 20 19 15 14 12 11 7 6 0
64 // ┌───────────┬────────────┬────────────┬────────┬────────┬─────────┐
65 // │ imm[11:5] │ rs2 │ rs1 │ funct3 │imm[4:0]│ opcode │
66 // └───────────┴────────────┴────────────┴────────┴────────┴─────────┘
67 // B-type:
68 // 31 30 25 24 20 19 15 14 12 11 8 7 6 0
69 // ┌─────┬─────────┬────────┬────────────┬────────┬────────┬──┬──────┐
70 // │imm12│imm[10:5]│ rs2 │ rs1 │ funct3 │imm[4:1]│11│opcode│
71 // └─────┴─────────┴────────┴────────────┴────────┴────────┴──┴──────┘
72 //
73 // Note: we keep `imm` as the I-type imm, and in the S-type we compute
74 // the S-type respective imm from 31-25 and 11-7 positions, and for
75 // B-type we obtain imm from the positions 31, 31-25, 11-8, 7, and
76 // imm[0]=0.
77 let imm = sign_extend(instruction >> 20, 12);
78
79 let a = self.regs[rs1];
80 let b = self.regs[rs2];
81 let mut result: Option<u32> = None;
82
83 // 0x7f=0111_1111, match the lowest 7 bits to read the opcode
84 match instruction & 0x7f {
85 // I-type op'ns, loads:
86 0x03 => {
87 let (size, signed) = match funct3 {
88 // LB: load a byte at rs1 + immediate and sign-extend it
89 // into rd
90 0 => (1, true),
91 // LH: load a halfword at rs1 + immediate and sign-extend it
92 // into rd
93 1 => (2, true),
94 // LW: load a 32-bit word at rs1 + immediate into rd
95 2 => (4, false),
96 // LBU: load a byte at rs1 + immediate and zero-extend it
97 // into rd
98 4 => (1, false),
99 // LHU: load a halfword at rs1 + immediate and zero-extend
100 // it into rd
101 5 => (2, false),
102 _ => {
103 return Err(anyhow!(
104 "illegal instruction in I-type (load): {}",
105 instruction
106 ))
107 }
108 };
109 let value = self.read(a.wrapping_add(imm), size)?;
110 result = Some(if signed {
111 sign_extend(value, size as u32 * 8)
112 } else {
113 value
114 });
115 }
116
117 // I-type arithmetic & logical & shifts op'ns:
118 0x13 => {
119 result = Some(match funct3 {
120 // ADDI: add the sign-extended immediate to rs1, wrapping to
121 // 32 bits
122 0 => a.wrapping_add(imm),
123 // SLTI: signed comparison. Set rd to one if rs1 is less
124 // than the immediate as signed values
125 2 => u32::from((a as i32) < (imm as i32)),
126 // SLTIU: unsigned comparison. Compare rs1 and the
127 // sign-extended immediate as unsigned values
128 3 => u32::from(a < imm),
129 // XORI: write the bitwise XOR of rs1 and the sign-extended
130 // immediate
131 4 => a ^ imm,
132 // ORI: write the bitwise OR of rs1 and the sign-extended
133 // immediate
134 6 => a | imm,
135 // ANDI: write the bitwise AND of rs1 and the sign-extended
136 // immediate
137 7 => a & imm,
138 // SLLI: shift rs1 left by the five-bit immediate shift
139 // amount
140 1 if funct7 == 0 => a << rs2,
141 // SRLI: logical right shift. Shift rs1 right by the
142 // immediate amount, filling with zeros
143 5 if funct7 == 0 => a >> rs2,
144 // SRAI: arithmetic right shift. Shift rs1 right by the
145 // immediate amount, preserving its sign
146 5 if funct7 == 0x20 => ((a as i32) >> rs2) as u32,
147 _ => {
148 return Err(anyhow!(
149 "illegal instruction in I-type (arithmetic/logical): {}",
150 instruction
151 ))
152 }
153 });
154 }
155
156 // S-type op'ns:
157 0x23 => {
158 let size = match funct3 {
159 // SB: store the low byte of rs2 at rs1 + immediate
160 0 => 1,
161 // SH: store the low halfword of rs2 at rs1 + immediate
162 1 => 2,
163 // SW: store the full 32-bit value of rs2 at rs1 + immediate
164 2 => 4,
165 _ => return Err(anyhow!("illegal instruction in S-type: {}", instruction)),
166 };
167 // for the S-type imm, get it from 25-31 and 7-11 positions
168 let s_type_imm = ((instruction >> 25) << 5) | ((instruction >> 7) & 31);
169 let offset = sign_extend(s_type_imm, 12);
170 let range = self.range(a.wrapping_add(offset), size)?;
171 self.mem[range].copy_from_slice(&b.to_le_bytes()[..size]);
172 }
173
174 // R-type (register-register) integer operations
175 0x33 => {
176 result = Some(match (funct7, funct3) {
177 // ADD: add rs1 and rs2, wrapping to 32 bits
178 (0, 0) => a.wrapping_add(b),
179 // SUB: subtract rs2 from rs1, wrapping to 32 bits
180 (0x20, 0) => a.wrapping_sub(b),
181 // SLL: shift rs1 left by the low five bits of rs2
182 (0, 1) => a << (b & 31),
183 // SLT: set rd to one if rs1 is less than rs2 as signed
184 // values, else zero
185 (0, 2) => u32::from((a as i32) < (b as i32)),
186 // SLTU: set rd to one if rs1 is less than rs2 as unsigned
187 // values, else zero
188 (0, 3) => u32::from(a < b),
189 // XOR: write the bitwise XOR of rs1 and rs2 into rd
190 (0, 4) => a ^ b,
191 // SRL: shift rs1 right by the low five bits of rs2, filling
192 // with zeros
193 (0, 5) => a >> (b & 31),
194 // SRA: shift rs1 right by the low five bits of rs2,
195 // preserving its sign
196 (0x20, 5) => ((a as i32) >> (b & 31)) as u32,
197 // OR: write the bitwise OR of rs1 and rs2 into rd
198 (0, 6) => a | b,
199 // AND: write the bitwise AND of rs1 and rs2 into rd
200 (0, 7) => a & b,
201
202 // Note: MUL,DIV,REM are not part of RV32I, but they are in
203 // the M extension.
204
205 // MUL: write the low 32 bits of the product of rs1 and rs2
206 (1, 0) => a.wrapping_mul(b),
207 // DIV: signed quotient toward zero; zero divisors return
208 // all ones
209 (1, 4) => {
210 if b == 0 {
211 u32::MAX
212 } else {
213 (a as i32).wrapping_div(b as i32) as u32
214 }
215 }
216 // REM: signed remainder; zero divisors return the dividend
217 (1, 6) => {
218 if b == 0 {
219 a
220 } else {
221 (a as i32).wrapping_rem(b as i32) as u32
222 }
223 }
224
225 _ => return Err(anyhow!("illegal instruction in R-type: {}", instruction)),
226 });
227 }
228
229 // B-type operations
230 0x63 => {
231 let taken = match funct3 {
232 // BEQ: branch by the signed pc-relative offset if the
233 // registers are equal
234 0 => a == b,
235 // BNE: branch if the registers are unequal
236 1 => a != b,
237 // BLT: branch if rs1 is less than rs2 using signed comparison
238 4 => (a as i32) < (b as i32),
239 // BGE: branch if rs1 is at least rs2 using signed comparison
240 5 => (a as i32) >= (b as i32),
241 // BLTU: branch if rs1 is less than rs2 using unsigned comparison
242 6 => a < b,
243 // BGEU: branch if rs1 is at least rs2 using unsigned comparison
244 7 => a >= b,
245 _ => return Err(anyhow!("illegal instruction in B-type: {}", instruction)),
246 };
247 if taken {
248 // offset: reconstruct imm for B-type from positions 31,
249 // 31-25, 11-8, 7, and imm[0]=0
250 let b_type_imm = ((instruction >> 31) << 12)
251 | (((instruction >> 7) & 1) << 11)
252 | (((instruction >> 25) & 0x3f) << 5)
253 | (((instruction >> 8) & 15) << 1);
254 next_pc = self.pc.wrapping_add(sign_extend(b_type_imm, 13));
255 }
256 }
257
258 // U-type operations
259 // LUI: load the upper 20 immediate bits into rd, clearing the lower
260 // 12 bits
261 0x37 => result = Some(instruction & 0xfffff000),
262 // AUIPC: add the upper immediate to the current pc and write rd
263 0x17 => result = Some(self.pc.wrapping_add(instruction & 0xfffff000)),
264
265 // J-type operations
266 // JAL: jump by a signed pc-relative offset and save pc + 4 in rd
267 0x6f => {
268 let offset = ((instruction >> 31) << 20)
269 | (((instruction >> 12) & 0xff) << 12)
270 | (((instruction >> 20) & 1) << 11)
271 | (((instruction >> 21) & 0x3ff) << 1);
272 next_pc = self.pc.wrapping_add(sign_extend(offset, 21));
273 result = Some(self.pc.wrapping_add(4));
274 }
275 // JALR (I-type): jump to rs1 + immediate with bit zero cleared, saving pc + 4 in rd
276 0x67 if funct3 == 0 => {
277 next_pc = a.wrapping_add(imm) & !1;
278 result = Some(self.pc.wrapping_add(4));
279 }
280 _ => return Err(anyhow!("illegal instruction: {}", instruction)),
281 }
282
283 if next_pc & 3 != 0 {
284 return Err(anyhow!("misaligned, address: {}", next_pc,));
285 }
286 if let Some(value) = result {
287 self.set_register(rd, value);
288 }
289 self.pc = next_pc;
290 Ok(())
291 }
292}
293
294// sign-extend a smaller signed number to 32 bits
295fn sign_extend(value: u32, bits: u32) -> u32 {
296 ((value << (32 - bits)) as i32 >> (32 - bits)) as u32
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 const OPCODE_R: u32 = 0b0110011;
304 const OPCODE_LOAD: u32 = 0b0000011;
305
306 // encodes R-type instruction
307 fn encode_r(name: &str, rd: u32, rs1: u32, rs2: u32) -> u32 {
308 let (funct7, funct3) = match name {
309 "add" => (0b0000000, 0b000),
310 "sub" => (0b0100000, 0b000),
311 "sll" => (0b0000000, 0b001),
312 "slt" => (0b0000000, 0b010),
313 "sltu" => (0b0000000, 0b011),
314 "xor" => (0b0000000, 0b100),
315 "srl" => (0b0000000, 0b101),
316 "sra" => (0b0100000, 0b101),
317 "or" => (0b0000000, 0b110),
318 "and" => (0b0000000, 0b111),
319 // M extension
320 "mul" => (0b0000001, 0b000),
321 "div" => (0b0000001, 0b100),
322 "rem" => (0b0000001, 0b110),
323 _ => panic!("unknown R-type instruction: {name}"),
324 };
325
326 (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_R
327 }
328 fn encode_i(name: &str, rd: u32, rs1: u32, imm: i32) -> u32 {
329 let (funct3, opcode) = match name {
330 // loads: opcode = 0x03
331 "lb" => (0b000, OPCODE_LOAD),
332 "lh" => (0b001, OPCODE_LOAD),
333 "lw" => (0b010, OPCODE_LOAD),
334 "lbu" => (0b100, OPCODE_LOAD),
335 "lhu" => (0b101, OPCODE_LOAD),
336 // arithmetic & logic & shifts: opcode = 0x13
337 "addi" => (0b000, 0x13),
338 "slti" => (0b010, 0x13),
339 "sltiu" => (0b011, 0x13),
340 "xori" => (0b100, 0x13),
341 "ori" => (0b110, 0x13),
342 "andi" => (0b111, 0x13),
343 "slli" => (0b001, 0x13),
344 "srli" => (0b101, 0x13),
345 "srai" => (0b101, 0x13),
346 "jalr" => (0b000, 0x67),
347
348 _ => panic!("unknown I-type instruction: {name}"),
349 };
350 let funct7 = if name == "srai" {
351 0b010_0000
352 } else {
353 0b000_0000
354 };
355
356 let imm = (imm as u32) & 0xfff;
357
358 (imm << 20) | (funct7 << 25) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
359 }
360 fn encode_s(name: &str, rs1: u32, rs2: u32, imm: i32) -> u32 {
361 let (opcode, funct3) = match name {
362 "sb" => (0b0100011, 0b000),
363 "sh" => (0b0100011, 0b001),
364 "sw" => (0b0100011, 0b010),
365 _ => panic!("unknown store instruction: {name}"),
366 };
367
368 let imm = imm as u32;
369
370 let imm_11_5 = (imm >> 5) & 0x7f;
371 let imm_4_0 = imm & 0x1f;
372
373 (imm_11_5 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (imm_4_0 << 7) | opcode
374 }
375 fn encode_b(name: &str, rs1: u32, rs2: u32, offset: i32) -> u32 {
376 let funct3 = match name {
377 "beq" => 0b000,
378 "bne" => 0b001,
379 "blt" => 0b100,
380 "bge" => 0b101,
381 "bltu" => 0b110,
382 "bgeu" => 0b111,
383 _ => panic!("unknown B-type instruction: {name}"),
384 };
385
386 assert!(
387 offset % 2 == 0,
388 "B-type branch offset must be 2-byte aligned"
389 );
390
391 assert!(
392 (-4096..=4094).contains(&offset),
393 "B-type branch offset out of range: {offset}"
394 );
395
396 let imm = offset as u32;
397
398 let imm12 = (imm >> 12) & 0x1;
399 let imm11 = (imm >> 11) & 0x1;
400 let imm10_5 = (imm >> 5) & 0x3f;
401 let imm4_1 = (imm >> 1) & 0xf;
402
403 (imm12 << 31)
404 | (imm10_5 << 25)
405 | (rs2 << 20)
406 | (rs1 << 15)
407 | (funct3 << 12)
408 | (imm4_1 << 8)
409 | (imm11 << 7)
410 | 0b1100011
411 }
412 fn encode_j(name: &str, rd: u32, offset: i32) -> u32 {
413 assert!(matches!(name, "jal"));
414 assert!(offset % 2 == 0);
415 assert!((-1 << 20..=1 << 20).contains(&offset));
416
417 let imm = offset as u32;
418
419 let imm20 = (imm >> 20) & 0x1;
420 let imm10_1 = (imm >> 1) & 0x3ff;
421 let imm11 = (imm >> 11) & 0x1;
422 let imm19_12 = (imm >> 12) & 0xff;
423
424 (imm20 << 31)
425 | (imm19_12 << 12)
426 | (imm11 << 20)
427 | (imm10_1 << 21)
428 | ((rd & 0x1f) << 7)
429 | 0x6f
430 }
431 fn encode_u(name: &str, rd: u32, immediate: u32) -> u32 {
432 let opcode = match name {
433 "lui" => 0x37,
434 "auipc" => 0x17,
435 _ => panic!("unsupported U-type instruction: {name}"),
436 };
437
438 assert!(
439 immediate <= 0xfffff,
440 "U-type immediate must fit in 20 bits: {immediate:#x}"
441 );
442
443 ((immediate & 0xfffff) << 12) | ((rd & 0x1f) << 7) | opcode
444 }
445
446 // R-type ops test
447 #[test]
448 fn test_ops_r_type() {
449 let test_vectors = vec![
450 // (op, rs1, rs2, rd)
451 ("add", 10, 20, 30),
452 ("add", u32::MAX, 1, 0), // overflow
453 ("sub", 30, 20, 10),
454 ("sub", 0, 1, u32::MAX), // overflow
455 ("sll", 1, 4, 16),
456 ("sll", 1, 31, 0x8000_0000), // SLL maximum shift
457 ("sll", 1, 32, 1), // SLL shift wrapping
458 ("slt", (-10i32) as u32, 20, 1),
459 ("sltu", 10, 20, 1),
460 ("slt", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0
461 ("slt", 0xffff_ffff, 0, 1), // signed vs unsigned distinction
462 ("sltu", 0xffff_ffff, 0, 0),
463 ("xor", 0b1010, 0b1100, 0b0110),
464 ("srl", 0x8000_0000, 4, 0x0800_0000),
465 ("srl", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill
466 ("sra", 0x8000_0000, 4, 0xf800_0000),
467 ("sra", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill
468 ("sra", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1
469 ("or", 0b1010, 0b1100, 0b1110),
470 ("and", 0b1010, 0b1100, 0b1000),
471 ];
472 test_ops_r_types_opt(test_vectors);
473 }
474 #[test]
475 fn test_ops_m_extension() {
476 let test_vectors = vec![
477 ("mul", 10, 20, 200),
478 ("mul", 0, 123, 0),
479 ("mul", 1, u32::MAX, u32::MAX),
480 ("mul", u32::MAX, 2, u32::MAX - 1), // result wraps to lower 32 bits
481 ("mul", (-1i32) as u32, 10, (-10i32) as u32), // -1 * 10 = -10
482 ("mul", (-1i32) as u32, (-1i32) as u32, 1), // -1 * -1 = 1
483 ("mul", 0x8000_0000, 2, 0), // INT_MIN * 2 = 0 modulo 2^32
484 ("mul", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN * -1 also wraps to INT_MIN
485 ("div", 20, 5, 4),
486 ("div", 21, 5, 4), // truncates toward zero
487 ("div", 0, 10, 0),
488 ("div", 10, 1, 10),
489 ("div", 10, (-1i32) as u32, (-10i32) as u32),
490 ("div", (-20i32) as u32, 5, (-4i32) as u32), // negative dividend
491 ("div", 20, (-5i32) as u32, (-4i32) as u32), // negative divisor
492 ("div", (-20i32) as u32, (-5i32) as u32, 4), // negative / negative
493 ("div", (-21i32) as u32, 5, (-4i32) as u32), // truncation toward zero: -21 / 5 = -4
494 // RISC-V special case:
495 ("div", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN / -1 = INT_MIN (overflow)
496 ("div", 10, 0, u32::MAX), // division by zero returns -1 for signed DIV
497 ("div", 0x8000_0000, 0, u32::MAX),
498 ("rem", 20, 5, 0),
499 ("rem", 21, 5, 1),
500 ("rem", 0, 10, 0),
501 ("rem", 10, 1, 0),
502 ("rem", 23, 5, 3), // positive remainder
503 ("rem", (-23i32) as u32, 5, (-3i32) as u32), // remainder follows the sign of the dividend
504 ("rem", 23, (-5i32) as u32, 3), // negative divisor does not change the sign of the remainder
505 ("rem", (-23i32) as u32, (-5i32) as u32, (-3i32) as u32), // negative / negative
506 ("rem", 3, 5, 3), // dividend smaller than divisor
507 // RISC-V special case:
508 ("rem", 0x8000_0000, 0xffff_ffff, 0), // INT_MIN % -1 = 0.
509 ("rem", 123, 0, 123), // remainder by zero returns the dividend.
510 ("rem", u32::MAX, 0, u32::MAX),
511 ("rem", 0x8000_0000, 0, 0x8000_0000),
512 ];
513 test_ops_r_types_opt(test_vectors);
514 }
515 fn test_ops_r_types_opt(test_vectors: Vec<(&str, u32, u32, u32)>) {
516 for (name, rs1_value, rs2_value, expected) in test_vectors {
517 let rd = 3;
518 let rs1 = 1;
519 let rs2 = 2;
520
521 let instruction = encode_r(name, rd, rs1, rs2);
522
523 let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
524
525 cpu.regs[rs1 as usize] = rs1_value;
526 cpu.regs[rs2 as usize] = rs2_value;
527
528 cpu.step().unwrap();
529
530 assert_eq!(
531 cpu.regs[rd as usize], expected,
532 "{name} failed:
533 rs1={rs1_value:#010x}, rs2={rs2_value:#010x},
534 expected={expected:#010x}, got={:#010x}",
535 cpu.regs[rd as usize],
536 );
537 }
538 }
539
540 // I-type ops test
541 #[test]
542 fn test_ops_load() {
543 let test_vectors: Vec<(&str, u32, i32, u32)> = vec![
544 ("lb", 0x0000_0080, 0, 0xffff_ff80),
545 ("lbu", 0x0000_0080, 0, 0x0000_0080),
546 ("lb", 0x0000_00ff, 0, 0xffff_ffff),
547 ("lbu", 0x0000_00ff, 0, 0x0000_00ff),
548 ("lh", 0x0000_8000, 0, 0xffff_8000),
549 ("lhu", 0x0000_8000, 0, 0x0000_8000),
550 ("lh", 0x0000_ffff, 0, 0xffff_ffff),
551 ("lhu", 0x0000_ffff, 0, 0x0000_ffff),
552 ];
553
554 for (name, memory_value, address, expected) in test_vectors {
555 let rd = 1;
556 let rs1 = 2;
557
558 let instruction = encode_i(name, rd, rs1, address);
559
560 let mut memory = instruction.to_le_bytes().to_vec();
561 let data_address = memory.len() as u32;
562 memory.extend_from_slice(&memory_value.to_le_bytes());
563 let mut cpu = RV32::new(memory);
564
565 cpu.regs[rs1 as usize] = data_address;
566
567 cpu.step().unwrap();
568
569 assert_eq!(
570 cpu.regs[rd as usize], expected,
571 "{name} failed: memory={memory_value:#010x},
572 address={address}, \
573 expected={expected:#010x},
574 got={:#010x}",
575 cpu.regs[rd as usize],
576 );
577 }
578 }
579 #[test]
580 fn test_ops_i_type_arithmetic() {
581 // almost same test vectors as for `test_ops_r_type`, same names with
582 // `i` as suffix
583 let test_vectors: Vec<(&str, u32, i32, u32)> = vec![
584 // (name, rs1, immediate, expected rd)
585 ("addi", 10, 20, 30),
586 ("addi", u32::MAX, 1, 0), // overflow
587 ("slli", 1, 4, 16),
588 ("slli", 1, 31, 0x8000_0000), // SLL maximum shift
589 ("slti", (-10i32) as u32, 20, 1),
590 ("sltiu", 10, 20, 1),
591 ("slti", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0
592 ("slti", 0xffff_ffff, 0, 1), // signed vs unsigned distinction
593 ("sltiu", 0xffff_ffff, 0, 0),
594 ("xori", 0b1010, 0b1100, 0b0110),
595 ("srli", 0x8000_0000, 4, 0x0800_0000),
596 ("srli", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill
597 ("srai", 0x8000_0000, 4, 0xf800_0000),
598 ("srai", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill
599 ("srai", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1
600 ("ori", 0b1010, 0b1100, 0b1110),
601 ("andi", 0b1010, 0b1100, 0b1000),
602 ];
603
604 for (name, rs1_value, imm, expected) in test_vectors {
605 dbg!(name);
606 let rd = 1;
607 let rs1 = 2;
608
609 let instruction = encode_i(name, rd, rs1, imm);
610 let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
611
612 cpu.regs[rs1 as usize] = rs1_value;
613
614 cpu.step().unwrap();
615
616 assert_eq!(
617 cpu.regs[rd as usize], expected,
618 "{name} failed: rs1={rs1_value:#010x}, \
619 imm={imm}, \
620 expected={expected:#010x}, \
621 got={:#010x}",
622 cpu.regs[rd as usize],
623 );
624 }
625 }
626
627 // S-type ops test
628 #[test]
629 fn test_ops_store() {
630 let test_vectors: Vec<(&str, u32, i32)> = vec![
631 ("sb", 0x0000_0080, 0),
632 ("sb", 0x0000_00ff, 0),
633 ("sb", 0x1234_5678, 0),
634 ("sh", 0x0000_8000, 0),
635 ("sh", 0x0000_ffff, 0),
636 ("sw", 0x1234_5678, 0),
637 ];
638
639 for (name, value, address) in test_vectors {
640 let rs1 = 1;
641 let rs2 = 2;
642
643 let instruction = encode_s(name, rs1, rs2, address);
644
645 let mut memory = instruction.to_le_bytes().to_vec();
646 let data_address = memory.len() as u32;
647
648 // add space for the value being stored
649 memory.extend_from_slice(&[0u8; 4]);
650
651 let mut cpu = RV32::new(memory);
652
653 cpu.regs[rs1 as usize] = data_address;
654 cpu.regs[rs2 as usize] = value;
655
656 cpu.step().unwrap();
657
658 let stored = u32::from_le_bytes(
659 cpu.mem[data_address as usize..data_address as usize + 4]
660 .try_into()
661 .unwrap(),
662 );
663
664 let expected = match name {
665 "sb" => value & 0xff,
666 "sh" => value & 0xffff,
667 "sw" => value,
668 _ => unreachable!(),
669 };
670
671 assert_eq!(
672 stored, expected,
673 "{name} failed: value={value:#010x},
674 address={address}, \
675 expected={expected:#010x},
676 got={stored:#010x}",
677 );
678 }
679 }
680
681 // B-type ops test
682 #[test]
683 fn test_ops_branch() {
684 let test_vectors: Vec<(&str, u32, u32, i32, bool)> = vec![
685 // (name, rs1 value, rs2 value, offset, taken)
686 ("beq", 10, 10, 8, true),
687 ("beq", 10, 20, 8, false),
688 ("bne", 10, 20, 8, true),
689 ("bne", 10, 10, 8, false),
690 ("blt", 10, 20, 8, true),
691 ("blt", 20, 10, 8, false),
692 ("bge", 20, 10, 8, true),
693 ("bge", 10, 20, 8, false),
694 ("bltu", 10, 20, 8, true),
695 ("bltu", 20, 10, 8, false),
696 ("bgeu", 20, 10, 8, true),
697 ("bgeu", 10, 20, 8, false),
698 ("beq", 10, 10, -4, true), // negative branch offsets
699 ("bne", 10, 20, -4, true),
700 ("blt", 0xffff_ffff, 0, 8, true), // signed vs unsigned comparison
701 ("bltu", 0xffff_ffff, 0, 8, false),
702 ("bge", 0xffff_ffff, 0, 8, false),
703 ("bgeu", 0xffff_ffff, 0, 8, true),
704 ];
705
706 for (name, rs1_value, rs2_value, offset, taken) in test_vectors {
707 let rs1 = 1;
708 let rs2 = 2;
709
710 let instruction = encode_b(name, rs1, rs2, offset);
711 let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
712
713 cpu.regs[rs1 as usize] = rs1_value;
714 cpu.regs[rs2 as usize] = rs2_value;
715
716 let initial_pc = cpu.pc;
717
718 cpu.step().unwrap();
719
720 let expected_pc = if taken {
721 initial_pc.wrapping_add(offset as u32)
722 } else {
723 initial_pc.wrapping_add(4)
724 };
725
726 assert_eq!(
727 cpu.pc, expected_pc,
728 "{name} failed: rs1={rs1_value:#010x}, \
729 rs2={rs2_value:#010x}, offset={offset}, \
730 expected_pc={expected_pc:#010x}, \
731 got={:#010x}",
732 cpu.pc,
733 );
734 }
735 }
736 #[test]
737 fn test_ops_upper_and_jump() {
738 let test_vectors: Vec<(&str, u32, u32, u32, u32)> = vec![
739 // name, rd, immediate, pc, expected
740 ("lui", 1, 0x12345, 0, 0x1234_5000),
741 ("lui", 1, 0xfffff, 0, 0xffff_f000),
742 ("auipc", 1, 0x12345, 0x1000, 0x1234_6000),
743 ("auipc", 1, 0xfffff, 0x1000, 0x0000_0000),
744 ];
745
746 for (name, rd, immediate, pc, expected) in test_vectors {
747 let instruction = encode_u(name, rd, immediate);
748 let mut memory = vec![0; pc as usize + 4];
749 memory[pc as usize..pc as usize + 4].copy_from_slice(&instruction.to_le_bytes());
750 let mut cpu = RV32::new(memory);
751
752 cpu.pc = pc;
753 cpu.step().unwrap();
754
755 assert_eq!(
756 cpu.regs[rd as usize], expected,
757 "{name} failed: immediate={immediate:#010x}, \
758 pc={pc:#010x}, expected={expected:#010x}, got={:#010x}",
759 cpu.regs[rd as usize],
760 );
761 }
762 }
763
764 #[test]
765 fn test_ops_jumps() {
766 // JAL
767 let test_vectors: Vec<(&str, i32, u32)> = vec![
768 ("jal", 8, 0x0000_0008),
769 ("jal", -8, 0xffff_fff8),
770 ("jal", 2048, 0x0000_0800),
771 ("jal", -2048, 0xffff_f800),
772 ];
773
774 for (name, offset, expected_pc) in test_vectors {
775 let rd = 1;
776 let pc = 0x1000;
777
778 let instruction = encode_j(name, rd, offset);
779 let mut memory = vec![0; pc as usize + 4];
780 memory[pc as usize..pc as usize + 4].copy_from_slice(&instruction.to_le_bytes());
781 let mut cpu = RV32::new(memory);
782
783 cpu.pc = pc;
784 cpu.step().unwrap();
785
786 assert_eq!(
787 cpu.pc,
788 pc.wrapping_add(expected_pc),
789 "{name} failed: offset={offset}, \
790 expected_pc={:#010x}, got={:#010x}",
791 pc.wrapping_add(expected_pc),
792 cpu.pc,
793 );
794
795 assert_eq!(
796 cpu.regs[rd as usize],
797 pc.wrapping_add(4),
798 "{name} failed to save pc + 4: expected={:#010x}, got={:#010x}",
799 pc.wrapping_add(4),
800 cpu.regs[rd as usize],
801 );
802 }
803
804 // JALR
805 let test_vectors: Vec<(&str, u32, i32, u32)> = vec![
806 ("jalr", 0x2000, 4, 0x2004),
807 ("jalr", 0x2000, -4, 0x1ffc),
808 ("jalr", 0x2001, 0, 0x2000), // target bit 0 must be cleared.
809 ("jalr", 0x2000, 5, 0x2004),
810 ];
811
812 for (name, rs1_value, immediate, expected_pc) in test_vectors {
813 let rd = 1;
814 let rs1 = 2;
815 let pc = 0x1000;
816
817 let instruction = encode_i(name, rd, rs1, immediate);
818
819 let mut memory = vec![0; pc as usize + 4];
820 memory[pc as usize..pc as usize + 4].copy_from_slice(&instruction.to_le_bytes());
821 let mut cpu = RV32::new(memory);
822 cpu.pc = pc;
823 cpu.regs[rs1 as usize] = rs1_value;
824
825 cpu.step().unwrap();
826
827 assert_eq!(
828 cpu.pc, expected_pc,
829 "{name} failed: rs1={rs1_value:#010x}, \
830 immediate={immediate}, expected_pc={expected_pc:#010x}, \
831 got={:#010x}",
832 cpu.pc,
833 );
834
835 assert_eq!(
836 cpu.regs[rd as usize],
837 pc.wrapping_add(4),
838 "{name} failed to save pc + 4: expected={:#010x}, got={:#010x}",
839 pc.wrapping_add(4),
840 cpu.regs[rd as usize],
841 );
842 }
843 }
844}