Skip to content

Commit

Permalink
emulator: Add Zba,Zbc,Zbs instruction sets
Browse files Browse the repository at this point in the history
And enables support in code generation to use them.
This saves X, Y, and Z bytes in the ROM, FMC, and Runtime,
respectively.

This has the negative side effect of making tests a little slower to run due to the quantity of macros used in generating all of the test cases.

This work extends the previous Zbb bit manipulation instruction set we supported to include the rest of the b family, all of which are supported by our VeeR CPU.

We also add missing tests for the Zbb instruction set.
  • Loading branch information
swenson committed Nov 27, 2024
1 parent f22cbca commit 3c220d5
Show file tree
Hide file tree
Showing 8 changed files with 2,628 additions and 135 deletions.
3 changes: 3 additions & 0 deletions .cargo/config
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
rustflags = [
"-C", "target-feature=+relax",
"-C", "target-feature=+unaligned-scalar-mem",
"-C", "target-feature=+zba",
"-C", "target-feature=+zbb",
"-C", "target-feature=+zbc",
"-C", "target-feature=+zbs",
]
1,701 changes: 1,701 additions & 0 deletions sw-emulator/lib/cpu/src/instr/bit.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions sw-emulator/lib/cpu/src/instr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ References:
--*/

mod auipc;
mod bit;
mod branch;
mod compression;
mod fence;
Expand Down
42 changes: 4 additions & 38 deletions sw-emulator/lib/cpu/src/instr/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ References:

use crate::cpu::Cpu;
use crate::types::{RvInstr32OpFunct3, RvInstr32OpFunct7, RvInstr32Opcode, RvInstr32R};
use crate::xreg_file::XReg;
use caliptra_emu_bus::Bus;
use caliptra_emu_types::{RvData, RvException};

Expand Down Expand Up @@ -161,44 +160,11 @@ impl<TBus: Bus> Cpu<TBus> {
}
}

// AND with inverted operand
(RvInstr32OpFunct3::Seven, RvInstr32OpFunct7::Andn) => val1 & !val2,

// OR with inverted operand
(RvInstr32OpFunct3::Six, RvInstr32OpFunct7::Orn) => val1 | !val2,

// Exclusive NOR
(RvInstr32OpFunct3::Four, RvInstr32OpFunct7::Xnor) => !(val1 ^ val2),

// Maximum
(RvInstr32OpFunct3::Six, RvInstr32OpFunct7::MinMaxClmul) => {
i32::max(val1 as i32, val2 as i32) as u32
}

// Maximum unsigned
(RvInstr32OpFunct3::Seven, RvInstr32OpFunct7::MinMaxClmul) => u32::max(val1, val2),

// Minimum
(RvInstr32OpFunct3::Four, RvInstr32OpFunct7::MinMaxClmul) => {
i32::min(val1 as i32, val2 as i32) as u32
}

// Minimum unsigned
(RvInstr32OpFunct3::Five, RvInstr32OpFunct7::MinMaxClmul) => u32::min(val1, val2),

// Zero-extend halfword
(RvInstr32OpFunct3::Four, RvInstr32OpFunct7::Zext) if instr.rs2() == XReg::X0 => {
val1 & 0xffff
}

// Rotate left
(RvInstr32OpFunct3::One, RvInstr32OpFunct7::Rotate) => val1.rotate_left(val2 & 0x1f),

// Rotate right
(RvInstr32OpFunct3::Five, RvInstr32OpFunct7::Rotate) => val1.rotate_right(val2 & 0x1f),

// Illegal instruction
_ => Err(RvException::illegal_instr(instr.0))?,
_ => match self.exec_bit_instr_op(instr, val1, val2) {
Some(val) => val,
_ => Err(RvException::illegal_instr(instr.0))?,
},
};

