more compressed instructions

This commit is contained in:
nora 2025-03-10 16:11:29 +01:00
parent dd2e4001fd
commit 957b6a263a
3 changed files with 265 additions and 55 deletions

View file

@ -1,4 +1,4 @@
use crate::inst::{AmoOp, Inst, InstCode}; use crate::inst::{AmoOp, Inst, InstCode, IsCompressed};
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
ops::{Index, IndexMut}, ops::{Index, IndexMut},
@ -162,12 +162,16 @@ impl Emulator {
fn step(&mut self) -> Result<(), Status> { fn step(&mut self) -> Result<(), Status> {
let code = self.mem.load_u32(self.pc)?; let code = self.mem.load_u32(self.pc)?;
let inst = Inst::decode(code)?; let (inst, was_compressed) = Inst::decode(code)?;
if self.debug { if self.debug {
println!("executing 0x{:x} {inst:?}", self.pc); println!("executing 0x{:x} {inst:?}", self.pc);
} }
let next_pc = self.pc.wrapping_add(match was_compressed {
IsCompressed::Yes => 2,
IsCompressed::No => 4,
});
let mut jumped = false; let mut jumped = false;
match inst { match inst {
@ -175,13 +179,13 @@ impl Emulator {
Inst::Auipc { uimm, dest } => self[dest] = self.pc.wrapping_add(uimm), Inst::Auipc { uimm, dest } => self[dest] = self.pc.wrapping_add(uimm),
Inst::Jal { offset, dest } => { Inst::Jal { offset, dest } => {
let target = self.pc.wrapping_add(offset); let target = self.pc.wrapping_add(offset);
self[dest] = self.pc.wrapping_add(4); self[dest] = next_pc;
self.set_pc(target)?; self.set_pc(target)?;
jumped = true; jumped = true;
} }
Inst::Jalr { offset, base, dest } => { Inst::Jalr { offset, base, dest } => {
let target = self[base].wrapping_add(offset) & !1; let target = self[base].wrapping_add(offset) & !1;
self[dest] = self.pc.wrapping_add(4); self[dest] = next_pc;
self.set_pc(target)?; self.set_pc(target)?;
jumped = true; jumped = true;
} }
@ -406,7 +410,7 @@ impl Emulator {
} }
if !jumped { if !jumped {
self.set_pc(self.pc + 4)?; self.set_pc(next_pc)?;
} }
Ok(()) Ok(())
} }

View file

@ -5,77 +5,127 @@ use std::ops::RangeInclusive;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
#[rustfmt::skip] #[rustfmt::skip]
pub enum Inst { pub enum Inst {
/// Load Upper Immediate
Lui { uimm: u32, dest: Reg }, Lui { uimm: u32, dest: Reg },
/// Add Upper Immediate to PC
Auipc { uimm: u32, dest: Reg }, Auipc { uimm: u32, dest: Reg },
/// Jump And Link
Jal { offset: u32, dest: Reg }, Jal { offset: u32, dest: Reg },
/// Jump And Link Register (indirect)
Jalr { offset: u32, base: Reg, dest: Reg }, Jalr { offset: u32, base: Reg, dest: Reg },
/// Branch Equal
Beq { offset: u32, src1: Reg, src2: Reg }, Beq { offset: u32, src1: Reg, src2: Reg },
/// Branch Not Equal
Bne { offset: u32, src1: Reg, src2: Reg }, Bne { offset: u32, src1: Reg, src2: Reg },
/// Branch Less Than (signed)
Blt { offset: u32, src1: Reg, src2: Reg }, Blt { offset: u32, src1: Reg, src2: Reg },
/// Branch Greater or Equal (signed)
Bge { offset: u32, src1: Reg, src2: Reg }, Bge { offset: u32, src1: Reg, src2: Reg },
/// Branch Less Than Unsigned
Bltu { offset: u32, src1: Reg, src2: Reg }, Bltu { offset: u32, src1: Reg, src2: Reg },
/// Branch Greater or Equal Unsigned
Bgeu { offset: u32, src1: Reg, src2: Reg }, Bgeu { offset: u32, src1: Reg, src2: Reg },
/// Load Byte (sign-ext)
Lb { offset: u32, dest: Reg, base: Reg }, Lb { offset: u32, dest: Reg, base: Reg },
/// Load Unsigned Byte (zero-ext)
Lbu { offset: u32, dest: Reg, base: Reg }, Lbu { offset: u32, dest: Reg, base: Reg },
/// Load Half (sign-ext)
Lh { offset: u32, dest: Reg, base: Reg }, Lh { offset: u32, dest: Reg, base: Reg },
/// Load Unsigned Half (zero-ext)
Lhu { offset: u32, dest: Reg, base: Reg }, Lhu { offset: u32, dest: Reg, base: Reg },
/// Load Word
Lw { offset: u32, dest: Reg, base: Reg }, Lw { offset: u32, dest: Reg, base: Reg },
/// Store Byte
Sb { offset: u32, src: Reg, base: Reg }, Sb { offset: u32, src: Reg, base: Reg },
/// Store Half
Sh { offset: u32, src: Reg, base: Reg }, Sh { offset: u32, src: Reg, base: Reg },
/// Store Word
Sw { offset: u32, src: Reg, base: Reg }, Sw { offset: u32, src: Reg, base: Reg },
/// Add Immediate
Addi { imm: u32, dest: Reg, src1: Reg }, Addi { imm: u32, dest: Reg, src1: Reg },
/// Set Less Than Immediate (signed)
Slti { imm: u32, dest: Reg, src1: Reg }, Slti { imm: u32, dest: Reg, src1: Reg },
/// Set Less Than Immediate Unsigned
Sltiu { imm: u32, dest: Reg, src1: Reg }, Sltiu { imm: u32, dest: Reg, src1: Reg },
/// XOR Immediate
Xori { imm: u32, dest: Reg, src1: Reg }, Xori { imm: u32, dest: Reg, src1: Reg },
/// OR Immediate
Ori { imm: u32, dest: Reg, src1: Reg }, Ori { imm: u32, dest: Reg, src1: Reg },
/// AND Immediate
Andi { imm: u32, dest: Reg, src1: Reg }, Andi { imm: u32, dest: Reg, src1: Reg },
/// Shift Left Logical Immediate
Slli { imm: u32, dest: Reg, src1: Reg }, Slli { imm: u32, dest: Reg, src1: Reg },
/// Shift Right Logical Immediate (unsigned)
Srli { imm: u32, dest: Reg, src1: Reg }, Srli { imm: u32, dest: Reg, src1: Reg },
/// Shift Right Arithmetic Immediate (signed)
Srai { imm: u32, dest: Reg, src1: Reg }, Srai { imm: u32, dest: Reg, src1: Reg },
/// Add
Add { dest: Reg, src1: Reg, src2: Reg }, Add { dest: Reg, src1: Reg, src2: Reg },
/// Subtract
Sub { dest: Reg, src1: Reg, src2: Reg }, Sub { dest: Reg, src1: Reg, src2: Reg },
/// Shift Left Logical
Sll { dest: Reg, src1: Reg, src2: Reg }, Sll { dest: Reg, src1: Reg, src2: Reg },
/// Set Less Than (signed)
Slt { dest: Reg, src1: Reg, src2: Reg }, Slt { dest: Reg, src1: Reg, src2: Reg },
/// Set Less Than Unsigned
Sltu { dest: Reg, src1: Reg, src2: Reg }, Sltu { dest: Reg, src1: Reg, src2: Reg },
/// XOR
Xor { dest: Reg, src1: Reg, src2: Reg }, Xor { dest: Reg, src1: Reg, src2: Reg },
/// Shift Right Logical (unsigned)
Srl { dest: Reg, src1: Reg, src2: Reg }, Srl { dest: Reg, src1: Reg, src2: Reg },
/// Shift Right Arithmetic (unsigned)
Sra { dest: Reg, src1: Reg, src2: Reg }, Sra { dest: Reg, src1: Reg, src2: Reg },
/// OR
Or { dest: Reg, src1: Reg, src2: Reg }, Or { dest: Reg, src1: Reg, src2: Reg },
/// AND
And { dest: Reg, src1: Reg, src2: Reg }, And { dest: Reg, src1: Reg, src2: Reg },
/// Memory Fence
Fence { fence: Fence }, Fence { fence: Fence },
/// ECALL, call into environment
Ecall, Ecall,
/// EBREAK, break into debugger
Ebreak, Ebreak,
// M // ------------- M extension -------------
/// Multiply
Mul { dest: Reg, src1: Reg, src2: Reg }, Mul { dest: Reg, src1: Reg, src2: Reg },
/// Mul Upper Half Signed-Signed
Mulh { dest: Reg, src1: Reg, src2: Reg }, Mulh { dest: Reg, src1: Reg, src2: Reg },
/// Mul Upper Half Signed-Unsigned
Mulhsu { dest: Reg, src1: Reg, src2: Reg }, Mulhsu { dest: Reg, src1: Reg, src2: Reg },
/// Mul Upper Half Unsigned-Unsigned
Mulhu { dest: Reg, src1: Reg, src2: Reg }, Mulhu { dest: Reg, src1: Reg, src2: Reg },
/// Divide (signed)
Div { dest: Reg, src1: Reg, src2: Reg }, Div { dest: Reg, src1: Reg, src2: Reg },
/// Divide Unsigned
Divu { dest: Reg, src1: Reg, src2: Reg }, Divu { dest: Reg, src1: Reg, src2: Reg },
/// Remainder (signed)
Rem { dest: Reg, src1: Reg, src2: Reg }, Rem { dest: Reg, src1: Reg, src2: Reg },
/// Remainder Unsigned
Remu { dest: Reg, src1: Reg, src2: Reg }, Remu { dest: Reg, src1: Reg, src2: Reg },
// A // ------------- A extension -------------
/// Load-Reserved Word
LrW { LrW {
order: AmoOrdering, order: AmoOrdering,
dest: Reg, dest: Reg,
addr: Reg, addr: Reg,
}, },
/// Store-Conditional Word
ScW { ScW {
order: AmoOrdering, order: AmoOrdering,
dest: Reg, dest: Reg,
addr: Reg, addr: Reg,
src: Reg, src: Reg,
}, },
/// Atomic Memory Operation
AmoW { AmoW {
order: AmoOrdering, order: AmoOrdering,
op: AmoOp, op: AmoOp,
@ -337,7 +387,7 @@ impl Display for AmoOp {
} }
} }
fn sign_extend32(value: u32, size: u32) -> u32 { fn sign_extend(value: u32, size: u32) -> u32 {
assert!(size <= u32::BITS); assert!(size <= u32::BITS);
let sign = value >> (size - 1); let sign = value >> (size - 1);
let imm = if sign == 1 { let imm = if sign == 1 {
@ -347,16 +397,6 @@ fn sign_extend32(value: u32, size: u32) -> u32 {
}; };
imm imm
} }
fn sign_extend16(value: u16, size: u16) -> u16 {
assert!(size <= u16::BITS as u16);
let sign = value >> (size - 1);
let imm = if sign == 1 {
(u16::MAX << size) | value
} else {
value
};
imm
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct InstCode(u32); pub struct InstCode(u32);
@ -383,7 +423,7 @@ impl InstCode {
let this_size = from.end() - from.start() + 1; let this_size = from.end() - from.start() + 1;
size = size.max(*to + this_size); size = size.max(*to + this_size);
} }
sign_extend32(imm, size) sign_extend(imm, size)
} }
fn opcode(self) -> u32 { fn opcode(self) -> u32 {
@ -431,39 +471,55 @@ impl Debug for InstCode {
pub struct InstCodeC(u16); pub struct InstCodeC(u16);
impl InstCodeC { impl InstCodeC {
fn extract(self, range: RangeInclusive<u16>) -> u16 { fn extract(self, range: RangeInclusive<u32>) -> u32 {
let end_span = 32 - (range.end() + 1); let end_span = u16::BITS - (range.end() + 1);
(self.0 << (end_span)) >> (end_span + range.start()) ((self.0 << (end_span)) >> (end_span + range.start())) as u32
} }
fn immediate_u(self, mappings: &[(RangeInclusive<u16>, u16)]) -> u16 { fn immediate_u(self, mappings: &[(RangeInclusive<u32>, u32)]) -> u32 {
let mut imm = 0; let mut imm = 0;
for (from, to) in mappings { for (from, to) in mappings {
let value = self.extract(from.clone()); let value = self.extract(from.clone());
imm |= value << to; imm |= value << to;
} }
imm imm as u32
} }
fn immediate_s(self, mappings: &[(RangeInclusive<u32>, u32)]) -> u32 {
fn immediate_s(self, mappings: &[(RangeInclusive<u16>, u16)]) -> u16 {
let mut imm = 0; let mut imm = 0;
let mut size = 0; let mut size = 0;
for (from, to) in mappings { for (from, to) in mappings {
assert!(from.start() <= from.end());
let value = self.extract(from.clone()); let value = self.extract(from.clone());
imm |= value << to; imm |= value << to;
let this_size = from.end() - from.start() + 1; let this_size = from.end() - from.start() + 1;
size = size.max(*to + this_size); size = size.max(*to + this_size);
} }
sign_extend16(imm, size) sign_extend(imm, size)
} }
fn op(self) -> u16 { fn quadrant(self) -> u16 {
self.0 & 0b11 self.0 & 0b11
} }
fn funct3(self) -> u16 { fn funct3(self) -> u32 {
self.extract(13..=15) self.extract(13..=15)
} }
/// rd/rs1 (7..=11)
fn rd(self) -> Reg { fn rd(self) -> Reg {
Reg(self.extract(7..=11) as u32) Reg(self.extract(7..=11) as u32)
} }
fn rs2(self) -> Reg {
Reg(self.extract(2..=6) as u32)
}
/// rs1' (7..=9)
fn rs1_short(self) -> Reg {
let smol_reg = self.extract(7..=9);
// map to x8..=x15
Reg(smol_reg as u32 + 8)
}
/// rs2' (2..=4)
fn rs2_short(self) -> Reg {
let smol_reg = self.extract(2..=4);
// map to x8..=x15
Reg(smol_reg as u32 + 8)
}
} }
impl From<InstCodeC> for InstCode { impl From<InstCodeC> for InstCode {
@ -472,44 +528,191 @@ impl From<InstCodeC> for InstCode {
} }
} }
pub enum IsCompressed {
Yes,
No,
}
impl Inst { impl Inst {
pub fn decode(code: u32) -> Result<Inst, Status> { pub fn decode(code: u32) -> Result<(Inst, IsCompressed), Status> {
let is_compressed = (code & 0b11) != 0b11; let is_compressed = (code & 0b11) != 0b11;
if is_compressed { if is_compressed {
Self::decode_compressed(code as u16) Ok((Self::decode_compressed(code as u16)?, IsCompressed::Yes))
} else { } else {
Self::decode_normal(code) Ok((Self::decode_normal(code)?, IsCompressed::No))
} }
} }
fn decode_compressed(code: u16) -> Result<Inst, Status> { fn decode_compressed(code: u16) -> Result<Inst, Status> {
let code = InstCodeC(code); let code = InstCodeC(code);
if code.0 == 0 {
return Err(Status::IllegalInstruction(code.into(), "null instruction"));
}
let inst = match code.op() { let inst = match code.quadrant() {
// C0 // C0
0b00 => todo!(), 0b00 => match code.funct3() {
// C.LW -> lw \dest \offset(\base)
0b010 => Inst::Lw {
offset: code.immediate_u(&[(10..=12, 3), (5..=5, 6), (6..=6, 2)]),
dest: code.rs2_short(),
base: code.rs1_short(),
},
// C.SW -> sw \src, \offset(\base)
0b110 => Inst::Sw {
offset: code.immediate_u(&[(10..=12, 3), (5..=5, 6), (6..=6, 2)]),
src: code.rs2_short(),
base: code.rs1_short(),
},
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
},
// C1 // C1
0b01 => todo!(), 0b01 => match code.funct3() {
// C2 // C.ADDI -> addi \rd, \rd, \imm
0b10 => { 0b000 => Inst::Addi {
match code.funct3() { imm: code.immediate_s(&[(2..=6, 0), (12..=12, 5)]),
// C.LDSP dest: code.rd(),
010 => { src1: code.rd(),
let dest = code.rd(); },
if dest.0 == 0 { // C.JAL -> jal ra, \offset
return Err(Status::IllegalInstruction(code.into(), "rd")); 0b001 => Inst::Jal {
} offset: code.immediate_s(&[
let imm = code.immediate_s(&[(12..=12, 5), (4..=6, 2), (2..=3, 6)]); (2..=2, 5),
(3..=5, 1),
Inst::Lw { (6..=6, 7),
offset: imm as u32, (7..=7, 6),
dest, (8..=8, 10),
base: Reg::SP, (9..=10, 8),
(11..=11, 4),
(12..=12, 11),
]),
dest: Reg::RA,
},
// C.LI -> addi \rd, zero, \imm
0b010 => Inst::Addi {
imm: code.immediate_s(&[(2..=4, 0), (12..=12, 5)]),
dest: code.rd(),
src1: Reg::ZERO,
},
// C.J -> jal zero, \offset
0b101 => Inst::Jal {
offset: code.immediate_s(&[
(2..=2, 5),
(3..=5, 1),
(6..=6, 7),
(7..=7, 6),
(8..=8, 10),
(9..=10, 8),
(11..=11, 4),
(12..=12, 11),
]),
dest: Reg::ZERO,
},
0b011 => {
match code.rd().0 {
// C.ADDI16SP -> addi sp, sp, \imm
2 => Inst::Addi {
imm: code.immediate_s(&[
(2..=2, 2),
(3..=4, 7),
(5..=5, 6),
(6..=6, 4),
]),
dest: Reg::SP,
src1: Reg::SP,
},
// C.LUI -> lui \rd, \imm
_ => {
let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]);
if uimm == 0 {
return Err(Status::IllegalInstruction(code.into(), "imm"));
}
Inst::Lui {
uimm,
dest: code.rd(),
}
} }
} }
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
} }
} // C.BEQZ -> beq \rs1', zero, \offset
0b110 => Inst::Beq {
offset: code.immediate_s(&[
(2..=2, 5),
(3..=4, 1),
(5..=6, 6),
(10..=11, 3),
(12..=12, 8),
]),
src1: code.rs1_short(),
src2: Reg::ZERO,
},
// C.BEQZ -> bne \rs1', zero, \offset
0b111 => Inst::Bne {
offset: code.immediate_s(&[
(2..=2, 5),
(3..=4, 1),
(5..=6, 6),
(10..=11, 3),
(12..=12, 8),
]),
src1: code.rs1_short(),
src2: Reg::ZERO,
},
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
},
// C2
0b10 => match code.funct3() {
// C.LWSP -> lw \reg \offset(sp)
0b010 => {
let dest = code.rd();
if dest.0 == 0 {
return Err(Status::IllegalInstruction(code.into(), "rd"));
}
Inst::Lw {
offset: code.immediate_u(&[(12..=12, 5), (4..=6, 2), (2..=3, 6)]),
dest,
base: Reg::SP,
}
}
0b100 => {
let bit = code.extract(12..=12);
let rs2 = code.rs2();
let rd_rs1 = code.rd();
match (bit, rd_rs1.0, rs2.0) {
// C.JR -> jalr zero, 0(\rs1)
(0, _, 0) if rd_rs1.0 != 0 => Inst::Jalr {
offset: 0,
base: rd_rs1,
dest: Reg::ZERO,
},
// C.MV
(0, _, _) if rd_rs1.0 != 0 && rs2.0 != 0 => todo!(),
// C.EBREAK
(1, 0, 0) => Inst::Ebreak,
// C.JALR -> jalr ra, 0(\rs1)
(1, _, 0) if rd_rs1.0 != 0 => Inst::Jalr {
offset: 0,
base: rd_rs1,
dest: Reg::RA,
},
// C.ADD
(1, _, _) if rd_rs1.0 != 0 && rs2.0 != 0 => Inst::Add {
dest: todo!(),
src1: todo!(),
src2: todo!(),
},
_ => return Err(Status::IllegalInstruction(code.into(), "inst")),
}
}
// C.SWSP -> sw \reg \offset(sp)
0b110 => Inst::Sw {
offset: code.immediate_u(&[(7..=8, 6), (9..=12, 2)]),
src: code.rs2(),
base: Reg::SP,
},
_ => return Err(Status::IllegalInstruction(code.into(), "funct3")),
},
_ => return Err(Status::IllegalInstruction(code.into(), "op")), _ => return Err(Status::IllegalInstruction(code.into(), "op")),
}; };
Ok(inst) Ok(inst)

View file

@ -1,12 +1,15 @@
CC = clang -Wall -Wpedantic -target riscv32-unknown-linux-gnu -fuse-ld=lld -march=rv32i CC = clang -Wall -Wpedantic -target riscv32-unknown-linux-gnu -fuse-ld=lld -march=rv32imac
CC_STATIC = $(CC) -static -nostdlib -nodefaultlibs CC_STATIC = $(CC) -static -nostdlib -nodefaultlibs
RUSTC = rustc --target riscv32imac-unknown-none-elf RUSTC = rustc --target riscv32imac-unknown-none-elf
all: init init1 all: init init1 x
init: init.c init: init.c
$(CC_STATIC) init.c -o init $(CC_STATIC) init.c -o init
init1: init1.rs init1: init1.rs
$(RUSTC) init1.rs $(RUSTC) init1.rs
x: x.s
$(CC_STATIC) x.s -o x