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                    _ => return Err(anyhow!("illegal instruction (A): {}", instruction)),
 91                });
 92            }
 93            _ => return Err(anyhow!("illegal instruction (B): {}", instruction)),
 94        }
 95
 96        if next_pc & 3 != 0 {
 97            return Err(anyhow!("misaligned, address: {}", next_pc,));
 98        }
 99        if let Some(value) = result {
100            self.set_register(rd, value);
101        }
102        self.pc = next_pc;
103        Ok(())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    const OPCODE_R: u32 = 0b0110011;
112
113    // encodes R-type instruction
114    fn encode_r(name: &str, rd: u32, rs1: u32, rs2: u32) -> u32 {
115        let (funct7, funct3) = match name {
116            "add" => (0b0000000, 0b000),
117            "sub" => (0b0100000, 0b000),
118            "sll" => (0b0000000, 0b001),
119            "slt" => (0b0000000, 0b010),
120            "sltu" => (0b0000000, 0b011),
121            "xor" => (0b0000000, 0b100),
122            "srl" => (0b0000000, 0b101),
123            "sra" => (0b0100000, 0b101),
124            "or" => (0b0000000, 0b110),
125            "and" => (0b0000000, 0b111),
126            _ => panic!("unknown R-type instruction: {name}"),
127        };
128
129        (funct7 << 25) | (rs2 << 20) | (rs1 << 15) | (funct3 << 12) | (rd << 7) | OPCODE_R
130    }
131
132    #[test]
133    fn test_r_type_operations() {
134        let test_vectors = [
135            // (op, rs1, rs2, rd)
136            ("add", 10, 20, 30),
137            ("add", u32::MAX, 1, 0), // overflow
138            ("sub", 30, 20, 10),
139            ("sub", 0, 1, u32::MAX), // overflow
140            ("sll", 1, 4, 16),
141            ("sll", 1, 31, 0x8000_0000), // SLL maximum shift
142            ("sll", 1, 32, 1),           // SLL shift wrapping
143            ("slt", (-10i32) as u32, 20, 1),
144            ("sltu", 10, 20, 1),
145            ("slt", 0xffff_ffff, 0, 1), // Signed comparison: -1 < 0
146            ("slt", 0xffff_ffff, 0, 1), // Signed vs unsigned distinction
147            ("sltu", 0xffff_ffff, 0, 0),
148            ("xor", 0b1010, 0b1100, 0b0110),
149            ("srl", 0x8000_0000, 4, 0x0800_0000),
150            ("srl", 0x8000_0000, 1, 0x4000_0000), // Logical shift should zero-fill
151            ("sra", 0x8000_0000, 4, 0xf800_0000),
152            ("sra", 0x8000_0000, 1, 0xc000_0000), // Arithmetic shift should sign-fill
153            ("sra", 0xffff_ffff, 31, 0xffff_ffff), // Arithmetic shift of -1 stays -1
154            ("or", 0b1010, 0b1100, 0b1110),
155            ("and", 0b1010, 0b1100, 0b1000),
156        ];
157
158        for (name, rs1_value, rs2_value, expected) in test_vectors {
159            let rd = 3;
160            let rs1 = 1;
161            let rs2 = 2;
162
163            let instruction = encode_r(name, rd, rs1, rs2);
164
165            let mut cpu = RV32::new(instruction.to_le_bytes().to_vec());
166
167            cpu.regs[rs1 as usize] = rs1_value;
168            cpu.regs[rs2 as usize] = rs2_value;
169
170            cpu.step().unwrap();
171
172            assert_eq!(
173                cpu.regs[rd as usize], expected,
174                "{name} failed:
175                 rs1={rs1_value:#010x}, rs2={rs2_value:#010x}, 
176                 expected={expected:#010x}, got={:#010x}",
177                cpu.regs[rd as usize],
178            );
179        }
180    }
181}