use std::ffi::c_void;
pub use dsp56300_core::{
MemSpace, PC_MASK, PERIPH_BASE, PERIPH_SIZE, REG_MASKS, mask_pc, mask_reg, reg, sr,
};
#[derive(Clone, Copy)]
pub enum RegionKind {
Buffer { base: *mut u32, offset: u32 },
Callback {
opaque: *mut c_void,
read_fn: unsafe extern "C" fn(*mut c_void, u32) -> u32,
write_fn: unsafe extern "C" fn(*mut c_void, u32, u32),
},
}
unsafe impl Send for RegionKind {}
unsafe impl Sync for RegionKind {}
#[derive(Clone, Copy)]
pub struct MemoryRegion {
pub start: u32,
pub end: u32,
pub kind: RegionKind,
}
#[derive(Clone, Default)]
pub struct MemoryMap {
pub x_regions: Vec<MemoryRegion>,
pub y_regions: Vec<MemoryRegion>,
pub p_regions: Vec<MemoryRegion>,
}
impl MemoryMap {
pub fn regions(&self, space: MemSpace) -> &[MemoryRegion] {
match space {
MemSpace::X => &self.x_regions,
MemSpace::Y => &self.y_regions,
MemSpace::P => &self.p_regions,
}
}
pub fn lookup(&self, space: MemSpace, addr: u32) -> Option<&MemoryRegion> {
self.regions(space)
.iter()
.find(|r| addr >= r.start && addr < r.end)
}
pub fn read_pram(&self, addr: u32) -> u32 {
for region in &self.p_regions {
if addr >= region.start && addr < region.end {
let raw = match region.kind {
RegionKind::Buffer { base, offset } => {
let idx = (addr - region.start + offset) as usize;
unsafe { *base.add(idx) }
}
RegionKind::Callback {
opaque, read_fn, ..
} => unsafe { read_fn(opaque, addr) },
};
return raw & PC_MASK;
}
}
0
}
pub fn p_space_end(&self) -> u32 {
self.p_regions.iter().map(|r| r.end).max().unwrap_or(0)
}
pub fn test(xram: &mut [u32], yram: &mut [u32], pram: &mut [u32]) -> Self {
MemoryMap {
x_regions: vec![MemoryRegion {
start: 0,
end: xram.len() as u32,
kind: RegionKind::Buffer {
base: xram.as_mut_ptr(),
offset: 0,
},
}],
y_regions: vec![MemoryRegion {
start: 0,
end: yram.len() as u32,
kind: RegionKind::Buffer {
base: yram.as_mut_ptr(),
offset: 0,
},
}],
p_regions: vec![MemoryRegion {
start: 0,
end: pram.len() as u32,
kind: RegionKind::Buffer {
base: pram.as_mut_ptr(),
offset: 0,
},
}],
}
}
}
#[derive(Clone, Default)]
pub struct CreateInfo {
pub memory_map: MemoryMap,
}
impl From<MemoryMap> for CreateInfo {
fn from(memory_map: MemoryMap) -> Self {
Self { memory_map }
}
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PowerState {
Normal = 0,
Wait = 1,
Stop = 2,
}
impl From<u8> for PowerState {
fn from(v: u8) -> Self {
match v {
1 => Self::Wait,
2 => Self::Stop,
_ => Self::Normal,
}
}
}
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum InterruptState {
None = 0,
Fast = 1,
Long = 2,
}
impl From<u8> for InterruptState {
fn from(v: u8) -> Self {
match v {
1 => Self::Fast,
2 => Self::Long,
_ => Self::None,
}
}
}
impl std::fmt::Display for InterruptState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => f.write_str("none"),
Self::Fast => f.write_str("fast"),
Self::Long => f.write_str("long"),
}
}
}
#[derive(Clone)]
pub struct InterruptPipeline {
pub state: InterruptState,
pub pending_bits: [u64; 2],
pub pipeline_stage: u8,
pub vector_addr: u32,
pub saved_pc: u32,
pub ipl: [i8; interrupt::COUNT],
pub ipl_to_raise: u8,
}
impl Default for InterruptPipeline {
fn default() -> Self {
Self::new()
}
}
impl InterruptPipeline {
pub fn new() -> Self {
let mut ipl = [-1i8; interrupt::COUNT];
ipl[interrupt::RESET] = 3;
ipl[interrupt::ILLEGAL] = 3;
ipl[interrupt::STACK_ERROR] = 3;
ipl[interrupt::TRAP] = 3;
Self {
state: InterruptState::None,
pending_bits: [0; 2],
pipeline_stage: 0,
vector_addr: 0xFFFF,
saved_pc: 0xFFFF,
ipl,
ipl_to_raise: 0,
}
}
pub fn has_pending(&self) -> bool {
self.pending_bits[0] != 0 || self.pending_bits[1] != 0
}
pub fn pending(&self, i: usize) -> bool {
self.pending_bits[i / 64] & (1u64 << (i % 64)) != 0
}
pub fn set_pending(&mut self, i: usize) {
self.pending_bits[i / 64] |= 1u64 << (i % 64);
}
pub fn clear_pending(&mut self, i: usize) {
self.pending_bits[i / 64] &= !(1u64 << (i % 64));
}
pub fn add(&mut self, inter: usize) {
if inter >= interrupt::COUNT || self.ipl[inter] == -1 {
return;
}
self.set_pending(inter);
}
}
pub mod interrupt {
pub const COUNT: usize = 128;
pub const RESET: usize = 0; pub const STACK_ERROR: usize = 1; pub const ILLEGAL: usize = 2; pub const DEBUG: usize = 3; pub const TRAP: usize = 4; pub const NMI: usize = 5;
pub const fn vector_addr(i: usize) -> u16 {
(i as u16) * 2
}
}
pub struct PramDirtyBitmap {
pram_size: usize,
pub dirty: Vec<u64>,
pub generation: u32,
}
impl PramDirtyBitmap {
pub fn new(pram_size: usize) -> Self {
Self {
pram_size,
dirty: vec![0u64; pram_size.div_ceil(64)],
generation: 0,
}
}
pub fn mark_dirty(&mut self, addr: u32) {
let addr = addr as usize;
if addr < self.pram_size {
self.dirty[addr / 64] |= 1u64 << (addr % 64);
self.generation = self.generation.wrapping_add(1);
}
}
pub fn is_range_dirty(&self, start_pc: u32, end_pc: u32) -> bool {
let lo = start_pc as usize;
let hi = (end_pc as usize).min(self.pram_size);
if lo >= hi {
return false;
}
let first_word = lo / 64;
let last_word = (hi - 1) / 64;
if first_word == last_word {
let mask = Self::range_mask(lo % 64, hi.wrapping_sub(first_word * 64).min(64));
return self.dirty[first_word] & mask != 0;
}
let first_mask = !0u64 << (lo % 64);
if self.dirty[first_word] & first_mask != 0 {
return true;
}
if self.dirty[(first_word + 1)..last_word]
.iter()
.any(|&w| w != 0)
{
return true;
}
let last_bits = hi - last_word * 64;
let last_mask = if last_bits >= 64 {
!0u64
} else {
(1u64 << last_bits) - 1
};
self.dirty[last_word] & last_mask != 0
}
pub fn clear_dirty_range(&mut self, start_pc: u32, end_pc: u32) {
let lo = start_pc as usize;
let hi = (end_pc as usize).min(self.pram_size);
if lo >= hi {
return;
}
let first_word = lo / 64;
let last_word = (hi - 1) / 64;
if first_word == last_word {
let mask = Self::range_mask(lo % 64, hi.wrapping_sub(first_word * 64).min(64));
self.dirty[first_word] &= !mask;
return;
}
self.dirty[first_word] &= !(!0u64 << (lo % 64));
self.dirty[(first_word + 1)..last_word].fill(0);
let last_bits = hi - last_word * 64;
let last_mask = if last_bits >= 64 {
!0u64
} else {
(1u64 << last_bits) - 1
};
self.dirty[last_word] &= !last_mask;
}
pub fn range_mask(lo_bit: usize, hi_bit: usize) -> u64 {
debug_assert!(lo_bit < 64 && hi_bit <= 64 && lo_bit < hi_bit);
let top = if hi_bit >= 64 {
!0u64
} else {
(1u64 << hi_bit) - 1
};
top & !((1u64 << lo_bit) - 1)
}
}
pub struct DspState {
pub pc: u32,
pub pc_advance: u32,
pub cycle_count: u32,
pub registers: [u32; reg::COUNT],
pub stack: [[u32; 16]; 2],
pub loop_rep: bool,
pub pc_on_rep: bool,
pub interrupts: InterruptPipeline,
pub cycle_budget: i32,
pub halt_requested: bool,
pub exit_requested: bool,
pub power_state: PowerState,
pub pram_dirty: PramDirtyBitmap,
pub map: MemoryMap,
warned_bits: u32,
}
impl DspState {
pub fn new(info: impl Into<CreateInfo>) -> Self {
let map = info.into().memory_map;
let mut registers = [0u32; reg::COUNT];
for i in 0..8 {
registers[reg::M0 + i] = REG_MASKS[reg::M0];
}
registers[reg::SR] = 0xC0_0300;
registers[reg::OMR] = 0x0300;
Self {
pc: 0,
pc_advance: 0,
cycle_count: 0,
registers,
stack: [[0; 16]; 2],
loop_rep: false,
pc_on_rep: false,
cycle_budget: 0,
halt_requested: false,
exit_requested: false,
power_state: PowerState::Normal,
interrupts: InterruptPipeline::new(),
pram_dirty: PramDirtyBitmap::new(map.p_space_end() as usize),
map,
warned_bits: 0,
}
}
pub fn stack_push(&mut self, ssh_val: u32, ssl_val: u32) {
let stack_error = self.registers[reg::SP] & (1 << 4); let underflow = self.registers[reg::SP] & (1 << 5); let stack = (self.registers[reg::SP] & 0xF) + 1;
if stack_error == 0 && (stack & (1 << 4)) != 0 {
self.interrupts.add(interrupt::STACK_ERROR);
}
self.registers[reg::SP] = (underflow | stack_error | stack) & 0x3F;
let idx = (stack & 0xF) as usize;
if stack != 0 {
self.stack[0][idx] = ssh_val & REG_MASKS[reg::SSH];
self.stack[1][idx] = ssl_val & REG_MASKS[reg::SSL];
}
self.registers[reg::SSH] = self.stack[0][idx];
self.registers[reg::SSL] = self.stack[1][idx];
}
pub fn stack_pop(&mut self) -> (u32, u32) {
let stack_error = self.registers[reg::SP] & (1 << 4);
let underflow = self.registers[reg::SP] & (1 << 5);
let stack = (self.registers[reg::SP] & 0xF).wrapping_sub(1);
if stack_error == 0 && (stack & (1 << 4)) != 0 {
self.interrupts.add(interrupt::STACK_ERROR);
}
self.registers[reg::SP] = (underflow | stack_error | stack) & 0x3F;
let ssh = self.registers[reg::SSH];
let ssl = self.registers[reg::SSL];
let idx = (stack & 0xF) as usize;
self.registers[reg::SSH] = self.stack[0][idx];
self.registers[reg::SSL] = self.stack[1][idx];
(ssh, ssl)
}
#[inline]
fn check_unimplemented_modes(&mut self) {
const SR_UNIMPL: u32 = (1 << sr::SC) | (1 << sr::SA) | (1 << sr::DM);
const OMR_UNIMPL: u32 = (1 << 20) | (1 << 7); let sr = self.registers[reg::SR];
let omr = self.registers[reg::OMR];
if (sr & SR_UNIMPL) | (omr & OMR_UNIMPL) == 0 {
return;
}
self.check_unimplemented_modes_slow();
}
#[cold]
fn check_unimplemented_modes_slow(&mut self) {
let sr = self.registers[reg::SR];
let omr = self.registers[reg::OMR];
const W_SC: u32 = 1 << 0;
const W_SA: u32 = 1 << 1;
const W_DM: u32 = 1 << 2;
const W_SEN: u32 = 1 << 3;
const W_MS: u32 = 1 << 4;
let mut new_warnings = 0u32;
if sr & (1 << sr::SC) != 0 && self.warned_bits & W_SC == 0 {
eprintln!(
"WARNING: DSP56300 16-bit compatibility mode (SC, SR bit 13) enabled at PC=${:06X} - not implemented",
self.pc
);
new_warnings |= W_SC;
}
if sr & (1 << sr::SA) != 0 && self.warned_bits & W_SA == 0 {
eprintln!(
"WARNING: DSP56300 16-bit arithmetic mode (SA, SR bit 17) enabled at PC=${:06X} - not implemented",
self.pc
);
new_warnings |= W_SA;
}
if sr & (1 << sr::DM) != 0 && self.warned_bits & W_DM == 0 {
eprintln!(
"WARNING: DSP56300 double-precision multiply mode (DM, SR bit 14) enabled at PC=${:06X} - not implemented",
self.pc
);
new_warnings |= W_DM;
}
if omr & (1 << 20) != 0 && self.warned_bits & W_SEN == 0 {
eprintln!(
"WARNING: DSP56300 stack extension (SEN, OMR bit 20) enabled at PC=${:06X} - not implemented",
self.pc
);
new_warnings |= W_SEN;
}
if omr & (1 << 7) != 0 && self.warned_bits & W_MS == 0 {
eprintln!(
"WARNING: DSP56300 memory switch mode (MS, OMR bit 7) enabled at PC=${:06X} - not implemented",
self.pc
);
new_warnings |= W_MS;
}
self.warned_bits |= new_warnings;
}
pub fn advance_pc(&mut self) {
self.check_unimplemented_modes();
if self.loop_rep {
if !self.pc_on_rep {
self.registers[reg::LC] =
self.registers[reg::LC].wrapping_sub(1) & REG_MASKS[reg::LC];
if self.registers[reg::LC] > 0 {
self.pc_advance = 0; } else {
self.loop_rep = false;
self.registers[reg::LC] = self.registers[reg::TEMP];
}
} else {
if self.registers[reg::LC] == 0 {
self.registers[reg::LC] = 0x10000;
}
self.pc_on_rep = false;
}
}
self.pc = mask_pc(self.pc + self.pc_advance);
if (self.registers[reg::SR] & (1 << sr::LF)) != 0
&& self.pc == mask_pc(self.registers[reg::LA] + 1)
{
self.registers[reg::LC] = self.registers[reg::LC].wrapping_sub(1) & REG_MASKS[reg::LC];
if self.registers[reg::LC] == 0 && (self.registers[reg::SR] & (1 << sr::FV)) == 0 {
let (_saved_pc, saved_sr) = self.stack_pop();
let lf_fv_mask = (1 << sr::LF) | (1 << sr::FV);
self.registers[reg::SR] =
(self.registers[reg::SR] & !lf_fv_mask) | (saved_sr & lf_fv_mask);
let (la, lc) = self.stack_pop();
self.registers[reg::LA] = la;
self.registers[reg::LC] = lc;
} else {
self.pc = self.registers[reg::SSH];
}
}
}
pub fn process_pending_interrupts(&mut self) {
if self.loop_rep {
return;
}
if self.interrupts.state == InterruptState::Fast {
match self.interrupts.pipeline_stage {
5 => {
self.interrupts.pipeline_stage -= 1;
return;
}
4 => {
self.interrupts.saved_pc = self.pc;
self.pc = self.interrupts.vector_addr;
let instr = self.read_memory(MemSpace::P, self.pc);
self.detect_long_interrupt(instr);
self.interrupts.pipeline_stage -= 1;
return;
}
3 => {
if self.pc == mask_pc(self.interrupts.vector_addr + 1) {
let instr = self.read_memory(MemSpace::P, self.pc);
self.detect_long_interrupt(instr);
}
self.interrupts.pipeline_stage -= 1;
return;
}
2 => {
if self.interrupts.state != InterruptState::Long
&& self.pc == mask_pc(self.interrupts.vector_addr + 2)
{
self.pc = self.interrupts.saved_pc;
}
self.interrupts.pipeline_stage -= 1;
return;
}
1 => {
self.interrupts.pipeline_stage -= 1;
return;
}
0 => {
self.interrupts.saved_pc = 0xFFFF;
self.interrupts.vector_addr = 0xFFFF;
self.interrupts.state = InterruptState::None;
}
_ => return,
}
}
if !self.interrupts.has_pending() {
return;
}
let ipl_sr = ((self.registers[reg::SR] >> sr::I0) & 0x3) as i8;
let mut index: Option<usize> = None;
let mut ipl_to_raise: i8 = -1;
for i in 0..interrupt::COUNT {
if !self.interrupts.pending(i) {
continue;
}
if self.interrupts.ipl[i] == 3 {
index = Some(i);
break;
}
if self.interrupts.ipl[i] < ipl_sr {
continue;
}
if self.interrupts.ipl[i] > ipl_to_raise {
index = Some(i);
ipl_to_raise = self.interrupts.ipl[i];
}
}
let Some(idx) = index else { return };
self.interrupts.clear_pending(idx);
let new_ipl = (self.interrupts.ipl[idx] + 1).min(3);
let vba = self.registers[reg::VBA] & 0xFFFF00;
self.interrupts.vector_addr = vba | interrupt::vector_addr(idx) as u32;
self.interrupts.pipeline_stage = 5;
self.interrupts.state = InterruptState::Fast;
self.interrupts.ipl_to_raise = new_ipl as u8;
}
fn detect_long_interrupt(&mut self, instr: u32) {
use dsp56300_core::{Instruction, decode};
let is_long = matches!(
decode::decode(instr),
Instruction::Jsr { .. }
| Instruction::JsrEa { .. }
| Instruction::Jscc { .. }
| Instruction::JsccEa { .. }
| Instruction::Bsr { .. }
| Instruction::BsrLong
| Instruction::BsrRn { .. }
| Instruction::Bscc { .. }
| Instruction::BsccLong { .. }
| Instruction::BsccRn { .. }
| Instruction::JsclrEa { .. }
| Instruction::JsclrAa { .. }
| Instruction::JsclrPp { .. }
| Instruction::JsclrQq { .. }
| Instruction::JsclrReg { .. }
| Instruction::JssetEa { .. }
| Instruction::JssetAa { .. }
| Instruction::JssetPp { .. }
| Instruction::JssetQq { .. }
| Instruction::JssetReg { .. }
| Instruction::BsclrEa { .. }
| Instruction::BsclrAa { .. }
| Instruction::BsclrPp { .. }
| Instruction::BsclrQq { .. }
| Instruction::BsclrReg { .. }
| Instruction::BssetEa { .. }
| Instruction::BssetAa { .. }
| Instruction::BssetPp { .. }
| Instruction::BssetQq { .. }
| Instruction::BssetReg { .. }
);
if is_long && self.interrupts.state != InterruptState::Long {
self.interrupts.state = InterruptState::Long;
self.stack_push(self.interrupts.saved_pc, self.registers[reg::SR]);
let clear_mask = (1 << sr::LF)
| (1 << sr::S1)
| (1 << sr::S0)
| (1 << sr::I0)
| (1 << sr::I1)
| (1 << sr::SA);
self.registers[reg::SR] &= !clear_mask;
self.registers[reg::SR] |= (self.interrupts.ipl_to_raise as u32) << sr::I0;
}
}
pub fn read_memory(&self, space: MemSpace, addr: u32) -> u32 {
let regions = match space {
MemSpace::X => &self.map.x_regions,
MemSpace::Y => &self.map.y_regions,
MemSpace::P => &self.map.p_regions,
};
for region in regions {
if addr >= region.start && addr < region.end {
let raw = match region.kind {
RegionKind::Buffer { base, offset } => {
let idx = (addr - region.start + offset) as usize;
unsafe { *base.add(idx) }
}
RegionKind::Callback {
opaque, read_fn, ..
} => unsafe { read_fn(opaque, addr) },
};
return raw & 0x00FF_FFFF;
}
}
0
}
pub fn update_rn(&mut self, numreg: usize, modifier: i32) {
let r_mask = REG_MASKS[reg::R0];
let m_reg = self.registers[reg::M0 + numreg] & REG_MASKS[reg::M0];
if m_reg == REG_MASKS[reg::M0] {
let value = (self.registers[reg::R0 + numreg] as i32).wrapping_add(modifier);
self.registers[reg::R0 + numreg] = (value as u32) & r_mask;
} else if m_reg == 0 {
self.update_rn_bitreverse(numreg);
} else if (m_reg & 0xC000) == 0x8000 {
let modulo = (m_reg & 0x3FFF) + 1; let r_val = self.registers[reg::R0 + numreg];
let base = r_val & !(modulo - 1);
let offset = r_val & (modulo - 1);
let new_offset = (offset as i32).wrapping_add(modifier) as u32 & (modulo - 1);
self.registers[reg::R0 + numreg] = (base | new_offset) & r_mask;
} else if m_reg <= 0x7FFFFF {
self.update_rn_modulo(numreg, modifier);
}
}
fn update_rn_bitreverse(&mut self, numreg: usize) {
let r_mask = REG_MASKS[reg::R0];
let n_val = self.registers[reg::N0 + numreg] & REG_MASKS[reg::N0];
let revbits: u32 = if n_val == 0 {
24
} else {
n_val.trailing_zeros() + 1
}
.min(24);
let r_reg = self.registers[reg::R0 + numreg] & r_mask;
let high_mask = r_mask.wrapping_shl(revbits) & r_mask;
let mut value = r_reg & high_mask;
for i in 0..revbits {
if r_reg & (1u32 << i) != 0 {
value |= 1u32 << (revbits - i - 1);
}
}
let revmask = 1u32.wrapping_shl(revbits).wrapping_sub(1);
value = (value + 1) & revmask;
let r_new = (r_reg & high_mask) | value;
let mut result = r_new & high_mask;
for i in 0..revbits {
if r_new & (1u32 << i) != 0 {
result |= 1u32 << (revbits - i - 1);
}
}
self.registers[reg::R0 + numreg] = result & r_mask;
}
fn update_rn_modulo(&mut self, numreg: usize, mut modifier: i32) {
let r_mask = REG_MASKS[reg::R0];
let modulo = (self.registers[reg::M0 + numreg] & REG_MASKS[reg::M0]).wrapping_add(1);
let orig_modifier = modifier;
let mut bufsize: u32 = 1;
let mut bufmask: u32 = r_mask;
while bufsize < modulo {
bufsize <<= 1;
bufmask <<= 1;
}
bufmask &= r_mask;
let lobound = self.registers[reg::R0 + numreg] & bufmask;
let hibound = lobound.wrapping_add(modulo).wrapping_sub(1) & r_mask;
let mut r_reg = self.registers[reg::R0 + numreg] as i32;
if orig_modifier > (modulo as i32) {
let bs = bufsize as i32;
while modifier > bs {
r_reg = r_reg.wrapping_add(bufsize as i32);
modifier = modifier.wrapping_sub(bufsize as i32);
}
while modifier < -bs {
r_reg = r_reg.wrapping_sub(bufsize as i32);
modifier = modifier.wrapping_add(bufsize as i32);
}
}
r_reg = r_reg.wrapping_add(modifier);
if orig_modifier != (modulo as i32) {
if r_reg > (hibound as i32) {
r_reg = r_reg.wrapping_sub(modulo as i32);
} else if r_reg < (lobound as i32) {
r_reg = r_reg.wrapping_add(modulo as i32);
}
}
self.registers[reg::R0 + numreg] = (r_reg as u32) & r_mask;
}
pub fn write_memory(&mut self, space: MemSpace, addr: u32, value: u32) {
let value = value & 0x00FF_FFFF;
let regions = match space {
MemSpace::X => &self.map.x_regions,
MemSpace::Y => &self.map.y_regions,
MemSpace::P => &self.map.p_regions,
};
for region in regions {
if addr >= region.start && addr < region.end {
match region.kind {
RegionKind::Buffer { base, offset } => {
let idx = (addr - region.start + offset) as usize;
unsafe {
*base.add(idx) = value;
}
}
RegionKind::Callback {
opaque, write_fn, ..
} => unsafe {
write_fn(opaque, addr, value);
},
}
return;
}
}
}
}
pub unsafe extern "C" fn jit_update_rn(state: *mut DspState, numreg: u32, modifier: i32) {
let state = unsafe { &mut *state };
state.update_rn(numreg as usize, modifier);
}
pub unsafe extern "C" fn jit_write_ssh(state: *mut DspState, value: u32) {
let state = unsafe { &mut *state };
let stack_error = state.registers[reg::SP] & (1 << 4);
let underflow = state.registers[reg::SP] & (1 << 5);
let stack = (state.registers[reg::SP] & 0xF) + 1;
if stack_error == 0 && (stack & (1 << 4)) != 0 {
state.interrupts.add(interrupt::STACK_ERROR);
}
state.registers[reg::SP] = (underflow | stack_error | stack) & 0x3F;
let idx = (stack & 0xF) as usize;
if stack != 0 {
state.stack[0][idx] = value & REG_MASKS[reg::SSH];
}
state.registers[reg::SSH] = state.stack[0][idx];
state.registers[reg::SSL] = state.stack[1][idx];
}
pub unsafe extern "C" fn jit_read_ssh(state: *mut DspState) -> u32 {
let state = unsafe { &mut *state };
let (ssh, _ssl) = state.stack_pop();
ssh
}
pub unsafe extern "C" fn jit_write_ssl(state: *mut DspState, value: u32) {
let state = unsafe { &mut *state };
let idx = (state.registers[reg::SP] & 0xF) as usize;
let value = if idx == 0 {
0
} else {
value & REG_MASKS[reg::SSL]
};
state.stack[1][idx] = value;
state.registers[reg::SSL] = value;
}
pub unsafe extern "C" fn jit_write_sp(state: *mut DspState, value: u32) {
let state = unsafe { &mut *state };
let mask = REG_MASKS[reg::SP];
let stack_error = state.registers[reg::SP] & (3 << 4);
if stack_error == 0 && (value & (3 << 4)) != 0 {
state.interrupts.add(interrupt::STACK_ERROR);
}
state.registers[reg::SP] = value & mask;
let idx = (state.registers[reg::SP] & 0xF) as usize;
state.registers[reg::SSH] = state.stack[0][idx];
state.registers[reg::SSL] = state.stack[1][idx];
}
pub unsafe extern "C" fn jit_read_mem(state: *mut DspState, space: MemSpace, address: u32) -> u32 {
unsafe { &*state }.read_memory(space, address)
}
pub unsafe extern "C" fn jit_write_mem(
state: *mut DspState,
space: MemSpace,
address: u32,
value: u32,
) {
let state = unsafe { &mut *state };
if space == MemSpace::P {
let masked = value & 0x00FF_FFFF;
let old = state.read_memory(MemSpace::P, address);
if old != masked {
state.write_memory(MemSpace::P, address, masked);
state.pram_dirty.mark_dirty(address);
}
} else {
state.write_memory(space, address, value)
}
}
pub unsafe extern "C" fn jit_rnd56(state: *mut DspState, val: i64) -> i64 {
let state = unsafe { &*state };
let sr = state.registers[reg::SR];
let convergent = sr & (1 << sr::RM) == 0;
let (r2, r1, r0) = if sr & (1 << sr::S0) != 0 {
let sum = val.wrapping_add(1 << 24); let s0 = (sum & 0xFF_FFFF) as u32;
let mut s1 = ((sum >> 24) & 0xFF_FFFF) as u32;
let s2 = ((sum >> 48) & 0xFF) as u32;
if convergent && s0 == 0 && (s1 & 1) == 0 {
s1 &= 0xFF_FFFF - 0x3;
}
s1 &= 0xFF_FFFE;
(s2, s1, 0u32)
} else if sr & (1 << sr::S1) != 0 {
let sum = val.wrapping_add(1 << 22);
let mut s0 = (sum & 0xFF_FFFF) as u32;
let s1 = ((sum >> 24) & 0xFF_FFFF) as u32;
let s2 = ((sum >> 48) & 0xFF) as u32;
if convergent && (s0 & 0x7F_FFFF) == 0 {
s0 = 0;
}
s0 &= 0x80_0000;
(s2, s1, s0)
} else {
let sum = val.wrapping_add(1 << 23);
let s0 = (sum & 0xFF_FFFF) as u32;
let mut s1 = ((sum >> 24) & 0xFF_FFFF) as u32;
let s2 = ((sum >> 48) & 0xFF) as u32;
if convergent && s0 == 0 {
s1 &= 0xFF_FFFE;
}
(s2, s1, 0u32)
};
((r2 as i64) << 48) | ((r1 as i64) << 24) | (r0 as i64)
}
pub unsafe extern "C" fn jit_update_nz(state: *mut DspState, acc_val: i64) {
let state = unsafe { &mut *state };
let sr = state.registers[reg::SR];
let reg0 = ((acc_val >> 48) & 0xFF) as u32; let reg1 = ((acc_val >> 24) & 0xFF_FFFF) as u32;
let scaling = (sr >> sr::S0) & 3;
let (e, u) = match scaling {
0 => {
let val_e = ((reg0 << 1) | (reg1 >> 23)) & 0x1FF;
let e = val_e != 0 && val_e != 0x1FF;
let bits = reg1 & 0xC0_0000;
let u = bits == 0 || bits == 0xC0_0000;
(e, u)
}
1 => {
let e = reg0 != 0 && reg0 != 0xFF;
let val = ((reg0 << 1) | (reg1 >> 23)) & 3;
let u = val == 0 || val == 3;
(e, u)
}
2 => {
let val_e = ((reg0 << 2) | (reg1 >> 22)) & 0x3FF;
let e = val_e != 0 && val_e != 0x3FF;
let bits = reg1 & 0x60_0000;
let u = bits == 0 || bits == 0x60_0000;
(e, u)
}
_ => (false, false), };
let n = (acc_val >> 55) & 1 != 0;
let z = (acc_val & 0x00FF_FFFF_FFFF_FFFF) == 0;
let clear_mask = !((1u32 << sr::E) | (1u32 << sr::U) | (1u32 << sr::N) | (1u32 << sr::Z));
let mut new_sr = sr & clear_mask;
if e {
new_sr |= 1 << sr::E;
}
if u {
new_sr |= 1 << sr::U;
}
if n {
new_sr |= 1 << sr::N;
}
if z {
new_sr |= 1 << sr::Z;
}
state.registers[reg::SR] = new_sr;
}
pub unsafe extern "C" fn jit_saturate_sm(state: *mut DspState, val: i64) -> i64 {
let state = unsafe { &*state };
let sr = state.registers[reg::SR];
if sr & (1 << sr::SM) == 0 {
return val; }
let b55 = (val >> 55) & 1;
let b48 = (val >> 48) & 1;
let b47 = (val >> 47) & 1;
let mismatch = (b55 ^ b48) | (b48 ^ b47);
if mismatch == 0 {
return val; }
let saturated = if b55 != 0 {
0x00FF_8000_0000_0000_u64 as i64
} else {
0x0000_7FFF_FFFF_FFFF_i64
};
saturated | (1i64 << 56) }
#[unsafe(no_mangle)]
pub unsafe extern "C" fn jit_read_accu24(state: *mut DspState, acc_idx: u32) -> u32 {
let state = unsafe { &mut *state };
let (a2, a1, a0) = if acc_idx == 0 {
(
state.registers[reg::A2],
state.registers[reg::A1],
state.registers[reg::A0],
)
} else {
(
state.registers[reg::B2],
state.registers[reg::B1],
state.registers[reg::B0],
)
};
let sr = state.registers[reg::SR];
let scaling = (sr >> sr::S0) & 3;
let combined = (a2 << 24) | a1;
let value = match scaling {
1 => combined >> 1, 2 => (combined << 1) | ((a0 >> 23) & 1), _ => combined, } & 0x00FF_FFFF;
let ok_pos = a2 == 0 && value <= 0x7F_FFFF;
let ok_neg = a2 == 0xFF && value >= 0x80_0000;
let mut no_limit = ok_pos || ok_neg;
if scaling == 2 {
let a1_bit23 = (a1 >> 23) & 1;
let ok_pos_s2 = a2 == 0 && value <= 0x7F_FFFF && a1_bit23 == 0;
let ok_neg_s2 = a2 == 0xFF && value >= 0x80_0000 && a1_bit23 != 0;
no_limit = ok_pos_s2 || ok_neg_s2;
}
let result = if no_limit {
value
} else {
if a2 & 0x80 != 0 { 0x80_0000 } else { 0x7F_FFFF }
};
let mut new_sr = sr;
if !no_limit {
new_sr |= 1 << sr::L;
}
let acc_packed =
((a2 as u64 & 0xFF) << 48) | ((a1 as u64 & 0xFF_FFFF) << 24) | (a0 as u64 & 0xFF_FFFF);
let s_bit = match scaling {
1 => ((acc_packed >> 45) ^ (acc_packed >> 44)) & 1, 2 => ((acc_packed >> 47) ^ (acc_packed >> 46)) & 1, _ => ((acc_packed >> 46) ^ (acc_packed >> 45)) & 1, };
if s_bit != 0 {
new_sr |= 1 << sr::S;
}
state.registers[reg::SR] = new_sr;
result | (if no_limit { 1 << 24 } else { 0 })
}
impl Default for DspState {
fn default() -> Self {
Self::new(MemoryMap::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
pub const PRAM_SIZE: usize = 4096;
pub const XRAM_SIZE: usize = 4096;
pub const YRAM_SIZE: usize = 2048;
#[test]
fn test_new_state_is_zeroed() {
let state = DspState::new(MemoryMap::default());
assert_eq!(state.pc, 0);
assert_eq!(state.cycle_count, 0);
assert!(!state.halt_requested);
assert_eq!(state.registers[reg::A1], 0);
}
#[test]
fn test_memory_read_write() {
let mut xram = [0u32; XRAM_SIZE];
let mut yram = [0u32; YRAM_SIZE];
let mut pram = [0u32; PRAM_SIZE];
let mut state = DspState::new(MemoryMap::test(&mut xram, &mut yram, &mut pram));
state.write_memory(MemSpace::X, 3, 0x123456);
assert_eq!(state.read_memory(MemSpace::X, 3), 0x123456);
state.write_memory(MemSpace::Y, 100, 0xABCDEF);
assert_eq!(state.read_memory(MemSpace::Y, 100), 0xABCDEF);
state.write_memory(MemSpace::P, 0x40, 0x0AF080);
assert_eq!(state.read_memory(MemSpace::P, 0x40), 0x0AF080);
}
#[test]
fn test_memory_24bit_mask() {
let mut xram = [0u32; XRAM_SIZE];
let mut yram = [0u32; YRAM_SIZE];
let mut pram = [0u32; PRAM_SIZE];
let mut state = DspState::new(MemoryMap::test(&mut xram, &mut yram, &mut pram));
state.write_memory(MemSpace::X, 0, 0xFF123456);
assert_eq!(state.read_memory(MemSpace::X, 0), 0x123456);
}
#[test]
fn test_out_of_bounds_read_returns_zero() {
let mut xram = [0u32; XRAM_SIZE];
let mut yram = [0u32; YRAM_SIZE];
let mut pram = [0u32; PRAM_SIZE];
let state = DspState::new(MemoryMap::test(&mut xram, &mut yram, &mut pram));
assert_eq!(state.read_memory(MemSpace::X, XRAM_SIZE as u32 + 100), 0);
assert_eq!(state.read_memory(MemSpace::Y, YRAM_SIZE as u32 + 100), 0);
assert_eq!(state.read_memory(MemSpace::P, PRAM_SIZE as u32 + 100), 0);
}
#[test]
fn test_bitreverse_basic() {
let mut state = DspState::new(MemoryMap::default());
state.registers[reg::M0] = 0;
state.registers[reg::N0] = 8;
state.registers[reg::R0] = 0;
let expected = [8, 4, 12, 2, 10, 6, 14, 1];
for &exp in &expected {
state.update_rn(0, 1); assert_eq!(
state.registers[reg::R0],
exp,
"expected R0={exp} after bit-reverse step"
);
}
}
#[test]
fn test_bitreverse_n0_4() {
let mut state = DspState::new(MemoryMap::default());
state.registers[reg::M0] = 0;
state.registers[reg::N0] = 4;
state.registers[reg::R0] = 0;
let expected = [4, 2, 6, 1, 5, 3, 7, 0];
for &exp in &expected {
state.update_rn(0, 1);
assert_eq!(state.registers[reg::R0], exp);
}
}
#[test]
fn test_interrupt_priority_level3_wins() {
let mut xram = [0u32; XRAM_SIZE];
let mut yram = [0u32; YRAM_SIZE];
let mut pram = [0u32; PRAM_SIZE];
pram[0x3E] = 0x000000; pram[0x3F] = 0x000000; let mut state = DspState::new(MemoryMap::test(&mut xram, &mut yram, &mut pram));
state.interrupts.vector_addr = 0xFFFF;
state.interrupts.saved_pc = 0xFFFF;
state.registers[reg::SR] = 3 << sr::I0;
state.interrupts.ipl[interrupt::ILLEGAL] = 3;
state.interrupts.add(interrupt::ILLEGAL);
assert!(state.interrupts.has_pending());
state.process_pending_interrupts();
assert!(!state.interrupts.has_pending());
assert_eq!(state.interrupts.state, InterruptState::Fast);
}
#[test]
fn test_interrupt_masked_by_ipl() {
let mut state = DspState::new(MemoryMap::default());
state.interrupts.vector_addr = 0xFFFF;
state.interrupts.saved_pc = 0xFFFF;
state.registers[reg::SR] = 2 << sr::I0;
state.interrupts.ipl[interrupt::TRAP] = 1;
state.interrupts.add(interrupt::TRAP);
state.process_pending_interrupts();
assert!(state.interrupts.has_pending());
assert!(state.interrupts.pending(interrupt::TRAP));
}
#[test]
fn test_interrupt_highest_priority_wins() {
let mut xram = [0u32; XRAM_SIZE];
let mut yram = [0u32; YRAM_SIZE];
let mut pram = [0u32; PRAM_SIZE];
let vec_addr = interrupt::vector_addr(interrupt::ILLEGAL) as usize;
pram[vec_addr] = 0x000000;
pram[vec_addr + 1] = 0x000000;
let mut state = DspState::new(MemoryMap::test(&mut xram, &mut yram, &mut pram));
state.interrupts.vector_addr = 0xFFFF;
state.interrupts.saved_pc = 0xFFFF;
state.registers[reg::SR] = 0;
state.interrupts.ipl[interrupt::TRAP] = 1;
state.interrupts.ipl[interrupt::ILLEGAL] = 2;
state.interrupts.add(interrupt::TRAP);
state.interrupts.add(interrupt::ILLEGAL);
state.process_pending_interrupts();
assert!(!state.interrupts.pending(interrupt::ILLEGAL));
assert!(state.interrupts.pending(interrupt::TRAP));
assert!(state.interrupts.has_pending());
}
fn pack_acc56(a2: u32, a1: u32, a0: u32) -> i64 {
((a2 as i64 & 0xFF) << 48) | ((a1 as i64 & 0xFF_FFFF) << 24) | (a0 as i64 & 0xFF_FFFF)
}
#[test]
fn test_rnd56_default_round_up() {
let mut state = DspState::new(MemoryMap::default());
let val = pack_acc56(0x00, 0x400000, 0x800001);
let result = unsafe { jit_rnd56(&mut state as *mut DspState, val) };
let r1 = ((result >> 24) & 0xFF_FFFF) as u32;
let r0 = (result & 0xFF_FFFF) as u32;
assert_eq!(r1, 0x400001, "A1 should round up");
assert_eq!(r0, 0, "A0 should be cleared");
}
#[test]
fn test_rnd56_default_round_down() {
let mut state = DspState::new(MemoryMap::default());
let val = pack_acc56(0x00, 0x400000, 0x7FFFFF);
let result = unsafe { jit_rnd56(&mut state as *mut DspState, val) };
let r1 = ((result >> 24) & 0xFF_FFFF) as u32;
assert_eq!(r1, 0x400000, "A1 should stay");
}
#[test]
fn test_rnd56_convergent_half_even() {
let mut state = DspState::new(MemoryMap::default());
let val = pack_acc56(0x00, 0x400000, 0x800000);
let result = unsafe { jit_rnd56(&mut state as *mut DspState, val) };
let r1 = ((result >> 24) & 0xFF_FFFF) as u32;
assert_eq!(r1, 0x400000, "even A1 -> round down");
}
#[test]
fn test_rnd56_convergent_half_odd() {
let mut state = DspState::new(MemoryMap::default());
let val = pack_acc56(0x00, 0x400001, 0x800000);
let result = unsafe { jit_rnd56(&mut state as *mut DspState, val) };
let r1 = ((result >> 24) & 0xFF_FFFF) as u32;
assert_eq!(r1, 0x400002, "odd A1 -> round up");
}
#[test]
fn test_rnd56_s0_scaling() {
let mut state = DspState::new(MemoryMap::default());
state.registers[reg::SR] = 1 << sr::S0;
let val = pack_acc56(0x00, 0x400000, 0x800000);
let result = unsafe { jit_rnd56(&mut state as *mut DspState, val) };
let r0 = (result & 0xFF_FFFF) as u32;
assert_eq!(r0, 0);
}
#[test]
fn test_rnd56_s1_scaling() {
let mut state = DspState::new(MemoryMap::default());
state.registers[reg::SR] = 1 << sr::S1;
let val = pack_acc56(0x00, 0x400001, 0xC00000);
let result = unsafe { jit_rnd56(&mut state as *mut DspState, val) };
let r0 = (result & 0xFF_FFFF) as u32;
assert_eq!(
r0 & 0x7F_FFFF,
0,
"lower 23 bits of A0 should be cleared in S1 mode"
);
}
#[test]
fn test_read_pram_buffer() {
let mut pram = [0u32; 4];
pram[0] = 0xABCDEF;
pram[1] = 0xFFFFFF;
pram[2] = 0x123456;
let map = MemoryMap {
p_regions: vec![MemoryRegion {
start: 0,
end: 4,
kind: RegionKind::Buffer {
base: pram.as_mut_ptr(),
offset: 0,
},
}],
..Default::default()
};
assert_eq!(map.read_pram(0), 0xABCDEF);
assert_eq!(map.read_pram(1), 0xFFFFFF);
assert_eq!(map.read_pram(2), 0x123456);
assert_eq!(map.read_pram(4), 0); assert_eq!(map.p_space_end(), 4);
}
#[test]
fn test_read_pram_callback() {
unsafe extern "C" fn cb_read(_opaque: *mut std::ffi::c_void, addr: u32) -> u32 {
addr * 0x111
}
unsafe extern "C" fn cb_write(_opaque: *mut std::ffi::c_void, _addr: u32, _val: u32) {}
let map = MemoryMap {
p_regions: vec![MemoryRegion {
start: 0,
end: 8,
kind: RegionKind::Callback {
opaque: std::ptr::null_mut(),
read_fn: cb_read,
write_fn: cb_write,
},
}],
..Default::default()
};
assert_eq!(map.read_pram(0), 0);
assert_eq!(map.read_pram(3), 0x333);
assert_eq!(map.read_pram(7), 0x777);
assert_eq!(map.read_pram(8), 0); assert_eq!(map.p_space_end(), 8);
}
#[test]
fn test_read_pram_masks_to_24_bits() {
unsafe extern "C" fn cb_read(_opaque: *mut std::ffi::c_void, _addr: u32) -> u32 {
0xFF123456 }
unsafe extern "C" fn cb_write(_opaque: *mut std::ffi::c_void, _addr: u32, _val: u32) {}
let map = MemoryMap {
p_regions: vec![MemoryRegion {
start: 0,
end: 1,
kind: RegionKind::Callback {
opaque: std::ptr::null_mut(),
read_fn: cb_read,
write_fn: cb_write,
},
}],
..Default::default()
};
assert_eq!(map.read_pram(0), 0x123456);
}
#[test]
fn test_read_pram_empty_map() {
let map = MemoryMap::default();
assert_eq!(map.read_pram(0), 0);
assert_eq!(map.p_space_end(), 0);
}
#[test]
fn test_interrupt_state_from() {
assert!(matches!(InterruptState::from(0), InterruptState::None));
assert!(matches!(InterruptState::from(1), InterruptState::Fast));
assert!(matches!(InterruptState::from(2), InterruptState::Long));
assert!(matches!(InterruptState::from(255), InterruptState::None));
}
#[test]
fn test_interrupt_state_display() {
assert_eq!(format!("{}", InterruptState::None), "none");
assert_eq!(format!("{}", InterruptState::Fast), "fast");
assert_eq!(format!("{}", InterruptState::Long), "long");
}
#[test]
fn test_dirty_range_middle_words() {
let mut cache = PramDirtyBitmap::new(4096);
cache.mark_dirty(74);
assert!(cache.is_range_dirty(0, 192));
assert!(!cache.is_range_dirty(0, 64));
assert!(!cache.is_range_dirty(128, 192));
}
#[test]
fn test_clear_dirty_range_multi_word() {
let mut cache = PramDirtyBitmap::new(4096);
cache.mark_dirty(10); cache.mark_dirty(74); cache.mark_dirty(140); assert!(cache.is_range_dirty(0, 192));
cache.clear_dirty_range(0, 192);
assert!(!cache.is_range_dirty(0, 192));
}
#[test]
fn test_modulo_negative_wrap() {
let mut state = DspState::new(MemoryMap::default());
state.registers[reg::M0] = 3; state.registers[reg::R0] = 6; state.registers[reg::N0] = 0;
state.update_rn(0, -10);
let r = state.registers[reg::R0];
assert!(r <= 7, "R0 should be within modulo buffer bounds, got {r}");
}
}