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 // I-type arithmetic & logical & shifts op'ns:
117 0x13 => {
118 result = Some(match funct3 {
119 // ADDI: add the sign-extended immediate to rs1, wrapping to
120 // 32 bits
121 0 => a.wrapping_add(imm),
122 // SLTI: signed comparison. Set rd to one if rs1 is less
123 // than the immediate as signed values
124 2 => u32::from((a as i32) < (imm as i32)),
125 // SLTIU: unsigned comparison. Compare rs1 and the
126 // sign-extended immediate as unsigned values
127 3 => u32::from(a < imm),
128 // XORI: write the bitwise XOR of rs1 and the sign-extended
129 // immediate
130 4 => a ^ imm,
131 // ORI: write the bitwise OR of rs1 and the sign-extended
132 // immediate
133 6 => a | imm,
134 // ANDI: write the bitwise AND of rs1 and the sign-extended
135 // immediate
136 7 => a & imm,
137 // SLLI: shift rs1 left by the five-bit immediate shift
138 // amount
139 1 if funct7 == 0 => a << rs2,
140 // SRLI: logical right shift. Shift rs1 right by the
141 // immediate amount, filling with zeros
142 5 if funct7 == 0 => a >> rs2,
143 // SRAI: arithmetic right shift. Shift rs1 right by the
144 // immediate amount, preserving its sign
145 5 if funct7 == 0x20 => ((a as i32) >> rs2) as u32,
146 _ => {
147 return Err(anyhow!(
148 "illegal instruction in I-type (arithmetic/logical): {}",
149 instruction
150 ))
151 }
152 });
153 }
154 // S-type op'ns:
155 0x23 => {
156 let size = match funct3 {
157 // SB: store the low byte of rs2 at rs1 + immediate
158 0 => 1,
159 // SH: store the low halfword of rs2 at rs1 + immediate
160 1 => 2,
161 // SW: store the full 32-bit value of rs2 at rs1 + immediate
162 2 => 4,
163 _ => return Err(anyhow!("illegal instruction in S-type: {}", instruction)),
164 };
165 // for the S-type imm, get it from 25-31 and 7-11 positions
166 let s_type_imm = ((instruction >> 25) << 5) | ((instruction >> 7) & 31);
167 let offset = sign_extend(s_type_imm, 12);
168 let range = self.range(a.wrapping_add(offset), size)?;
169 self.mem[range].copy_from_slice(&b.to_le_bytes()[..size]);
170 }
171
172 // R-type (register-register) integer operations
173 0x33 => {
174 result = Some(match (funct7, funct3) {
175 // ADD: add rs1 and rs2, wrapping to 32 bits
176 (0, 0) => a.wrapping_add(b),
177 // SUB: subtract rs2 from rs1, wrapping to 32 bits
178 (0x20, 0) => a.wrapping_sub(b),
179 // SLL: shift rs1 left by the low five bits of rs2
180 (0, 1) => a << (b & 31),
181 // SLT: set rd to one if rs1 is less than rs2 as signed
182 // values, else zero
183 (0, 2) => u32::from((a as i32) < (b as i32)),
184 // SLTU: set rd to one if rs1 is less than rs2 as unsigned
185 // values, else zero
186 (0, 3) => u32::from(a < b),
187 // XOR: write the bitwise XOR of rs1 and rs2 into rd
188 (0, 4) => a ^ b,
189 // SRL: shift rs1 right by the low five bits of rs2, filling
190 // with zeros
191 (0, 5) => a >> (b & 31),
192 // SRA: shift rs1 right by the low five bits of rs2,
193 // preserving its sign
194 (0x20, 5) => ((a as i32) >> (b & 31)) as u32,
195 // OR: write the bitwise OR of rs1 and rs2 into rd
196 (0, 6) => a | b,
197 // AND: write the bitwise AND of rs1 and rs2 into rd
198 (0, 7) => a & b,
199
200 // Note: MUL,DIV,REM are not part of RV32I, but they are in
201 // the M extension.
202
203 // MUL: write the low 32 bits of the product of rs1 and rs2
204 (1, 0) => a.wrapping_mul(b),
205 // DIV: signed quotient toward zero; zero divisors return
206 // all ones
207 (1, 4) => {
208 if b == 0 {
209 u32::MAX
210 } else {
211 (a as i32).wrapping_div(b as i32) as u32
212 }
213 }
214 // REM: signed remainder; zero divisors return the dividend
215 (1, 6) => {
216 if b == 0 {
217 a
218 } else {
219 (a as i32).wrapping_rem(b as i32) as u32
220 }
221 }
222
223 _ => return Err(anyhow!("illegal instruction in R-type: {}", instruction)),
224 });
225 }
226 // B-type operations
227 0x63 => {
228 let taken = match funct3 {
229 // BEQ: branch by the signed PC-relative offset if the
230 // registers are equal
231 0 => a == b,
232 // BNE: branch if the registers are unequal
233 1 => a != b,
234 // BLT: branch if rs1 is less than rs2 using signed comparison
235 4 => (a as i32) < (b as i32),
236 // BGE: branch if rs1 is at least rs2 using signed comparison
237 5 => (a as i32) >= (b as i32),
238 // BLTU: branch if rs1 is less than rs2 using unsigned comparison
239 6 => a < b,
240 // BGEU: branch if rs1 is at least rs2 using unsigned comparison
241 7 => a >= b,
242 _ => return Err(anyhow!("illegal instruction in B-type: {}", instruction)),
243 };
244 if taken {
245 // offset: reconstruct imm for B-type from positions 31,
246 // 31-25, 11-8, 7, and imm[0]=0
247 let b_type_imm = ((instruction >> 31) << 12)
248 | (((instruction >> 7) & 1) << 11)
249 | (((instruction >> 25) & 0x3f) << 5)
250 | (((instruction >> 8) & 15) << 1);
251 next_pc = self.pc.wrapping_add(sign_extend(b_type_imm, 13));
252 }
253 }
254
255 _ => return Err(anyhow!("illegal instruction: {}", instruction)),
256 }
257
258 if next_pc & 3 != 0 {
259 return Err(anyhow!("misaligned, address: {}", next_pc,));
260 }
261 if let Some(value) = result {
262 self.set_register(rd, value);
263 }
264 self.pc = next_pc;
265 Ok(())
266 }
267}
268
269// sign-extend a smaller signed number to 32 bits
270fn sign_extend(value: u32, bits: u32) -> u32 {
271 ((value << (32 - bits)) as i32 >> (32 - bits)) as u32
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 const OPCODE_R: u32 = 0b0110011;
279 const OPCODE_LOAD: u32 = 0b0000011;
280
281 // encodes R-type instruction
282 fn encode_r(name: &str, rd: u32, rs1: u32, rs2: u32) -> u32 {
283 let (funct7, funct3) = match name {
284 "add" => (0b0000000, 0b000),
285 "sub" => (0b0100000, 0b000),
286 "sll" => (0b0000000, 0b001),
287 "slt" => (0b0000000, 0b010),
288 "sltu" => (0b0000000, 0b011),
289 "xor" => (0b0000000, 0b100),
290 "srl" => (0b0000000, 0b101),
291 "sra" => (0b0100000, 0b101),
292 "or" => (0b0000000, 0b110),
293 "and" => (0b0000000, 0b111),
294 // M extension
295 "mul" => (0b0000001, 0b000),
296 "div" => (0b0000001, 0b100),
297 "rem" => (0b0000001, 0b110),
298 _ => panic!("unknown R-type instruction: {name}"),
299 };
300
301 (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_R
302 }
303 fn encode_i(name: &str, rd: u32, rs1: u32, imm: i32) -> u32 {
304 let (funct3, opcode) = match name {
305 // loads: opcode = 0x03
306 "lb" => (0b000, OPCODE_LOAD),
307 "lh" => (0b001, OPCODE_LOAD),
308 "lw" => (0b010, OPCODE_LOAD),
309 "lbu" => (0b100, OPCODE_LOAD),
310 "lhu" => (0b101, OPCODE_LOAD),
311 // arithmetic & logic & shifts: opcode = 0x13
312 "addi" => (0b000, 0x13),
313 "slti" => (0b010, 0x13),
314 "sltiu" => (0b011, 0x13),
315 "xori" => (0b100, 0x13),
316 "ori" => (0b110, 0x13),
317 "andi" => (0b111, 0x13),
318 "slli" => (0b001, 0x13),
319 "srli" => (0b101, 0x13),
320 "srai" => (0b101, 0x13),
321
322 _ => panic!("unknown I-type instruction: {name}"),
323 };
324 let funct7 = if name == "srai" {
325 0b010_0000
326 } else {
327 0b000_0000
328 };
329
330 let imm = (imm as u32) & 0xfff;
331
332 (imm << 20) | (funct7 << 25) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | opcode
333 }
334 fn encode_s(name: &str, rs1: u32, rs2: u32, imm: i32) -> u32 {
335 let (opcode, funct3) = match name {
336 "sb" => (0b0100011, 0b000),
337 "sh" => (0b0100011, 0b001),
338 "sw" => (0b0100011, 0b010),
339 _ => panic!("unknown store instruction: {name}"),
340 };
341
342 let imm = imm as u32;
343
344 let imm_11_5 = (imm >> 5) & 0x7f;
345 let imm_4_0 = imm & 0x1f;
346
347 (imm_11_5 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (imm_4_0 << 7) | opcode
348 }
349 fn encode_b(name: &str, rs1: u32, rs2: u32, offset: i32) -> u32 {
350 let funct3 = match name {
351 "beq" => 0b000,
352 "bne" => 0b001,
353 "blt" => 0b100,
354 "bge" => 0b101,
355 "bltu" => 0b110,
356 "bgeu" => 0b111,
357 _ => panic!("unknown B-type instruction: {name}"),
358 };
359
360 assert!(
361 offset % 2 == 0,
362 "B-type branch offset must be 2-byte aligned"
363 );
364
365 assert!(
366 (-4096..=4094).contains(&offset),
367 "B-type branch offset out of range: {offset}"
368 );
369
370 let imm = offset as u32;
371
372 let imm12 = (imm >> 12) & 0x1;
373 let imm11 = (imm >> 11) & 0x1;
374 let imm10_5 = (imm >> 5) & 0x3f;
375 let imm4_1 = (imm >> 1) & 0xf;
376
377 (imm12 << 31)
378 | (imm10_5 << 25)
379 | (rs2 << 20)
380 | (rs1 << 15)
381 | (funct3 << 12)
382 | (imm4_1 << 8)
383 | (imm11 << 7)
384 | 0b1100011
385 }
386
387 // R-type ops test
388 #[test]
389 fn test_ops_r_type() {
390 let test_vectors = vec![
391 // (op, rs1, rs2, rd)
392 ("add", 10, 20, 30),
393 ("add", u32::MAX, 1, 0), // overflow
394 ("sub", 30, 20, 10),
395 ("sub", 0, 1, u32::MAX), // overflow
396 ("sll", 1, 4, 16),
397 ("sll", 1, 31, 0x8000_0000), // SLL maximum shift
398 ("sll", 1, 32, 1), // SLL shift wrapping
399 ("slt", (-10i32) as u32, 20, 1),
400 ("sltu", 10, 20, 1),
401 ("slt", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0
402 ("slt", 0xffff_ffff, 0, 1), // signed vs unsigned distinction
403 ("sltu", 0xffff_ffff, 0, 0),
404 ("xor", 0b1010, 0b1100, 0b0110),
405 ("srl", 0x8000_0000, 4, 0x0800_0000),
406 ("srl", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill
407 ("sra", 0x8000_0000, 4, 0xf800_0000),
408 ("sra", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill
409 ("sra", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1
410 ("or", 0b1010, 0b1100, 0b1110),
411 ("and", 0b1010, 0b1100, 0b1000),
412 ];
413 test_ops_r_types_opt(test_vectors);
414 }
415 #[test]
416 fn test_ops_m_extension() {
417 let test_vectors = vec![
418 ("mul", 10, 20, 200),
419 ("mul", 0, 123, 0),
420 ("mul", 1, u32::MAX, u32::MAX),
421 ("mul", u32::MAX, 2, u32::MAX - 1), // result wraps to lower 32 bits
422 ("mul", (-1i32) as u32, 10, (-10i32) as u32), // -1 * 10 = -10
423 ("mul", (-1i32) as u32, (-1i32) as u32, 1), // -1 * -1 = 1
424 ("mul", 0x8000_0000, 2, 0), // INT_MIN * 2 = 0 modulo 2^32
425 ("mul", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN * -1 also wraps to INT_MIN
426 ("div", 20, 5, 4),
427 ("div", 21, 5, 4), // truncates toward zero
428 ("div", 0, 10, 0),
429 ("div", 10, 1, 10),
430 ("div", 10, (-1i32) as u32, (-10i32) as u32),
431 ("div", (-20i32) as u32, 5, (-4i32) as u32), // negative dividend
432 ("div", 20, (-5i32) as u32, (-4i32) as u32), // negative divisor
433 ("div", (-20i32) as u32, (-5i32) as u32, 4), // negative / negative
434 ("div", (-21i32) as u32, 5, (-4i32) as u32), // truncation toward zero: -21 / 5 = -4
435 // RISC-V special case:
436 ("div", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN / -1 = INT_MIN (overflow)
437 ("div", 10, 0, u32::MAX), // division by zero returns -1 for signed DIV
438 ("div", 0x8000_0000, 0, u32::MAX),
439 ("rem", 20, 5, 0),
440 ("rem", 21, 5, 1),
441 ("rem", 0, 10, 0),
442 ("rem", 10, 1, 0),
443 ("rem", 23, 5, 3), // positive remainder
444 ("rem", (-23i32) as u32, 5, (-3i32) as u32), // remainder follows the sign of the dividend
445 ("rem", 23, (-5i32) as u32, 3), // negative divisor does not change the sign of the remainder
446 ("rem", (-23i32) as u32, (-5i32) as u32, (-3i32) as u32), // negative / negative
447 ("rem", 3, 5, 3), // dividend smaller than divisor
448 // RISC-V special case:
449 ("rem", 0x8000_0000, 0xffff_ffff, 0), // INT_MIN % -1 = 0.
450 ("rem", 123, 0, 123), // remainder by zero returns the dividend.
451 ("rem", u32::MAX, 0, u32::MAX),
452 ("rem", 0x8000_0000, 0, 0x8000_0000),
453 ];
454 test_ops_r_types_opt(test_vectors);
455 }
456 fn test_ops_r_types_opt(test_vectors: Vec<(&str, u32, u32, u32)>) {
457 for (name, rs1_value, rs2_value, expected) in test_vectors {
458 let rd = 3;
459 let rs1 = 1;
460 let rs2 = 2;
461
462 let instruction = encode_r(name, rd, rs1, rs2);
463
464 let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
465
466 cpu.regs[rs1 as usize] = rs1_value;
467 cpu.regs[rs2 as usize] = rs2_value;
468
469 cpu.step().unwrap();
470
471 assert_eq!(
472 cpu.regs[rd as usize], expected,
473 "{name} failed:
474 rs1={rs1_value:#010x}, rs2={rs2_value:#010x},
475 expected={expected:#010x}, got={:#010x}",
476 cpu.regs[rd as usize],
477 );
478 }
479 }
480
481 // I-type ops test
482 #[test]
483 fn test_ops_load() {
484 let test_vectors: Vec<(&str, u32, i32, u32)> = vec![
485 ("lb", 0x0000_0080, 0, 0xffff_ff80),
486 ("lbu", 0x0000_0080, 0, 0x0000_0080),
487 ("lb", 0x0000_00ff, 0, 0xffff_ffff),
488 ("lbu", 0x0000_00ff, 0, 0x0000_00ff),
489 ("lh", 0x0000_8000, 0, 0xffff_8000),
490 ("lhu", 0x0000_8000, 0, 0x0000_8000),
491 ("lh", 0x0000_ffff, 0, 0xffff_ffff),
492 ("lhu", 0x0000_ffff, 0, 0x0000_ffff),
493 ];
494
495 for (name, memory_value, address, expected) in test_vectors {
496 let rd = 1;
497 let rs1 = 2;
498
499 let instruction = encode_i(name, rd, rs1, address);
500
501 let mut memory = instruction.to_le_bytes().to_vec();
502 let data_address = memory.len() as u32;
503 memory.extend_from_slice(&memory_value.to_le_bytes());
504 let mut cpu = RV32::new(memory);
505
506 cpu.regs[rs1 as usize] = data_address;
507
508 cpu.step().unwrap();
509
510 assert_eq!(
511 cpu.regs[rd as usize], expected,
512 "{name} failed: memory={memory_value:#010x},
513 address={address}, \
514 expected={expected:#010x},
515 got={:#010x}",
516 cpu.regs[rd as usize],
517 );
518 }
519 }
520 #[test]
521 fn test_ops_i_type_arithmetic() {
522 // almost same test vectors as for `test_ops_r_type`, same names with
523 // `i` as suffix
524 let test_vectors: Vec<(&str, u32, i32, u32)> = vec![
525 // (name, rs1, immediate, expected rd)
526 ("addi", 10, 20, 30),
527 ("addi", u32::MAX, 1, 0), // overflow
528 ("slli", 1, 4, 16),
529 ("slli", 1, 31, 0x8000_0000), // SLL maximum shift
530 ("slti", (-10i32) as u32, 20, 1),
531 ("sltiu", 10, 20, 1),
532 ("slti", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0
533 ("slti", 0xffff_ffff, 0, 1), // signed vs unsigned distinction
534 ("sltiu", 0xffff_ffff, 0, 0),
535 ("xori", 0b1010, 0b1100, 0b0110),
536 ("srli", 0x8000_0000, 4, 0x0800_0000),
537 ("srli", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill
538 ("srai", 0x8000_0000, 4, 0xf800_0000),
539 ("srai", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill
540 ("srai", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1
541 ("ori", 0b1010, 0b1100, 0b1110),
542 ("andi", 0b1010, 0b1100, 0b1000),
543 ];
544
545 for (name, rs1_value, imm, expected) in test_vectors {
546 dbg!(name);
547 let rd = 1;
548 let rs1 = 2;
549
550 let instruction = encode_i(name, rd, rs1, imm);
551 let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
552
553 cpu.regs[rs1 as usize] = rs1_value;
554
555 cpu.step().unwrap();
556
557 assert_eq!(
558 cpu.regs[rd as usize], expected,
559 "{name} failed: rs1={rs1_value:#010x}, \
560 imm={imm}, \
561 expected={expected:#010x}, \
562 got={:#010x}",
563 cpu.regs[rd as usize],
564 );
565 }
566 }
567
568 // S-type ops test
569 #[test]
570 fn test_ops_store() {
571 let test_vectors: Vec<(&str, u32, i32)> = vec![
572 ("sb", 0x0000_0080, 0),
573 ("sb", 0x0000_00ff, 0),
574 ("sb", 0x1234_5678, 0),
575 ("sh", 0x0000_8000, 0),
576 ("sh", 0x0000_ffff, 0),
577 ("sw", 0x1234_5678, 0),
578 ];
579
580 for (name, value, address) in test_vectors {
581 let rs1 = 1;
582 let rs2 = 2;
583
584 let instruction = encode_s(name, rs1, rs2, address);
585
586 let mut memory = instruction.to_le_bytes().to_vec();
587 let data_address = memory.len() as u32;
588
589 // add space for the value being stored
590 memory.extend_from_slice(&[0u8; 4]);
591
592 let mut cpu = RV32::new(memory);
593
594 cpu.regs[rs1 as usize] = data_address;
595 cpu.regs[rs2 as usize] = value;
596
597 cpu.step().unwrap();
598
599 let stored = u32::from_le_bytes(
600 cpu.mem[data_address as usize..data_address as usize + 4]
601 .try_into()
602 .unwrap(),
603 );
604
605 let expected = match name {
606 "sb" => value & 0xff,
607 "sh" => value & 0xffff,
608 "sw" => value,
609 _ => unreachable!(),
610 };
611
612 assert_eq!(
613 stored, expected,
614 "{name} failed: value={value:#010x},
615 address={address}, \
616 expected={expected:#010x},
617 got={stored:#010x}",
618 );
619 }
620 }
621
622 // B-type ops test
623 #[test]
624 fn test_ops_branch() {
625 let test_vectors: Vec<(&str, u32, u32, i32, bool)> = vec![
626 // (name, rs1 value, rs2 value, offset, taken)
627 ("beq", 10, 10, 8, true),
628 ("beq", 10, 20, 8, false),
629 ("bne", 10, 20, 8, true),
630 ("bne", 10, 10, 8, false),
631 ("blt", 10, 20, 8, true),
632 ("blt", 20, 10, 8, false),
633 ("bge", 20, 10, 8, true),
634 ("bge", 10, 20, 8, false),
635 ("bltu", 10, 20, 8, true),
636 ("bltu", 20, 10, 8, false),
637 ("bgeu", 20, 10, 8, true),
638 ("bgeu", 10, 20, 8, false),
639 ("beq", 10, 10, -4, true), // negative branch offsets
640 ("bne", 10, 20, -4, true),
641 ("blt", 0xffff_ffff, 0, 8, true), // signed vs unsigned comparison
642 ("bltu", 0xffff_ffff, 0, 8, false),
643 ("bge", 0xffff_ffff, 0, 8, false),
644 ("bgeu", 0xffff_ffff, 0, 8, true),
645 ];
646
647 for (name, rs1_value, rs2_value, offset, taken) in test_vectors {
648 let rs1 = 1;
649 let rs2 = 2;
650
651 let instruction = encode_b(name, rs1, rs2, offset);
652 let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
653
654 cpu.regs[rs1 as usize] = rs1_value;
655 cpu.regs[rs2 as usize] = rs2_value;
656
657 let initial_pc = cpu.pc;
658
659 cpu.step().unwrap();
660
661 let expected_pc = if taken {
662 initial_pc.wrapping_add(offset as u32)
663 } else {
664 initial_pc.wrapping_add(4)
665 };
666
667 assert_eq!(
668 cpu.pc, expected_pc,
669 "{name} failed: rs1={rs1_value:#010x}, \
670 rs2={rs2_value:#010x}, offset={offset}, \
671 expected_pc={expected_pc:#010x}, \
672 got={:#010x}",
673 cpu.pc,
674 );
675 }
676 }
677}