arnaucube
arnaucube/rv32-rsPublic Self-hosted repository
rv32-rs/src/lib.rs
src/lib.rsRaw
  1use anyhow::{anyhow, Result};
  2
  3pub struct RV32 {
  4    regs: [u32; 32], // registers
  5    mem: Vec<u8>,    // memory
  6    pc: u32,
  7}
  8
  9impl RV32 {
 10    pub fn new(mem: Vec<u8>) -> Self {
 11        Self {
 12            regs: [0; 32],
 13            pc: 0,
 14            mem,
 15        }
 16    }
 17    pub fn set_register(&mut self, index: usize, value: u32) {
 18        assert!(index < 32, "register index must <32");
 19        if index != 0 {
 20            self.regs[index] = value;
 21        }
 22    }
 23    // validate the access
 24    fn range(&self, address: u32, size: usize) -> Result<std::ops::Range<usize>> {
 25        if !address.is_multiple_of(size as u32) {
 26            return Err(anyhow!("misaligned at address: {}", address));
 27        }
 28        let start = address as usize;
 29        let end = start
 30            .checked_add(size)
 31            .filter(|&end| end <= self.mem.len())
 32            .ok_or(anyhow!("access fault, address: {}", address))?;
 33        Ok(start..end)
 34    }
 35    fn read(&self, address: u32, size: usize) -> Result<u32> {
 36        let range = self.range(address, size)?;
 37        let mut bytes = [0; 4];
 38        bytes[..size].copy_from_slice(&self.mem[range]);
 39        Ok(u32::from_le_bytes(bytes))
 40    }
 41    pub fn step(&mut self) -> Result<()> {
 42        let instruction = self.read(self.pc, 4)?;
 43        let next_pc = self.pc.wrapping_add(4);
 44
 45        // R-type instruction:
 46        //                             R-type:
 47        // 31        25 24        20 19        15 14    12 11     7 6        0
 48        // ┌───────────┬────────────┬────────────┬────────┬────────┬─────────┐
 49        // │   funct7  │    rs2     │    rs1     │ funct3 │   rd   │ opcode  │
 50        // └───────────┴────────────┴────────────┴────────┴────────┴─────────┘
 51        let rd = ((instruction >> 7) & 31) as usize;
 52        let funct3 = (instruction >> 12) & 7;
 53        let rs1 = ((instruction >> 15) & 31) as usize;
 54        let rs2 = ((instruction >> 20) & 31) as usize;
 55        let funct7 = instruction >> 25;
 56
 57        let a = self.regs[rs1];
 58        let b = self.regs[rs2];
 59        let result: Option<u32>;
 60
 61        // 0x7f=0111_1111, match the lowest 7 bits to read the opcode
 62        match instruction & 0x7f {
 63            // R-type (register-register) integer operations
 64            0x33 => {
 65                result = Some(match (funct7, funct3) {
 66                    // ADD: add rs1 and rs2, wrapping to 32 bits
 67                    (0, 0) => a.wrapping_add(b),
 68                    // SUB: subtract rs2 from rs1, wrapping to 32 bits
 69                    (0x20, 0) => a.wrapping_sub(b),
 70                    // SLL: shift rs1 left by the low five bits of rs2
 71                    (0, 1) => a << (b & 31),
 72                    // SLT: set rd to one if rs1 is less than rs2 as signed
 73                    // values, else zero
 74                    (0, 2) => u32::from((a as i32) < (b as i32)),
 75                    // SLTU: set rd to one if rs1 is less than rs2 as unsigned
 76                    // values, else zero
 77                    (0, 3) => u32::from(a < b),
 78                    // XOR: write the bitwise XOR of rs1 and rs2 into rd
 79                    (0, 4) => a ^ b,
 80                    // SRL: shift rs1 right by the low five bits of rs2, filling
 81                    // with zeros
 82                    (0, 5) => a >> (b & 31),
 83                    // SRA: shift rs1 right by the low five bits of rs2,
 84                    // preserving its sign
 85                    (0x20, 5) => ((a as i32) >> (b & 31)) as u32,
 86                    // OR: write the bitwise OR of rs1 and rs2 into rd
 87                    (0, 6) => a | b,
 88                    // AND: write the bitwise AND of rs1 and rs2 into rd
 89                    (0, 7) => a & b,
 90
 91                    // Note: MUL,DIV,REM are not part of RV32I, but they are in
 92                    // the M extension.
 93
 94                    // MUL: write the low 32 bits of the product of rs1 and rs2
 95                    (1, 0) => a.wrapping_mul(b),
 96                    // DIV: signed quotient toward zero; zero divisors return
 97                    // all ones
 98                    (1, 4) => {
 99                        if b == 0 {
100                            u32::MAX
101                        } else {
102                            (a as i32).wrapping_div(b as i32) as u32
103                        }
104                    }
105                    // REM: signed remainder; zero divisors return the dividend
106                    (1, 6) => {
107                        if b == 0 {
108                            a
109                        } else {
110                            (a as i32).wrapping_rem(b as i32) as u32
111                        }
112                    }
113
114                    _ => return Err(anyhow!("illegal instruction (A): {}", instruction)),
115                });
116            }
117            _ => return Err(anyhow!("illegal instruction (B): {}", instruction)),
118        }
119
120        if next_pc & 3 != 0 {
121            return Err(anyhow!("misaligned, address: {}", next_pc,));
122        }
123        if let Some(value) = result {
124            self.set_register(rd, value);
125        }
126        self.pc = next_pc;
127        Ok(())
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    const OPCODE_R: u32 = 0b0110011;
136
137    // encodes R-type instruction
138    fn encode_r(name: &str, rd: u32, rs1: u32, rs2: u32) -> u32 {
139        let (funct7, funct3) = match name {
140            "add" => (0b0000000, 0b000),
141            "sub" => (0b0100000, 0b000),
142            "sll" => (0b0000000, 0b001),
143            "slt" => (0b0000000, 0b010),
144            "sltu" => (0b0000000, 0b011),
145            "xor" => (0b0000000, 0b100),
146            "srl" => (0b0000000, 0b101),
147            "sra" => (0b0100000, 0b101),
148            "or" => (0b0000000, 0b110),
149            "and" => (0b0000000, 0b111),
150            // M extension
151            "mul" => (0b0000001, 0b000),
152            "div" => (0b0000001, 0b100),
153            "rem" => (0b0000001, 0b110),
154            _ => panic!("unknown R-type instruction: {name}"),
155        };
156
157        (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_R
158    }
159
160    #[test]
161    fn test_ops_r_type() {
162        let test_vectors = vec![
163            // (op, rs1, rs2, rd)
164            ("add", 10, 20, 30),
165            ("add", u32::MAX, 1, 0), // overflow
166            ("sub", 30, 20, 10),
167            ("sub", 0, 1, u32::MAX), // overflow
168            ("sll", 1, 4, 16),
169            ("sll", 1, 31, 0x8000_0000), // SLL maximum shift
170            ("sll", 1, 32, 1),           // SLL shift wrapping
171            ("slt", (-10i32) as u32, 20, 1),
172            ("sltu", 10, 20, 1),
173            ("slt", 0xffff_ffff, 0, 1), // signed comparison: -1 < 0
174            ("slt", 0xffff_ffff, 0, 1), // signed vs unsigned distinction
175            ("sltu", 0xffff_ffff, 0, 0),
176            ("xor", 0b1010, 0b1100, 0b0110),
177            ("srl", 0x8000_0000, 4, 0x0800_0000),
178            ("srl", 0x8000_0000, 1, 0x4000_0000), // logical shift should zero-fill
179            ("sra", 0x8000_0000, 4, 0xf800_0000),
180            ("sra", 0x8000_0000, 1, 0xc000_0000), // arithmetic shift should sign-fill
181            ("sra", 0xffff_ffff, 31, 0xffff_ffff), // arithmetic shift of -1 stays -1
182            ("or", 0b1010, 0b1100, 0b1110),
183            ("and", 0b1010, 0b1100, 0b1000),
184        ];
185        test_ops_r_types_opt(test_vectors);
186    }
187    #[test]
188    fn test_ops_m_extension() {
189        let test_vectors = vec![
190            ("mul", 10, 20, 200),
191            ("mul", 0, 123, 0),
192            ("mul", 1, u32::MAX, u32::MAX),
193            ("mul", u32::MAX, 2, u32::MAX - 1), // result wraps to lower 32 bits
194            ("mul", (-1i32) as u32, 10, (-10i32) as u32), // -1 * 10 = -10
195            ("mul", (-1i32) as u32, (-1i32) as u32, 1), // -1 * -1 = 1
196            ("mul", 0x8000_0000, 2, 0),         // INT_MIN * 2 = 0 modulo 2^32
197            ("mul", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN * -1 also wraps to INT_MIN
198            ("div", 20, 5, 4),
199            ("div", 21, 5, 4), // truncates toward zero
200            ("div", 0, 10, 0),
201            ("div", 10, 1, 10),
202            ("div", 10, (-1i32) as u32, (-10i32) as u32),
203            ("div", (-20i32) as u32, 5, (-4i32) as u32), // negative dividend
204            ("div", 20, (-5i32) as u32, (-4i32) as u32), // negative divisor
205            ("div", (-20i32) as u32, (-5i32) as u32, 4), // negative / negative
206            ("div", (-21i32) as u32, 5, (-4i32) as u32), // truncation toward zero: -21 / 5 = -4
207            // RISC-V special case:
208            ("div", 0x8000_0000, 0xffff_ffff, 0x8000_0000), // INT_MIN / -1 = INT_MIN (overflow)
209            ("div", 10, 0, u32::MAX), // division by zero returns -1 for signed DIV
210            ("div", 0x8000_0000, 0, u32::MAX),
211            ("rem", 20, 5, 0),
212            ("rem", 21, 5, 1),
213            ("rem", 0, 10, 0),
214            ("rem", 10, 1, 0),
215            ("rem", 23, 5, 3),                           // Positive remainder.
216            ("rem", (-23i32) as u32, 5, (-3i32) as u32), // Remainder follows the sign of the dividend.
217            ("rem", 23, (-5i32) as u32, 3), // Negative divisor does not change the sign of the remainder.
218            ("rem", (-23i32) as u32, (-5i32) as u32, (-3i32) as u32), // Negative / negative.
219            ("rem", 3, 5, 3),               // Dividend smaller than divisor.
220            // RISC-V special case:
221            ("rem", 0x8000_0000, 0xffff_ffff, 0), // INT_MIN % -1 = 0.
222            ("rem", 123, 0, 123),                 // Remainder by zero returns the dividend.
223            ("rem", u32::MAX, 0, u32::MAX),
224            ("rem", 0x8000_0000, 0, 0x8000_0000),
225        ];
226        test_ops_r_types_opt(test_vectors);
227    }
228    fn test_ops_r_types_opt(test_vectors: Vec<(&str, u32, u32, u32)>) {
229        for (name, rs1_value, rs2_value, expected) in test_vectors {
230            let rd = 3;
231            let rs1 = 1;
232            let rs2 = 2;
233
234            let instruction = encode_r(name, rd, rs1, rs2);
235
236            let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
237
238            cpu.regs[rs1 as usize] = rs1_value;
239            cpu.regs[rs2 as usize] = rs2_value;
240
241            cpu.step().unwrap();
242
243            assert_eq!(
244                cpu.regs[rd as usize], expected,
245                "{name} failed:
246                 rs1={rs1_value:#010x}, rs2={rs2_value:#010x}, 
247                 expected={expected:#010x}, got={:#010x}",
248                cpu.regs[rd as usize],
249            );
250        }
251    }
252}