use crate::core::sync::{AtomicBool, AtomicU64, Ordering};
use super::isa::Xlen;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Priv {
User = 0,
Supervisor = 1,
Machine = 3,
}
impl Priv {
#[must_use]
pub const fn from_bits(bits: u64) -> Option<Priv> {
match bits {
0 => Some(Priv::User),
1 => Some(Priv::Supervisor),
3 => Some(Priv::Machine),
_ => None,
}
}
#[inline]
#[must_use]
pub const fn bits(self) -> u64 {
self as u64
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Priv::User => "U",
Priv::Supervisor => "S",
Priv::Machine => "M",
}
}
}
pub mod cause {
pub const INSN_MISALIGNED: u64 = 0;
pub const INSN_ACCESS: u64 = 1;
pub const ILLEGAL_INSN: u64 = 2;
pub const BREAKPOINT: u64 = 3;
pub const LOAD_MISALIGNED: u64 = 4;
pub const LOAD_ACCESS: u64 = 5;
pub const STORE_MISALIGNED: u64 = 6;
pub const STORE_ACCESS: u64 = 7;
pub const ECALL_U: u64 = 8;
pub const ECALL_S: u64 = 9;
pub const ECALL_M: u64 = 11;
pub const INSN_PAGE_FAULT: u64 = 12;
pub const LOAD_PAGE_FAULT: u64 = 13;
pub const STORE_PAGE_FAULT: u64 = 15;
pub const IRQ_S_SOFT: u64 = 1;
pub const IRQ_M_SOFT: u64 = 3;
pub const IRQ_S_TIMER: u64 = 5;
pub const IRQ_M_TIMER: u64 = 7;
pub const IRQ_S_EXT: u64 = 9;
pub const IRQ_M_EXT: u64 = 11;
}
pub mod irq {
pub const SSI: u64 = 1 << super::cause::IRQ_S_SOFT;
pub const MSI: u64 = 1 << super::cause::IRQ_M_SOFT;
pub const STI: u64 = 1 << super::cause::IRQ_S_TIMER;
pub const MTI: u64 = 1 << super::cause::IRQ_M_TIMER;
pub const SEI: u64 = 1 << super::cause::IRQ_S_EXT;
pub const MEI: u64 = 1 << super::cause::IRQ_M_EXT;
pub const S_MASK: u64 = SSI | STI | SEI;
pub const ALL: u64 = SSI | MSI | STI | MTI | SEI | MEI;
pub const PRIORITY: [u64; 6] = [
super::cause::IRQ_M_EXT,
super::cause::IRQ_M_SOFT,
super::cause::IRQ_M_TIMER,
super::cause::IRQ_S_EXT,
super::cause::IRQ_S_SOFT,
super::cause::IRQ_S_TIMER,
];
}
pub mod status {
pub const SIE: u64 = 1 << 1;
pub const MIE: u64 = 1 << 3;
pub const SPIE: u64 = 1 << 5;
pub const UBE: u64 = 1 << 6;
pub const MPIE: u64 = 1 << 7;
pub const SPP: u64 = 1 << 8;
pub const MPP_SHIFT: u32 = 11;
pub const MPP: u64 = 3 << MPP_SHIFT;
pub const FS_SHIFT: u32 = 13;
pub const FS: u64 = 3 << FS_SHIFT;
pub const XS_SHIFT: u32 = 15;
pub const XS: u64 = 3 << XS_SHIFT;
pub const MPRV: u64 = 1 << 17;
pub const SUM: u64 = 1 << 18;
pub const MXR: u64 = 1 << 19;
pub const TVM: u64 = 1 << 20;
pub const TW: u64 = 1 << 21;
pub const TSR: u64 = 1 << 22;
pub const UXL_SHIFT: u32 = 32;
pub const SXL_SHIFT: u32 = 34;
pub const FS_OFF: u64 = 0;
pub const FS_DIRTY: u64 = 3;
pub const M_WRITABLE: u64 =
SIE | MIE | SPIE | UBE | MPIE | SPP | MPP | FS | MPRV | SUM | MXR | TVM | TW | TSR;
pub const S_VISIBLE: u64 = SIE | SPIE | UBE | SPP | FS | XS | SUM | MXR;
pub const S_WRITABLE: u64 = SIE | SPIE | UBE | SPP | FS | SUM | MXR;
}
pub mod num {
pub const FFLAGS: u32 = 0x001;
pub const FRM: u32 = 0x002;
pub const FCSR: u32 = 0x003;
pub const SSTATUS: u32 = 0x100;
pub const SIE: u32 = 0x104;
pub const STVEC: u32 = 0x105;
pub const SCOUNTEREN: u32 = 0x106;
pub const SENVCFG: u32 = 0x10a;
pub const SSCRATCH: u32 = 0x140;
pub const SEPC: u32 = 0x141;
pub const SCAUSE: u32 = 0x142;
pub const STVAL: u32 = 0x143;
pub const SIP: u32 = 0x144;
pub const SATP: u32 = 0x180;
pub const MSTATUS: u32 = 0x300;
pub const MISA: u32 = 0x301;
pub const MEDELEG: u32 = 0x302;
pub const MIDELEG: u32 = 0x303;
pub const MIE: u32 = 0x304;
pub const MTVEC: u32 = 0x305;
pub const MCOUNTEREN: u32 = 0x306;
pub const MENVCFG: u32 = 0x30a;
pub const MSTATUSH: u32 = 0x310;
pub const MENVCFGH: u32 = 0x31a;
pub const MCOUNTINHIBIT: u32 = 0x320;
pub const MSCRATCH: u32 = 0x340;
pub const MEPC: u32 = 0x341;
pub const MCAUSE: u32 = 0x342;
pub const MTVAL: u32 = 0x343;
pub const MIP: u32 = 0x344;
pub const PMPCFG0: u32 = 0x3a0;
pub const PMPCFG15: u32 = 0x3af;
pub const PMPADDR0: u32 = 0x3b0;
pub const PMPADDR63: u32 = 0x3ef;
pub const TSELECT: u32 = 0x7a0;
pub const TDATA1: u32 = 0x7a1;
pub const TDATA2: u32 = 0x7a2;
pub const TDATA3: u32 = 0x7a3;
pub const TINFO: u32 = 0x7a4;
pub const TCONTROL: u32 = 0x7a5;
pub const MCYCLE: u32 = 0xb00;
pub const MINSTRET: u32 = 0xb02;
pub const MCYCLEH: u32 = 0xb80;
pub const MINSTRETH: u32 = 0xb82;
pub const CYCLE: u32 = 0xc00;
pub const TIME: u32 = 0xc01;
pub const INSTRET: u32 = 0xc02;
pub const CYCLEH: u32 = 0xc80;
pub const TIMEH: u32 = 0xc81;
pub const INSTRETH: u32 = 0xc82;
pub const MVENDORID: u32 = 0xf11;
pub const MARCHID: u32 = 0xf12;
pub const MIMPID: u32 = 0xf13;
pub const MHARTID: u32 = 0xf14;
}
#[must_use]
pub fn csr_name(number: u32) -> Option<&'static str> {
let name = match number {
num::FFLAGS => "fflags",
num::FRM => "frm",
num::FCSR => "fcsr",
num::SSTATUS => "sstatus",
num::SIE => "sie",
num::STVEC => "stvec",
num::SCOUNTEREN => "scounteren",
num::SENVCFG => "senvcfg",
num::SSCRATCH => "sscratch",
num::SEPC => "sepc",
num::SCAUSE => "scause",
num::STVAL => "stval",
num::SIP => "sip",
num::SATP => "satp",
num::MSTATUS => "mstatus",
num::MISA => "misa",
num::MEDELEG => "medeleg",
num::MIDELEG => "mideleg",
num::MIE => "mie",
num::MTVEC => "mtvec",
num::MCOUNTEREN => "mcounteren",
num::MENVCFG => "menvcfg",
num::MSTATUSH => "mstatush",
num::MENVCFGH => "menvcfgh",
num::MCOUNTINHIBIT => "mcountinhibit",
num::MSCRATCH => "mscratch",
num::MEPC => "mepc",
num::MCAUSE => "mcause",
num::MTVAL => "mtval",
num::MIP => "mip",
num::PMPCFG0 => "pmpcfg0",
num::PMPADDR0 => "pmpaddr0",
num::TSELECT => "tselect",
num::TDATA1 => "tdata1",
num::TDATA2 => "tdata2",
num::TDATA3 => "tdata3",
num::TINFO => "tinfo",
num::TCONTROL => "tcontrol",
num::MCYCLE => "mcycle",
num::MINSTRET => "minstret",
num::MCYCLEH => "mcycleh",
num::MINSTRETH => "minstreth",
num::CYCLE => "cycle",
num::TIME => "time",
num::INSTRET => "instret",
num::CYCLEH => "cycleh",
num::TIMEH => "timeh",
num::INSTRETH => "instreth",
num::MVENDORID => "mvendorid",
num::MARCHID => "marchid",
num::MIMPID => "mimpid",
num::MHARTID => "mhartid",
_ => return None,
};
Some(name)
}
pub const PMP_ENTRIES: usize = 16;
#[derive(Debug, Default)]
pub struct Lines {
mip: AtomicU64,
reset_req: AtomicBool,
}
impl Lines {
pub fn set_pending(&self, mask: u64, asserted: bool) {
if asserted {
self.mip.fetch_or(mask, Ordering::AcqRel);
} else {
self.mip.fetch_and(!mask, Ordering::AcqRel);
}
}
#[must_use]
pub fn pending(&self) -> u64 {
self.mip.load(Ordering::Acquire)
}
pub fn set_all_pending(&self, value: u64) {
self.mip.store(value & irq::ALL, Ordering::Release);
}
pub fn request_reset(&self) {
self.reset_req.store(true, Ordering::Release);
}
pub fn take_reset_request(&self) -> bool {
self.reset_req.swap(false, Ordering::AcqRel)
}
#[must_use]
pub fn reset_requested(&self) -> bool {
self.reset_req.load(Ordering::Acquire)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Extensions {
pub m: bool,
pub a: bool,
pub f: bool,
pub d: bool,
pub c: bool,
pub s: bool,
pub u: bool,
}
impl Extensions {
pub const GC: Extensions = Extensions {
m: true,
a: true,
f: true,
d: true,
c: true,
s: true,
u: true,
};
pub const I: Extensions = Extensions {
m: false,
a: false,
f: false,
d: false,
c: false,
s: false,
u: false,
};
#[must_use]
pub const fn misa_bits(self) -> u64 {
let mut bits = 1 << 8; if self.m {
bits |= 1 << 12;
}
if self.a {
bits |= 1;
}
if self.f {
bits |= 1 << 5;
}
if self.d {
bits |= 1 << 3;
}
if self.c {
bits |= 1 << 2;
}
if self.s {
bits |= 1 << 18;
}
if self.u {
bits |= 1 << 20;
}
bits
}
}
#[derive(Debug, Clone)]
pub struct Csrs {
pub xlen: Xlen,
pub ext: Extensions,
pub priv_mode: Priv,
pub mstatus: u64,
pub medeleg: u64,
pub mideleg: u64,
pub mie: u64,
pub mtvec: u64,
pub mcounteren: u64,
pub mcountinhibit: u64,
pub mscratch: u64,
pub mepc: u64,
pub mcause: u64,
pub mtval: u64,
pub menvcfg: u64,
pub stvec: u64,
pub scounteren: u64,
pub sscratch: u64,
pub sepc: u64,
pub scause: u64,
pub stval: u64,
pub satp: u64,
pub senvcfg: u64,
pub fcsr: u64,
pub hartid: u64,
pub minstret: u64,
pub mcycle: u64,
pub mtime: u64,
pub pmp_count: usize,
pub pmpcfg: [u8; PMP_ENTRIES],
pub pmpaddr: [u64; PMP_ENTRIES],
pub translation_gen: u64,
}
impl Csrs {
#[must_use]
pub fn new(xlen: Xlen, ext: Extensions, hartid: u64, pmp_count: usize) -> Csrs {
let mut mstatus = 0u64;
if xlen == Xlen::Rv64 {
mstatus |= 2 << status::UXL_SHIFT;
if ext.s {
mstatus |= 2 << status::SXL_SHIFT;
}
}
Csrs {
xlen,
ext,
priv_mode: Priv::Machine,
mstatus,
medeleg: 0,
mideleg: 0,
mie: 0,
mtvec: 0,
mcounteren: 0,
mcountinhibit: 0,
mscratch: 0,
mepc: 0,
mcause: 0,
mtval: 0,
menvcfg: 0,
stvec: 0,
scounteren: 0,
sscratch: 0,
sepc: 0,
scause: 0,
stval: 0,
satp: 0,
senvcfg: 0,
fcsr: 0,
hartid,
minstret: 0,
mcycle: 0,
mtime: 0,
pmp_count: pmp_count.min(PMP_ENTRIES),
pmpcfg: [0; PMP_ENTRIES],
pmpaddr: [0; PMP_ENTRIES],
translation_gen: 1,
}
}
#[inline]
#[must_use]
pub const fn xmask(&self) -> u64 {
match self.xlen {
Xlen::Rv32 => 0xffff_ffff,
Xlen::Rv64 => u64::MAX,
}
}
#[must_use]
pub fn misa(&self) -> u64 {
let mxl: u64 = match self.xlen {
Xlen::Rv32 => 1,
Xlen::Rv64 => 2,
};
(mxl << (self.xlen.bits() - 2)) | self.ext.misa_bits()
}
#[inline]
#[must_use]
pub fn fs(&self) -> u64 {
(self.mstatus & status::FS) >> status::FS_SHIFT
}
#[inline]
#[must_use]
pub fn fp_enabled(&self) -> bool {
self.ext.f && self.fs() != status::FS_OFF
}
#[inline]
pub fn dirty_fp(&mut self) {
self.mstatus = (self.mstatus & !status::FS) | (status::FS_DIRTY << status::FS_SHIFT);
}
#[must_use]
pub fn read_mstatus(&self) -> u64 {
let mut v = self.mstatus;
if (v & status::FS) == status::FS || (v & status::XS) == status::XS {
v |= 1 << (self.xlen.bits() - 1);
}
v & self.xmask()
}
#[inline]
pub fn bump_translation(&mut self) {
self.translation_gen = self.translation_gen.wrapping_add(1);
}
fn accessible(&self, num: u32, write: bool) -> bool {
if write && (num >> 10) & 3 == 3 {
return false;
}
let needed = (num >> 8) & 3;
u64::from(needed) <= self.priv_mode.bits()
}
fn counter_enabled(&self, bit: u32) -> bool {
match self.priv_mode {
Priv::Machine => true,
Priv::Supervisor => self.mcounteren & (1 << bit) != 0,
Priv::User => self.mcounteren & (1 << bit) != 0 && self.scounteren & (1 << bit) != 0,
}
}
#[must_use]
pub fn read(&self, num: u32, pending: u64) -> Option<u64> {
if !self.accessible(num, false) {
return None;
}
let mask = self.xmask();
let v = match num {
num::FFLAGS => {
if !self.fp_enabled() {
return None;
}
self.fcsr & 0x1f
}
num::FRM => {
if !self.fp_enabled() {
return None;
}
(self.fcsr >> 5) & 7
}
num::FCSR => {
if !self.fp_enabled() {
return None;
}
self.fcsr & 0xff
}
num::SSTATUS => {
if !self.ext.s {
return None;
}
let mut v = self.mstatus & status::S_VISIBLE;
if self.xlen == Xlen::Rv64 {
v |= self.mstatus & (3 << status::UXL_SHIFT);
}
if (self.mstatus & status::FS) == status::FS {
v |= 1 << (self.xlen.bits() - 1);
}
v
}
num::SIE => self.s_view(self.mie)?,
num::STVEC => self.s_reg(self.stvec)?,
num::SCOUNTEREN => self.s_reg(self.scounteren)?,
num::SENVCFG => self.s_reg(self.senvcfg)?,
num::SSCRATCH => self.s_reg(self.sscratch)?,
num::SEPC => self.s_reg(self.sepc)?,
num::SCAUSE => self.s_reg(self.scause)?,
num::STVAL => self.s_reg(self.stval)?,
num::SIP => self.s_view(pending)?,
num::SATP => {
if !self.ext.s {
return None;
}
if self.priv_mode == Priv::Supervisor && self.mstatus & status::TVM != 0 {
return None;
}
self.satp
}
num::MSTATUS => self.read_mstatus(),
num::MSTATUSH => {
if self.xlen != Xlen::Rv32 {
return None;
}
self.mstatus >> 32
}
num::MISA => self.misa(),
num::MEDELEG => self.medeleg,
num::MIDELEG => self.mideleg,
num::MIE => self.mie,
num::MTVEC => self.mtvec,
num::MCOUNTEREN => self.mcounteren,
num::MCOUNTINHIBIT => self.mcountinhibit,
num::MENVCFG => self.menvcfg,
num::MENVCFGH => {
if self.xlen != Xlen::Rv32 {
return None;
}
self.menvcfg >> 32
}
num::MSCRATCH => self.mscratch,
num::MEPC => self.mepc,
num::MCAUSE => self.mcause,
num::MTVAL => self.mtval,
num::MIP => pending,
num::MCYCLE | num::CYCLE => {
if num == num::CYCLE && !self.counter_enabled(0) {
return None;
}
self.mcycle
}
num::TIME => {
if !self.counter_enabled(1) {
return None;
}
self.mtime
}
num::MINSTRET | num::INSTRET => {
if num == num::INSTRET && !self.counter_enabled(2) {
return None;
}
self.minstret
}
num::MCYCLEH | num::CYCLEH => {
if self.xlen != Xlen::Rv32 {
return None;
}
if num == num::CYCLEH && !self.counter_enabled(0) {
return None;
}
self.mcycle >> 32
}
num::TIMEH => {
if self.xlen != Xlen::Rv32 || !self.counter_enabled(1) {
return None;
}
self.mtime >> 32
}
num::MINSTRETH | num::INSTRETH => {
if self.xlen != Xlen::Rv32 {
return None;
}
if num == num::INSTRETH && !self.counter_enabled(2) {
return None;
}
self.minstret >> 32
}
num::TSELECT | num::TDATA1 | num::TDATA2 | num::TDATA3 | num::TCONTROL => 0,
num::TINFO => 1,
num::MVENDORID | num::MARCHID | num::MIMPID => 0,
num::MHARTID => self.hartid,
num::PMPCFG0..=num::PMPCFG15 => self.read_pmpcfg(num - num::PMPCFG0)?,
num::PMPADDR0..=num::PMPADDR63 => {
let i = (num - num::PMPADDR0) as usize;
if i >= self.pmp_count {
0
} else {
self.pmpaddr[i]
}
}
0xb03..=0xb1f | 0xb83..=0xb9f | 0x323..=0x33f | 0xc03..=0xc1f | 0xc83..=0xc9f => 0,
_ => return None,
};
Some(v & mask)
}
fn s_reg(&self, value: u64) -> Option<u64> {
if self.ext.s { Some(value) } else { None }
}
fn s_view(&self, value: u64) -> Option<u64> {
if self.ext.s {
Some(value & self.mideleg & irq::S_MASK)
} else {
None
}
}
fn read_pmpcfg(&self, index: u32) -> Option<u64> {
let (per, first) = match self.xlen {
Xlen::Rv32 => (4usize, index as usize * 4),
Xlen::Rv64 => {
if index & 1 != 0 {
return None;
}
(8usize, index as usize / 2 * 8)
}
};
let mut v = 0u64;
for i in 0..per {
let entry = first + i;
if entry < self.pmp_count {
v |= u64::from(self.pmpcfg[entry]) << (8 * i);
}
}
Some(v)
}
fn write_pmpcfg(&mut self, index: u32, value: u64) -> Option<()> {
let (per, first) = match self.xlen {
Xlen::Rv32 => (4usize, index as usize * 4),
Xlen::Rv64 => {
if index & 1 != 0 {
return None;
}
(8usize, index as usize / 2 * 8)
}
};
for i in 0..per {
let entry = first + i;
if entry >= self.pmp_count {
break;
}
if self.pmpcfg[entry] & 0x80 != 0 {
continue;
}
let mut byte = ((value >> (8 * i)) & 0xff) as u8;
byte &= 0x9f;
if byte & 0b11 == 0b10 {
byte &= !0b10;
}
self.pmpcfg[entry] = byte;
}
Some(())
}
#[must_use]
pub fn write(&mut self, num: u32, value: u64, pending: u64) -> Option<Option<u64>> {
if !self.accessible(num, true) {
return None;
}
let value = value & self.xmask();
let mut new_pending = None;
match num {
num::FFLAGS => {
if !self.fp_enabled() {
return None;
}
self.fcsr = (self.fcsr & !0x1f) | (value & 0x1f);
self.dirty_fp();
}
num::FRM => {
if !self.fp_enabled() {
return None;
}
self.fcsr = (self.fcsr & !0xe0) | ((value & 7) << 5);
self.dirty_fp();
}
num::FCSR => {
if !self.fp_enabled() {
return None;
}
self.fcsr = value & 0xff;
self.dirty_fp();
}
num::SSTATUS => {
if !self.ext.s {
return None;
}
self.mstatus = (self.mstatus & !status::S_WRITABLE) | (value & status::S_WRITABLE);
self.bump_translation();
}
num::SIE => {
if !self.ext.s {
return None;
}
let m = self.mideleg & irq::S_MASK;
self.mie = (self.mie & !m) | (value & m);
}
num::STVEC => {
if !self.ext.s {
return None;
}
self.stvec = value & !2;
}
num::SCOUNTEREN => {
if !self.ext.s {
return None;
}
self.scounteren = value & 0xffff_ffff;
}
num::SENVCFG => {
if !self.ext.s {
return None;
}
self.senvcfg = value & 1;
}
num::SSCRATCH => {
if !self.ext.s {
return None;
}
self.sscratch = value;
}
num::SEPC => {
if !self.ext.s {
return None;
}
self.sepc = self.align_epc(value);
}
num::SCAUSE => {
if !self.ext.s {
return None;
}
self.scause = value;
}
num::STVAL => {
if !self.ext.s {
return None;
}
self.stval = value;
}
num::SIP => {
if !self.ext.s {
return None;
}
let m = self.mideleg & irq::SSI;
new_pending = Some((pending & !m) | (value & m));
}
num::SATP => {
if !self.ext.s {
return None;
}
if self.priv_mode == Priv::Supervisor && self.mstatus & status::TVM != 0 {
return None;
}
if self.satp_mode_supported(value) {
self.satp = value;
self.bump_translation();
}
}
num::MSTATUS => {
self.mstatus = (self.mstatus & !status::M_WRITABLE) | (value & status::M_WRITABLE);
if Priv::from_bits((self.mstatus & status::MPP) >> status::MPP_SHIFT).is_none() {
self.mstatus |= status::MPP;
}
if !self.ext.s {
self.mstatus &= !(status::SPP | status::SIE | status::SPIE | status::TVM);
}
self.bump_translation();
}
num::MSTATUSH => {
if self.xlen != Xlen::Rv32 {
return None;
}
}
num::MISA => {}
num::MEDELEG => {
if !self.ext.s {
return None;
}
self.medeleg = value & !(1 << cause::ECALL_M);
}
num::MIDELEG => {
if !self.ext.s {
return None;
}
self.mideleg = value & irq::S_MASK;
}
num::MIE => self.mie = value & irq::ALL,
num::MTVEC => self.mtvec = value & !2,
num::MCOUNTEREN => self.mcounteren = value & 0xffff_ffff,
num::MCOUNTINHIBIT => self.mcountinhibit = value & 0xffff_fffd,
num::MENVCFG => self.menvcfg = value,
num::MENVCFGH => {
if self.xlen != Xlen::Rv32 {
return None;
}
}
num::MSCRATCH => self.mscratch = value,
num::MEPC => self.mepc = self.align_epc(value),
num::MCAUSE => self.mcause = value,
num::MTVAL => self.mtval = value,
num::MIP => {
let m = irq::S_MASK;
new_pending = Some((pending & !m) | (value & m));
}
num::MCYCLE => self.mcycle = value,
num::MINSTRET => self.minstret = value,
num::MCYCLEH => {
if self.xlen != Xlen::Rv32 {
return None;
}
self.mcycle = (self.mcycle & 0xffff_ffff) | (value << 32);
}
num::MINSTRETH => {
if self.xlen != Xlen::Rv32 {
return None;
}
self.minstret = (self.minstret & 0xffff_ffff) | (value << 32);
}
num::PMPCFG0..=num::PMPCFG15 => {
self.write_pmpcfg(num - num::PMPCFG0, value)?;
self.bump_translation();
}
num::PMPADDR0..=num::PMPADDR63 => {
let i = (num - num::PMPADDR0) as usize;
if i < self.pmp_count {
let locked = self.pmpcfg[i] & 0x80 != 0;
let next_tor_locked = i + 1 < self.pmp_count
&& self.pmpcfg[i + 1] & 0x80 != 0
&& (self.pmpcfg[i + 1] >> 3) & 3 == 1;
if !locked && !next_tor_locked {
self.pmpaddr[i] = value & 0x003f_ffff_ffff_ffff;
self.bump_translation();
}
}
}
num::TSELECT | num::TDATA1 | num::TDATA2 | num::TDATA3 | num::TCONTROL => {}
0xb03..=0xb1f | 0xb83..=0xb9f | 0x323..=0x33f => {}
_ => return None,
}
Some(new_pending)
}
fn satp_mode_supported(&self, value: u64) -> bool {
match self.xlen {
Xlen::Rv32 => true,
Xlen::Rv64 => matches!(value >> 60, 0 | 8),
}
}
fn align_epc(&self, value: u64) -> u64 {
if self.ext.c { value & !1 } else { value & !3 }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn csrs() -> Csrs {
Csrs::new(Xlen::Rv64, Extensions::GC, 0, PMP_ENTRIES)
}
#[test]
fn reset_starts_in_machine_mode_with_a_64_bit_misa() {
let c = csrs();
assert_eq!(c.priv_mode, Priv::Machine);
assert_eq!(c.misa() >> 62, 2, "MXL must say 64-bit");
for letter in ['i', 'm', 'a', 'f', 'd', 'c', 's', 'u'] {
let bit = 1u64 << (letter as u32 - 'a' as u32);
assert_ne!(c.misa() & bit, 0, "misa is missing {letter}");
}
}
#[test]
fn read_only_csrs_reject_writes() {
let mut c = csrs();
assert!(c.read(num::MHARTID, 0).is_some());
assert!(c.write(num::MHARTID, 1, 0).is_none());
assert!(c.read(num::CYCLE, 0).is_some());
assert!(c.write(num::CYCLE, 1, 0).is_none());
}
#[test]
fn privilege_gates_the_machine_registers() {
let mut c = csrs();
c.priv_mode = Priv::Supervisor;
assert!(c.read(num::MSTATUS, 0).is_none());
assert!(c.write(num::MSTATUS, 0, 0).is_none());
assert!(c.read(num::SSTATUS, 0).is_some());
c.priv_mode = Priv::User;
assert!(c.read(num::SSTATUS, 0).is_none());
}
#[test]
fn sstatus_is_a_masked_view_of_mstatus() {
let mut c = csrs();
c.write(num::MSTATUS, status::MIE | status::SIE | status::MPP, 0)
.unwrap();
let s = c.read(num::SSTATUS, 0).unwrap();
assert_ne!(s & status::SIE, 0);
assert_eq!(s & status::MIE, 0, "MIE is not visible in sstatus");
assert_eq!(s & status::MPP, 0, "MPP is not visible in sstatus");
c.write(num::SSTATUS, 0, 0).unwrap();
assert_ne!(c.mstatus & status::MIE, 0);
assert_eq!(c.mstatus & status::SIE, 0);
}
#[test]
fn sie_and_sip_are_filtered_through_mideleg() {
let mut c = csrs();
c.write(num::MIDELEG, irq::SSI | irq::STI, 0).unwrap();
c.write(num::MIE, irq::ALL, 0).unwrap();
let sie = c.read(num::SIE, 0).unwrap();
assert_eq!(sie, irq::SSI | irq::STI);
c.priv_mode = Priv::Supervisor;
c.write(num::SIE, 0, 0).unwrap();
assert_eq!(c.mie & (irq::SSI | irq::STI), 0);
assert_ne!(c.mie & irq::MEI, 0, "the machine bits survive");
}
#[test]
fn mip_writes_reach_the_lines_not_the_csr_file() {
let mut c = csrs();
let out = c.write(num::MIP, irq::SSI | irq::MEI, 0).unwrap();
assert_eq!(out, Some(irq::SSI));
}
#[test]
fn medeleg_cannot_delegate_a_machine_ecall() {
let mut c = csrs();
c.write(num::MEDELEG, u64::MAX, 0).unwrap();
assert_eq!(c.medeleg & (1 << cause::ECALL_M), 0);
}
#[test]
fn satp_rejects_a_mode_this_core_cannot_walk() {
let mut c = csrs();
c.write(num::SATP, 9 << 60, 0).unwrap();
assert_eq!(c.satp, 0, "an unsupported mode leaves satp unchanged");
c.write(num::SATP, 8 << 60 | 0x1234, 0).unwrap();
assert_eq!(c.satp >> 60, 8);
}
#[test]
fn epc_is_forced_to_an_aligned_address() {
let mut c = csrs();
c.write(num::MEPC, 0x1003, 0).unwrap();
assert_eq!(c.mepc, 0x1002, "C is present, so bit 1 survives");
let mut c = Csrs::new(Xlen::Rv64, Extensions::I, 0, PMP_ENTRIES);
c.write(num::MEPC, 0x1003, 0).unwrap();
assert_eq!(c.mepc, 0x1000, "without C the low two bits are zero");
}
#[test]
fn floating_point_csrs_need_fs_on() {
let mut c = csrs();
assert_eq!(c.fs(), status::FS_OFF);
assert!(c.read(num::FCSR, 0).is_none());
c.write(num::MSTATUS, 1 << status::FS_SHIFT, 0).unwrap();
assert!(c.read(num::FCSR, 0).is_some());
c.write(num::FRM, 3, 0).unwrap();
assert_eq!(c.read(num::FRM, 0).unwrap(), 3);
assert_eq!(c.read(num::FCSR, 0).unwrap(), 3 << 5);
assert_eq!(c.fs(), status::FS_DIRTY, "an FP write dirties the state");
assert_ne!(c.read_mstatus() >> 63, 0);
}
#[test]
fn counters_are_gated_by_mcounteren() {
let mut c = csrs();
c.priv_mode = Priv::Supervisor;
assert!(c.read(num::CYCLE, 0).is_none());
c.priv_mode = Priv::Machine;
c.write(num::MCOUNTEREN, 1, 0).unwrap();
c.priv_mode = Priv::Supervisor;
assert!(c.read(num::CYCLE, 0).is_some());
assert!(c.read(num::TIME, 0).is_none());
}
#[test]
fn pmp_configuration_packs_eight_entries_per_register_on_rv64() {
let mut c = csrs();
c.write(num::PMPCFG0, 0x0f0f_0f0f_0f0f_0f0f, 0).unwrap();
assert_eq!(c.pmpcfg[0], 0x0f);
assert_eq!(c.pmpcfg[7], 0x0f);
assert_eq!(c.read(num::PMPCFG0, 0).unwrap(), 0x0f0f_0f0f_0f0f_0f0f);
assert!(c.read(num::PMPCFG0 + 1, 0).is_none());
c.write(num::PMPCFG0 + 2, 0x8f, 0).unwrap();
assert_eq!(c.pmpcfg[8], 0x8f);
c.write(num::PMPCFG0 + 2, 0x00, 0).unwrap();
assert_eq!(c.pmpcfg[8], 0x8f);
}
#[test]
fn every_named_csr_has_a_name() {
for (number, expected) in [
(num::MSTATUS, "mstatus"),
(num::SATP, "satp"),
(num::MHARTID, "mhartid"),
(num::FCSR, "fcsr"),
(num::PMPADDR0, "pmpaddr0"),
] {
assert_eq!(csr_name(number), Some(expected));
}
assert_eq!(csr_name(0x7ff), None);
let c = csrs();
for number in 0u32..0x1000 {
let banked = (num::PMPCFG0..=num::PMPADDR63).contains(&number)
|| (0xb03..=0xc9f).contains(&number)
|| (0x323..=0x33f).contains(&number);
if c.read(number, 0).is_some() && !banked {
assert!(csr_name(number).is_some(), "csr {number:#x} has no name");
}
}
}
#[test]
fn unknown_csrs_are_illegal() {
let c = csrs();
assert!(c.read(0x7ff, 0).is_none());
assert!(c.read(0x200, 0).is_none());
}
#[test]
fn interrupt_lines_are_atomic_and_outside_the_lock() {
let lines = Lines::default();
lines.set_pending(irq::MTI, true);
assert_eq!(lines.pending(), irq::MTI);
lines.set_pending(irq::MEI, true);
assert_eq!(lines.pending(), irq::MTI | irq::MEI);
lines.set_pending(irq::MTI, false);
assert_eq!(lines.pending(), irq::MEI);
assert!(!lines.take_reset_request());
lines.request_reset();
assert!(lines.take_reset_request());
assert!(!lines.take_reset_request());
}
}