continue RV64
Some checks failed
Rust / build (push) Has been cancelled

This commit is contained in:
nora 2025-04-13 17:53:50 +02:00
parent f0c04f1466
commit 8d01c9f1d7
4 changed files with 290 additions and 67 deletions

View file

@ -274,10 +274,16 @@ pub enum Inst {
/// Add
Add { dest: Reg, src1: Reg, src2: Reg },
/// Add 32-bit (**RV64 only**)
AddW { dest: Reg, src1: Reg, src2: Reg },
/// Subtract
Sub { dest: Reg, src1: Reg, src2: Reg },
/// Subtract 32-bit (**RV64 only**)
SubW { dest: Reg, src1: Reg, src2: Reg },
/// Shift Left Logical
Sll { dest: Reg, src1: Reg, src2: Reg },
/// Shift Left Logical 32-bit (**RV64 only**)
SllW { dest: Reg, src1: Reg, src2: Reg },
/// Set Less Than (signed)
Slt { dest: Reg, src1: Reg, src2: Reg },
/// Set Less Than Unsigned
@ -286,8 +292,12 @@ pub enum Inst {
Xor { dest: Reg, src1: Reg, src2: Reg },
/// Shift Right Logical (unsigned)
Srl { dest: Reg, src1: Reg, src2: Reg },
/// Shift Right Logical (unsigned) 32-bit (**RV64 only**)
SrlW { dest: Reg, src1: Reg, src2: Reg },
/// Shift Right Arithmetic (unsigned)
Sra { dest: Reg, src1: Reg, src2: Reg },
/// Shift Right Arithmetic (unsigned) 32-bit (**RV64 only**)
SraW { dest: Reg, src1: Reg, src2: Reg },
/// OR
Or { dest: Reg, src1: Reg, src2: Reg },
/// AND
@ -303,6 +313,8 @@ pub enum Inst {
// ------------- M extension -------------
/// Multiply
Mul { dest: Reg, src1: Reg, src2: Reg },
/// Multiply 32-bit (**RV64 only**)
MulW { dest: Reg, src1: Reg, src2: Reg },
/// Mul Upper Half Signed-Signed
Mulh { dest: Reg, src1: Reg, src2: Reg },
/// Mul Upper Half Signed-Unsigned
@ -311,12 +323,20 @@ pub enum Inst {
Mulhu { dest: Reg, src1: Reg, src2: Reg },
/// Divide (signed)
Div { dest: Reg, src1: Reg, src2: Reg },
/// Divide (signed) 32-bit (**RV64 only**)
DivW { dest: Reg, src1: Reg, src2: Reg },
/// Divide Unsigned
Divu { dest: Reg, src1: Reg, src2: Reg },
/// Divide Unsigned 32-bit (**RV64 only**)
DivuW { dest: Reg, src1: Reg, src2: Reg },
/// Remainder (signed)
Rem { dest: Reg, src1: Reg, src2: Reg },
/// Remainder (signed) 32-bit (**RV64 only**)
RemW { dest: Reg, src1: Reg, src2: Reg },
/// Remainder Unsigned
Remu { dest: Reg, src1: Reg, src2: Reg },
/// Remainder Unsigned 32-bit (**RV64 only**)
RemuW { dest: Reg, src1: Reg, src2: Reg },
// ------------- A extension -------------
/// Load-Reserved Word
@ -545,7 +565,7 @@ impl Display for Inst {
if imm.as_u32() == 0 {
write!(f, "sext.w {dest}, {src1}")
} else {
write!(f, "addi.w {dest}, {src1}, {}", imm.as_i32())
write!(f, "addiw {dest}, {src1}, {}", imm.as_i32())
}
}
Inst::Slti {
@ -606,13 +626,20 @@ impl Display for Inst {
Inst::Add { dest, src1, src2 } => {
write!(f, "add {dest}, {src1}, {src2}")
}
Inst::AddW { dest, src1, src2 } => {
write!(f, "addw {dest}, {src1}, {src2}")
}
Inst::Sub { dest, src1, src2 } => write!(f, "sub {dest}, {src1}, {src2}"),
Inst::SubW { dest, src1, src2 } => write!(f, "subw {dest}, {src1}, {src2}"),
Inst::Sll { dest, src1, src2 } => write!(f, "sll {dest}, {src1}, {src2}"),
Inst::SllW { dest, src1, src2 } => write!(f, "sllw {dest}, {src1}, {src2}"),
Inst::Slt { dest, src1, src2 } => write!(f, "slt {dest}, {src1}, {src2}"),
Inst::Sltu { dest, src1, src2 } => write!(f, "sltu {dest}, {src1}, {src2}"),
Inst::Xor { dest, src1, src2 } => write!(f, "xor {dest}, {src1}, {src2}"),
Inst::Srl { dest, src1, src2 } => write!(f, "srl {dest}, {src1}, {src2}"),
Inst::SrlW { dest, src1, src2 } => write!(f, "srlw {dest}, {src1}, {src2}"),
Inst::Sra { dest, src1, src2 } => write!(f, "sra {dest}, {src1}, {src2}"),
Inst::SraW { dest, src1, src2 } => write!(f, "sraw {dest}, {src1}, {src2}"),
Inst::Or { dest, src1, src2 } => write!(f, "or {dest}, {src1}, {src2}"),
Inst::And { dest, src1, src2 } => write!(f, "and {dest}, {src1}, {src2}"),
Inst::Fence { fence } => match fence.fm {
@ -625,13 +652,18 @@ impl Display for Inst {
Inst::Ecall => write!(f, "ecall"),
Inst::Ebreak => write!(f, "ebreak"),
Inst::Mul { dest, src1, src2 } => write!(f, "mul {dest}, {src1}, {src2}"),
Inst::MulW { dest, src1, src2 } => write!(f, "mulw {dest}, {src1}, {src2}"),
Inst::Mulh { dest, src1, src2 } => write!(f, "mulh {dest}, {src1}, {src2}"),
Inst::Mulhsu { dest, src1, src2 } => write!(f, "mulhsu {dest}, {src1}, {src2}"),
Inst::Mulhu { dest, src1, src2 } => write!(f, "mulhu {dest}, {src1}, {src2}"),
Inst::Div { dest, src1, src2 } => write!(f, "div {dest}, {src1}, {src2}"),
Inst::DivW { dest, src1, src2 } => write!(f, "divw {dest}, {src1}, {src2}"),
Inst::Divu { dest, src1, src2 } => write!(f, "divu {dest}, {src1}, {src2}"),
Inst::DivuW { dest, src1, src2 } => write!(f, "divuw {dest}, {src1}, {src2}"),
Inst::Rem { dest, src1, src2 } => write!(f, "rem {dest}, {src1}, {src2}"),
Inst::RemW { dest, src1, src2 } => write!(f, "remw {dest}, {src1}, {src2}"),
Inst::Remu { dest, src1, src2 } => write!(f, "remu {dest}, {src1}, {src2}"),
Inst::RemuW { dest, src1, src2 } => write!(f, "remuw {dest}, {src1}, {src2}"),
Inst::LrW { order, dest, addr } => write!(f, "lr.w{order} {dest}, ({addr})",),
Inst::ScW {
order,
@ -737,14 +769,6 @@ impl InstCode {
let end_span = 32 - (range.end() + 1);
(self.0 << (end_span)) >> (end_span + range.start())
}
fn immediate_u(self, mappings: &[(RangeInclusive<u32>, u32)]) -> Imm {
let mut imm = 0;
for (from, to) in mappings {
let value = self.extract(from.clone());
imm |= value << to;
}
Imm::new_u32(imm)
}
fn immediate_s(self, mappings: &[(RangeInclusive<u32>, u32)]) -> Imm {
let mut imm = 0;
let mut size = 0;
@ -775,6 +799,10 @@ impl InstCode {
fn rs2_imm(self) -> u32 {
self.extract(20..=24)
}
// shifts on RV64 have one extra bit
fn rs2_imm_plus(self) -> u32 {
self.extract(20..=25)
}
fn rd(self) -> Reg {
Reg(self.extract(7..=11) as u8)
}
@ -788,7 +816,8 @@ impl InstCode {
self.immediate_s(&[(31..=31, 12), (7..=7, 11), (25..=30, 5), (8..=11, 1)])
}
fn imm_u(self) -> Imm {
self.immediate_u(&[(12..=31, 12)])
// Don't be fooled by the "u", LUI/AUIPC immediates are sign-extended on RV64.
self.immediate_s(&[(12..=31, 12)])
}
fn imm_j(self) -> Imm {
self.immediate_s(&[(31..=31, 20), (21..=30, 1), (20..=20, 11), (12..=19, 12)])
@ -1096,7 +1125,7 @@ impl Inst {
_ => {
let uimm = code.immediate_s(&[(2..=6, 12), (12..=12, 17)]);
if uimm.as_u32() == 0 {
return Err(decode_error(code, "C.LUI imm"));
return Err(decode_error(code, "C.LUI zero immediate"));
}
Inst::Lui {
uimm,
@ -1352,7 +1381,13 @@ impl Inst {
src1: code.rs1(),
},
0b001 => {
if code.funct7() != 0 {
// For RV32, bit 25 must be zero as well.
let left_zeroes = code.funct7()
>> match xlen {
Xlen::Rv32 => 0,
Xlen::Rv64 => 1,
};
if left_zeroes != 0 {
return Err(decode_error(code, "slli shift overflow"));
}
Inst::Slli {
@ -1361,7 +1396,8 @@ impl Inst {
src1: code.rs1(),
}
}
0b101 => match code.funct7() {
0b101 => match xlen {
Xlen::Rv32 => match code.funct7() {
0b0000000 => Inst::Srli {
imm: Imm::new_u32(code.rs2_imm()),
dest: code.rd(),
@ -1372,13 +1408,30 @@ impl Inst {
dest: code.rd(),
src1: code.rs1(),
},
_ => return Err(decode_error(code, "OP-IMM funct7")),
_ => return Err(decode_error(code, "srli shift overflow")),
},
Xlen::Rv64 => {
let upper = code.funct7() >> 1;
match upper {
0b010000 => Inst::Srai {
imm: Imm::new_u32(code.rs2_imm_plus()),
dest: code.rd(),
src1: code.rs1(),
},
0b000000 => Inst::Srli {
imm: Imm::new_u32(code.rs2_imm_plus()),
dest: code.rd(),
src1: code.rs1(),
},
_ => return Err(decode_error(code, "srai/srli upper bits")),
}
}
},
_ => return Err(decode_error(code, "OP-IMM funct3")),
},
// OP-IMM-32
0b0011011 => {
if !xlen.is_64() {
if xlen.is_32() {
return Err(decode_error(code, "OP-IMM-32 only on RV64"));
}
@ -1443,6 +1496,28 @@ impl Inst {
_ => return Err(decode_error(code, "OP funct3/funct7")),
}
}
// OP-32
0b0111011 => {
if xlen.is_32() {
return Err(decode_error(code, "OP-IMM-32 only on RV64"));
}
let (dest, src1, src2) = (code.rd(), code.rs1(), code.rs2());
match (code.funct3(), code.funct7()) {
(0b000, 0b0000000) => Inst::AddW { dest, src1, src2 },
(0b000, 0b0100000) => Inst::SubW { dest, src1, src2 },
(0b001, 0b0000000) => Inst::SllW { dest, src1, src2 },
(0b101, 0b0000000) => Inst::SrlW { dest, src1, src2 },
(0b101, 0b0100000) => Inst::SraW { dest, src1, src2 },
(0b000, 0b0000001) => Inst::MulW { dest, src1, src2 },
(0b100, 0b0000001) => Inst::DivW { dest, src1, src2 },
(0b101, 0b0000001) => Inst::DivuW { dest, src1, src2 },
(0b110, 0b0000001) => Inst::RemW { dest, src1, src2 },
(0b111, 0b0000001) => Inst::RemuW { dest, src1, src2 },
_ => return Err(decode_error(code, "OP-32 funct3/funct7")),
}
}
// MISC-MEM
0b0001111 => {
let fm = code.extract(28..=31);
@ -1713,16 +1788,13 @@ mod tests {
let start_time = std::time::Instant::now();
let completed = AtomicU32::new(0);
chunks
.par_iter()
.for_each(|&start| {
chunks.par_iter().for_each(|&start| {
let insts = (start..=start.saturating_add(CHUNK_SIZE))
.filter_map(|code| Some((code, Inst::decode_normal(code, Xlen::Rv32).ok()?)))
.filter(|(_, inst)| is_inst_supposed_to_roundtrip(inst))
.collect::<Vec<_>>();
let mut text =
std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n");
let mut text = std::format!(".section {TEST_SECTION_NAME}\n.globl _start\n_start:\n");
for (_, inst) in &insts {
writeln!(text, " {inst}").unwrap();
}
@ -1746,7 +1818,8 @@ mod tests {
let already_elapsed = start_time.elapsed();
let remaining_chunks = CHUNKS.saturating_sub(already_completed);
let remaining = already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks;
let remaining =
already_elapsed / std::cmp::max(already_completed, 1) * remaining_chunks;
writeln!(
std::io::stdout(),

View file

@ -386,8 +386,33 @@ impl<XLEN: XLen> Emulator<XLEN> {
Inst::Srli { imm, dest, src1 } => self[dest] = self[src1].unsigned_shr(imm.as_u32()),
Inst::Srai { imm, dest, src1 } => self[dest] = self[src1].signed_shr(imm.as_u32()),
Inst::Add { dest, src1, src2 } => self[dest] = self[src1].add(self[src2]),
Inst::AddW { dest, src1, src2 } => {
self[dest] = XLEN::switch2(
self[src1],
self[src2],
|_, _| unreachable!(),
|a, b| (a as u32).add(b as u32) as i32 as i64 as u64,
);
}
Inst::Sub { dest, src1, src2 } => self[dest] = self[src1].sub(self[src2]),
Inst::Sll { dest, src1, src2 } => self[dest] = self[src1].shl(self[src2].truncate32()),
Inst::SubW { dest, src1, src2 } => {
self[dest] = XLEN::switch2(
self[src1],
self[src2],
|_, _| unreachable!(),
|a, b| (a as u32).sub(b as u32) as i32 as i64 as u64,
);
}
Inst::Sll { dest, src1, src2 } => {
self[dest] = self[src1].shl(self[src2].truncate32());
}
Inst::SllW { dest, src1, src2 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|a| (a as u32).shl(self[src2].truncate32()) as i32 as i64 as u64,
);
}
Inst::Slt { dest, src1, src2 } => {
self[dest] = XLEN::from_bool(self[src1].signed_lt(self[src2]));
}
@ -398,9 +423,23 @@ impl<XLEN: XLen> Emulator<XLEN> {
Inst::Srl { dest, src1, src2 } => {
self[dest] = self[src1].unsigned_shr(self[src2].truncate32())
}
Inst::SrlW { dest, src1, src2 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|a| (a as u32).unsigned_shr(self[src2].truncate32()) as i32 as i64 as u64,
);
}
Inst::Sra { dest, src1, src2 } => {
self[dest] = self[src1].signed_shr(self[src2].truncate32())
}
Inst::SraW { dest, src1, src2 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|a| (a as u32).unsigned_shr(self[src2].truncate32()) as i32 as i64 as u64,
);
}
Inst::Or { dest, src1, src2 } => self[dest] = self[src1].or(self[src2]),
Inst::And { dest, src1, src2 } => self[dest] = self[src1].and(self[src2]),
Inst::Fence { fence: _ } => { /* dont care */ }
@ -502,7 +541,40 @@ impl<XLEN: XLen> Emulator<XLEN> {
}
self.reservation_set = None;
}
_ => return Err(Status::UnsupportedInst(inst)),
Inst::AddiW { imm, dest, src1 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|src| ((src as u32).wrapping_add(imm.as_u32())) as i32 as i64 as u64,
);
}
Inst::SlliW { imm, dest, src1 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|src| ((src as u32).shl(imm.as_u32())) as u64,
);
}
Inst::SrliW { imm, dest, src1 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|src| ((src as u32).unsigned_shr(imm.as_u32())) as u64,
);
}
Inst::SraiW { imm, dest, src1 } => {
self[dest] = XLEN::switch(
self[src1],
|_| unreachable!(),
|src| ((src as u32).signed_shr(imm.as_u32())) as u64,
);
}
Inst::MulW { dest, src1, src2 } => todo!(),
Inst::DivW { dest, src1, src2 } => todo!(),
Inst::DivuW { dest, src1, src2 } => todo!(),
Inst::RemW { dest, src1, src2 } => todo!(),
Inst::RemuW { dest, src1, src2 } => todo!(),
_ => todo!(),
}
if !jumped {
@ -676,7 +748,7 @@ impl<XLEN: XLen> Emulator<XLEN> {
}
}
pub trait XLen: Copy + PartialEq + Eq {
pub trait XLen: Copy + PartialEq + Eq + Debug {
type Signed;
type NextUnsigned;
type NextSigned;
@ -687,7 +759,15 @@ pub trait XLen: Copy + PartialEq + Eq {
const SIGNED_MIN: Self;
const MAX: Self;
fn switch<R>(self, on_32: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R;
const IS_64: bool = matches!(Self::XLEN, rvdc::Xlen::Rv64);
fn switch(self, on_32: impl FnOnce(u32) -> u32, on_64: impl FnOnce(u64) -> u64) -> Self;
fn switch2(
self,
other: Self,
on_32: impl FnOnce(u32, u32) -> u32,
on_64: impl FnOnce(u64, u64) -> u64,
) -> Self;
fn from_bool(v: bool) -> Self {
Self::from_32_z(v as u32)
@ -839,9 +919,17 @@ impl XLen for u32 {
const XLEN: rvdc::Xlen = rvdc::Xlen::Rv32;
fn switch<R>(self, on_32: impl FnOnce(u32) -> R, _: impl FnOnce(u64) -> R) -> R {
fn switch(self, on_32: impl FnOnce(u32) -> u32, _: impl FnOnce(u64) -> u64) -> Self {
on_32(self)
}
fn switch2(
self,
other: Self,
on_32: impl FnOnce(u32, u32) -> u32,
_: impl FnOnce(u64, u64) -> u64,
) -> Self {
on_32(self, other)
}
fn from_32_s(v: u32) -> Self {
v
@ -863,9 +951,17 @@ impl XLen for u64 {
const XLEN: rvdc::Xlen = rvdc::Xlen::Rv64;
fn switch<R>(self, _: impl FnOnce(u32) -> R, on_64: impl FnOnce(u64) -> R) -> R {
fn switch(self, _: impl FnOnce(u32) -> u32, on_64: impl FnOnce(u64) -> u64) -> Self {
on_64(self)
}
fn switch2(
self,
other: Self,
_: impl FnOnce(u32, u32) -> u32,
on_64: impl FnOnce(u64, u64) -> u64,
) -> Self {
on_64(self, other)
}
fn from_32_s(v: u32) -> Self {
v as i32 as i64 as u64

View file

@ -30,7 +30,7 @@ fn check() -> eyre::Result<()> {
test_case(tmpdir.path(), &file, name, 32, "rv32ima")?;
test_case(tmpdir.path(), &file, name, 32, "rv32imac")?;
// test_case(tmpdir.path(), &file, name, 64, "rv64ima")?;
test_case(tmpdir.path(), &file, name, 64, "rv64ima")?;
// test_case(tmpdir.path(), &file, name, 64, "rv64imac")?;
}
@ -70,7 +70,7 @@ fn test_case(
}
}),
Box::new(|_, xreg| {
if xreg[Reg::A7.0 as usize] == u32::MAX as u64 {
if xreg[Reg::A7.0 as usize] == u64::MAX {
if xreg[Reg::A0.0 as usize] == 1 {
Err(rustv32i::emu::Status::Exit { code: 0 })
} else {
@ -101,6 +101,7 @@ fn build(tmpdir: &Path, src: &Path, size: u8, march: &str) -> eyre::Result<PathB
cmd.arg(src);
cmd.arg("-o");
cmd.arg(&out_path);
cmd.arg(format!("-DRV{size}"));
let output = cmd.output().wrap_err("failed to spawn clang")?;
if !output.status.success() {

View file

@ -83,25 +83,49 @@ START_TEST
CASE sll, 2, 1, 4
CASE sll, 2, 20, 2097152
CASE sll, 2, 30, 2147483648
CASE sll, 2, 31, 0
CASER sll, 2, 32, 2 # error for immediate
CASE sll, 0, 10, 0
CASE sll, 10, 0, 10
#ifdef RV32
CASE sll, 2, 31, 0
CASE sll, -1, 31, -2147483648
CASER sll, -1, 32, -1 # error for immediate
CASER sll, 2, 32, 2 # error for immediate
#elif RV64
#CASE_BOTH sllw, slliw, 2, 31, 0
#CASE_BOTH sllw, slliw, -1, 31, -2147483648
#CASER sllw, -1, 32, -1 # error for immediate
#CASER sllw, 2, 32, 2 # error for immediate
CASE sll, -1, 31, 18446744071562067968
CASER sll, 2, 63, 0 # error for immediate
CASE sll, -1, 32, 18446744069414584320 # test with immediate as well
CASER sll, -1, 63, 9223372036854775808 # error for immediate
CASER sll, -1, 64, -1 # error for immediate
CASER sll, 2, 64, 2 # error for immediate
#endif
CASE srl, 4, 1, 2
CASE srl, 0, 10, 0
CASE srl, 10, 0, 10
CASE srl, -1, 1, 2147483647
CASE srl, 0b111, 2, 0b001
#ifdef RV32
CASE srl, -1, 1, 2147483647
CASER srl, -1, 32, -1 # error for immediate
#elif RV64
CASE srl, -1, 1, 9223372036854775807
CASE srl, -1, 32, 4294967295
CASER srl, -1, 64, -1 # error for immediate
#endif
CASER sub, 10, 5, 5
CASER sub, -1, 1, -2
CASER sub, 1, 2, -1
CASER sub, -1, -2, 1
#ifdef RV32
CASER sub, 0, 4294967295, 1
#elif RV64
CASER sub, 0, 18446744073709551615, 1
#endif
.macro CASE_SUB a:req, b:req
CASER sub, \a, \b, \a - \b
@ -115,7 +139,12 @@ START_TEST
CASE sra, -1, 1, -1
CASE sra, -1, 31, -1
CASE sra, 0b111, 2, 0b001
#ifdef RV32
CASER sra, 10, 32, 10 # error for immediate
#elif RV64
CASE sra, 10, 32, 0
CASER sra, 10, 64, 10 # error for immediate
#endif
# M extension
@ -123,7 +152,11 @@ START_TEST
CASER mul, 10, 0, 0
CASER mul, 10, 1, 10
CASER mul, -1, -1, 1
#ifdef RV32
CASER mul, 25252566, 5225225, 353909638
#elif RV64
// TODO
#endif
.macro CASE_MUL a:req, b:req
CASER mul, \a, \b, \a * \b
@ -133,11 +166,19 @@ START_TEST
CASER mulh 4, 4, 0
CASER mulh, -1, -1, 0
#ifdef RV32
CASER mulh, 25252566, 5225225, 30722
#elif RV64
// TODO
#endif
CASER mulhu 4, 4, 0
#ifdef RV32
CASER mulhu, -1, -1, 4294967294
CASER mulhu, 25252566, 5225225, 30722
#elif RV64
// TODO
#endif
# mulhsu hasn't been implemented yet.
@ -148,21 +189,33 @@ START_TEST
CASER div, -10, 2, -5
CASER div, 5, 2, 2
CASER div, 5, -1, -5
#ifdef RV32
CASER div, -2147483648, -1, -1
#elif RV64
// TODO
#endif
CASER divu, 4, 2, 2
CASER divu, -1, 1, -1
CASER divu, 1, 1, 1
CASER divu, 1, 0, -1
CASER divu, -10, 2, 2147483643
CASER divu, 5, 2, 2
#ifdef RV32
CASER divu, -10, 2, 2147483643
#elif RV64
// TODO
#endif
CASER rem, 4, 2, 0
CASER rem, 5, 2, 1
CASER rem, 5, 0, 5
CASER rem, -10, 3, -1
CASER rem, 5, -1, 0
#ifdef RV32
CASER rem, -2147483648, -1, 0
#elif RV64
// TODO
#endif
CASER remu, 4, 2, 0
CASER remu, 5, 2, 1