self.write_xreg(instr.rd(), data)
Expand Down
134 changes: 48 additions & 86 deletions sw-emulator/lib/cpu/src/instr/op_imm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,107 +39,69 @@ impl<TBus: Bus> Cpu<TBus> {
// Read the content of source register
let reg = self.read_xreg(instr.rs())?;

let data = match instr.funct3().into() {
// Add Immediate (`addi`) Instruction
RvInstr32OpImmFunct3::Addi => reg.wrapping_add(instr.imm() as u32) as RvData,

RvInstr32OpImmFunct3::Sli => {
match instr.funct7().into() {
// Shift Left Logical Immediate (`slli`) Instruction
RvInstr32OpImmFunct7::Slli => reg.wrapping_shl(instr.shamt()) as RvData,

// Count leading zeroes
RvInstr32OpImmFunct7::Bitmanip if instr.funct5() == 0b0_0000 => {
reg.leading_zeros()
}

// Count trailing zeroes
RvInstr32OpImmFunct7::Bitmanip if instr.funct5() == 0b0_0001 => {
reg.trailing_zeros()
}

// Count set bits
RvInstr32OpImmFunct7::Bitmanip if instr.funct5() == 0b0_0010 => {
reg.count_ones()
}

// Sign-extend byte
RvInstr32OpImmFunct7::Bitmanip if instr.funct5() == 0b0_0100 => {
reg as i8 as i32 as u32
let data = if let Some(data) = self.exec_bit_instr_op_imm(instr, reg) {
data
} else {
match instr.funct3().into() {
// Add Immediate (`addi`) Instruction
RvInstr32OpImmFunct3::Addi => reg.wrapping_add(instr.imm() as u32) as RvData,

RvInstr32OpImmFunct3::Sli => {
match instr.funct7().into() {
// Shift Left Logical Immediate (`slli`) Instruction
RvInstr32OpImmFunct7::Slli => reg.wrapping_shl(instr.shamt()) as RvData,

// Illegal Instruction
_ => Err(RvException::illegal_instr(instr.0))?,
}
}

// Sign-extend halfword
RvInstr32OpImmFunct7::Bitmanip if instr.funct5() == 0b0_0101 => {
reg as i16 as i32 as u32
// Set Less Than Immediate (`slti`) Instruction
RvInstr32OpImmFunct3::Slti => {
if (reg as i32) < instr.imm() {
1
} else {
0
}

// Illegal Instruction
_ => Err(RvException::illegal_instr(instr.0))?,
}
}

// Set Less Than Immediate (`slti`) Instruction
RvInstr32OpImmFunct3::Slti => {
if (reg as i32) < instr.imm() {
1
} else {
0
// Set Less Than Immediate Unsigned (`sltiu`) Instruction
RvInstr32OpImmFunct3::Sltiu => {
if reg < instr.imm() as u32 {
1
} else {
0
}
}
}

// Set Less Than Immediate Unsigned (`sltiu`) Instruction
RvInstr32OpImmFunct3::Sltiu => {
if reg < instr.imm() as u32 {
1
} else {
0
}
}
// Xor Immediate (`xori`) Instruction
RvInstr32OpImmFunct3::Xori => reg ^ instr.imm() as u32,

// Xor Immediate (`xori`) Instruction
RvInstr32OpImmFunct3::Xori => reg ^ instr.imm() as u32,
// Shift Right Immediate Instruction
RvInstr32OpImmFunct3::Sri => {
match instr.funct7().into() {
// Shift Right Logical Immediate (`srli`) Instruction
RvInstr32OpImmFunct7::Srli => reg.wrapping_shr(instr.shamt()) as RvData,

// Shift Right Immediate Instruction
RvInstr32OpImmFunct3::Sri => {
match instr.funct7().into() {
// Shift Right Logical Immediate (`srli`) Instruction
RvInstr32OpImmFunct7::Srli => reg.wrapping_shr(instr.shamt()) as RvData,
// Shift Right Arithmetic Immediate (`srai`) Instruction
RvInstr32OpImmFunct7::Srai => {
(reg as i32).wrapping_shr(instr.shamt()) as RvData
}

// Shift Right Arithmetic Immediate (`srai`) Instruction
RvInstr32OpImmFunct7::Srai => {
(reg as i32).wrapping_shr(instr.shamt()) as RvData
}
// Rotate Right Immediate (`rori`)
RvInstr32OpImmFunct7::Bitmanip => reg.rotate_right(instr.shamt()),

// Bitwise OR-Combine, byte granule
RvInstr32OpImmFunct7::Orc if instr.funct5() == 0b0_0111 => {
let reg_bytes = reg.to_le_bytes();
u32::from_le_bytes(core::array::from_fn(|i| {
if reg_bytes[i] != 0 {
0xff
} else {
0x00
}
}))
// Illegal Instruction
_ => Err(RvException::illegal_instr(instr.0))?,
}

// Byte-reverse register
RvInstr32OpImmFunct7::Rev8 if instr.funct5() == 0b1_1000 => reg.swap_bytes(),

// Illegal Instruction
_ => Err(RvException::illegal_instr(instr.0))?,
}
}

// Or Immediate (`ori`) Instruction
RvInstr32OpImmFunct3::Ori => reg | instr.imm() as u32,
// Or Immediate (`ori`) Instruction
RvInstr32OpImmFunct3::Ori => reg | instr.imm() as u32,

// And Immediate (`ori`) Instruction
RvInstr32OpImmFunct3::Andi => reg & instr.imm() as u32,
// And Immediate (`ori`) Instruction
RvInstr32OpImmFunct3::Andi => reg & instr.imm() as u32,

// Illegal Instruction
_ => Err(RvException::illegal_instr(instr.0))?,
// Illegal Instruction
_ => Err(RvException::illegal_instr(instr.0))?,
}
};

// Save the contents to register
Expand Down
134 changes: 134 additions & 0 deletions sw-emulator/lib/cpu/src/instr/test_encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ pub mod tests {
addi(XReg::X0, XReg::X0, 0)
}

pub fn sign_extend(x: u32) -> u32 {
let x = x as i32;
let x = x | (-(((x) >> 11) & 1) << 11);
x as u32
}

/// Encode Add Upper Immediate to program counter (`auipc`) instruction
pub fn auipc(rd: XReg, imm: i32) -> u32 {
let mut instr = RvInstr32U(0);
Expand Down Expand Up @@ -194,6 +200,124 @@ pub mod tests {
op_instr!(and, Seven, And);
op_instr!(remu, Seven, Remu);

// bit manipulation extension
op_instr!(sh1add, Two, Sh1add);
op_instr!(sh2add, Four, Sh1add);
op_instr!(sh3add, Six, Sh1add);
op_instr!(bset, One, Bset);
op_instr!(binv, One, Binv);
op_instr!(bclr, One, Bclr);
op_instr!(bext, Five, Bclr);
op_instr!(andn, Seven, Sub);
op_instr!(orn, Six, Sub);
op_instr!(xnor, Four, Sub);
op_instr!(max, Six, MinMaxClmul);
op_instr!(maxu, Seven, MinMaxClmul);
op_instr!(min, Four, MinMaxClmul);
op_instr!(minu, Five, MinMaxClmul);
op_instr!(rol, One, Rotate);
op_instr!(ror, Five, Rotate);
op_instr!(clmul, One, MinMaxClmul);
op_instr!(clmulh, Three, MinMaxClmul);
op_instr!(clmulr, Two, MinMaxClmul);
op_imm_instr!(bseti, Sli, Orc);
op_imm_instr!(binvi, Sli, Rev8);
op_imm_instr!(bclri, Sli, Bclr);
op_imm_instr!(bexti, Sri, Bclr);
op_imm_instr!(rori, Sri, Bitmanip);

pub fn clz(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x600);
instr.set_funct3(1);
instr.0
}

pub fn cpop(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x602);
instr.set_funct3(1);
instr.0
}

pub fn ctz(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x601);
instr.set_funct3(1);
instr.0
}

pub fn orc_b(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x287);
instr.set_funct3(5);
instr.0
}

pub fn rev8(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x698);
instr.set_funct3(5);
instr.0
}

pub fn sext_b(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x604);
instr.set_funct3(1);
instr.0
}

pub fn sext_h(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32I(0);
instr.set_opcode(RvInstr32Opcode::OpImm);
instr.set_rd(rd);
instr.set_rs(rs);
instr.set_imm(0x605);
instr.set_funct3(1);
instr.0
}

pub fn zext_h(rd: XReg, rs: XReg) -> u32 {
let mut instr = RvInstr32R(0);
instr.set_opcode(RvInstr32Opcode::Op);
instr.set_rd(rd);
instr.set_rs1(rs);
instr.set_rs2(XReg::X0);
instr.set_funct3(4);
instr.set_funct7(RvInstr32OpFunct7::Zext.into());
instr.0
}

// load immediate (first instruction)
pub fn li0(rd: XReg, imm: u32) -> u32 {
let v = (imm >> 12) + if imm & 0x800 == 0x800 { 1 } else { 0 };
lui(rd, v as i32)
}

// load immediate (second instruction)
pub fn li1(rd: XReg, imm: u32) -> u32 {
addi(rd, rd, (imm & 0xfff) as i32)
}

/// Encode Load Upper Immediate (`lui`) instruction
pub fn lui(rd: XReg, imm: i32) -> u32 {
let mut instr = RvInstr32U(0);
Expand Down Expand Up @@ -236,4 +360,14 @@ pub mod tests {
op_system_instr!(csrrwi, Csrrwi);
op_system_instr!(csrrsi, Csrrsi);
op_system_instr!(csrrci, Csrrci);

#[test]
fn test_sign_extend() {
for i in 0..0x800 {
assert_eq!(i, sign_extend(i));
}
for i in 0x800..0x1000 {
assert_eq!(0xffff_f000 | i, sign_extend(i));
}
}
}
Loading

0 comments on commit 3c220d5

Please sign in to comment.