arnaucube
arnaucube/rv32-rsPublic Self-hosted repository
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}